From 98c2e8af1756a42c703206c7173a7ca1281a6c33 Mon Sep 17 00:00:00 2001 From: Eric Hurkman Date: Tue, 30 Jun 2026 14:19:45 -0400 Subject: [PATCH 1/2] fix: drop Notion telemetry in the browser instead of bouncing it off the proxy Notion fires fire-and-forget telemetry/analytics to notion.so, splunkcloud.com, and statsigapi.net (plus a same-origin /api/v3/ping keepalive). Previously these were rewritten to a same-origin /200/ pseudo endpoint, so every one produced a noisy 200 log line at the proxy even though the response was discarded. Suppress them entirely in the browser: window.fetch resolves a synthetic 200 with no network call, and XMLHttpRequest.open is redirected to a data: URL. No request leaves the browser, nothing reaches the proxy, and the page is unaffected since these responses were already thrown away. Co-Authored-By: Claude Opus 4.8 --- src/rewriters/custom/head.js | 64 ++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/src/rewriters/custom/head.js b/src/rewriters/custom/head.js index 61f845b..e537209 100644 --- a/src/rewriters/custom/head.js +++ b/src/rewriters/custom/head.js @@ -100,27 +100,59 @@ window.history.replaceState = new Proxy(window.history.replaceState, { }, }); (function () { - const replacedUrl = function (url) { + // Notion fires fire-and-forget telemetry and analytics to these hosts. Nooxy + // previously bounced them off the proxy via a same-origin pseudo endpoint, + // which produced a noisy 200 log line for every request. We now drop them + // entirely in the browser so no network request is made and nothing reaches + // the proxy. These responses were already discarded, so the page is unaffected. + const pathOf = function (url) { + const noScheme = url.replace(/^https?:\/\/[^\\/]*/, ''); + return noScheme.split('?')[0].split('#')[0]; + }; + + const isSuppressedUrl = function (url) { + // Same-origin Notion keepalive ping is non-essential and only adds proxy log noise. + if (pathOf(url) === '/api/v3/ping') { + return true; + } const match = /^https?:\/\/([^\\/]*)/.exec(url); const domain = match ? match[1] : ''; - if ( + return ( (domain.endsWith('notion.so') && !domain.endsWith('msgstore.www.notion.so')) || domain.endsWith('splunkcloud.com') || domain.endsWith('statsigapi.net') - ) { - // console.info('[NOOXY]', 'Suppress request:', url); - return url.replace(/^.*:(.*)\/\//, '/200/$1'); + ); + }; + + const urlOf = function (input) { + if (typeof input === 'string') { + return input; + } + // Request object + if (input && typeof input.url === 'string') { + return input.url; + } + try { + return String(input); + } catch (_e) { + return ''; } - return url; }; window.fetch = new Proxy(window.fetch, { apply: function (target, that, args) { - let url = args[0]; - const rest = Array.prototype.slice.call(args, 1); - url = replacedUrl(url); - // console.info('[NOOXY]', 'Fetch Request:', url); - return Reflect.apply(target, that, [url].concat(rest)); + if (isSuppressedUrl(urlOf(args[0]))) { + // Silently drop: resolve with a synthetic success, make no network call. + // console.info('[NOOXY]', 'Dropped request:', urlOf(args[0])); + return Promise.resolve( + new Response('{}', { + status: 200, + statusText: 'OK', + headers: { 'content-type': 'application/json' }, + }), + ); + } + return Reflect.apply(target, that, args); }, }); @@ -130,10 +162,14 @@ window.history.replaceState = new Proxy(window.history.replaceState, { xhr.open = new Proxy(xhr.open, { apply: function (target, that, args) { const method = args[0]; - let url = args[1]; + const url = args[1]; const rest = Array.prototype.slice.call(args, 2); - url = replacedUrl(url); - // console.info('[NOOXY]', 'XML Request:', url); + if (isSuppressedUrl(urlOf(url))) { + // Resolve locally via a data: URL (allowed by connect-src 'data:') so + // the request never leaves the browser and never hits the proxy. + // console.info('[NOOXY]', 'Dropped request:', urlOf(url)); + return Reflect.apply(target, that, ['GET', 'data:application/json,%7B%7D'].concat(rest)); + } return Reflect.apply(target, that, [method, url].concat(rest)); }, }); From 198e1d0dc6733dcf79ec7169666b0e7b588b27ff Mon Sep 17 00:00:00 2001 From: Eric Hurkman Date: Tue, 30 Jun 2026 14:53:46 -0400 Subject: [PATCH 2/2] fix: never suppress file.notion.so / file.notion.com downloads file.notion.so and file.notion.com serve signed PDF and attachment downloads. These are real content, not telemetry, but the blanket notion.so suppression rule would drop them and leave react-pdf (and any other attachment fetch) with an empty body, so the in-page PDF viewer renders nothing. Exempt these two hosts from suppression before the notion.so check. Co-Authored-By: Claude Opus 4.8 --- src/rewriters/custom/head.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/rewriters/custom/head.js b/src/rewriters/custom/head.js index e537209..fdd6b6e 100644 --- a/src/rewriters/custom/head.js +++ b/src/rewriters/custom/head.js @@ -117,6 +117,13 @@ window.history.replaceState = new Proxy(window.history.replaceState, { } const match = /^https?:\/\/([^\\/]*)/.exec(url); const domain = match ? match[1] : ''; + // file.notion.so and file.notion.com serve signed PDF and attachment downloads. + // They are real content, not telemetry, so they must not be dropped. The blanket + // notion.so rule below would otherwise leave react-pdf with an empty body. Keep + // this comment slash-free: the converter mis-parses a bare slash as a regex. + if (domain === 'file.notion.so' || domain === 'file.notion.com') { + return false; + } return ( (domain.endsWith('notion.so') && !domain.endsWith('msgstore.www.notion.so')) || domain.endsWith('splunkcloud.com') ||