diff --git a/src/content/content.js b/src/content/content.js index 24eef4d..7d6b1fe 100644 --- a/src/content/content.js +++ b/src/content/content.js @@ -891,8 +891,11 @@ // ============================================================ // Fetch Interception — scans ALL POST requests with a body. // Service-agnostic: doesn't depend on URL patterns. + // Uses __ssOriginalFetch from the early hook (injected synchronously + // before any page JS) to ensure we have the real fetch, even if + // frameworks like Next.js (ChatGPT) store a reference early. // ============================================================ - const originalFetch = window.fetch; + const originalFetch = window.__ssOriginalFetch || window.fetch; // URLs to never touch (static assets, analytics, etc.) const SKIP_URL_PATTERNS = [ @@ -1012,6 +1015,10 @@ return originalFetch.call(this, url, options); }; + // Register our fetch interceptor so the early hook proxy can use it + window.__ssInterceptFetch = window.fetch; + window.__ssReady = true; + // ============================================================ // Document Upload Processing // @@ -1376,8 +1383,8 @@ // ============================================================ // XMLHttpRequest Interception — same aggressive approach // ============================================================ - const origOpen = XMLHttpRequest.prototype.open; - const origSend = XMLHttpRequest.prototype.send; + const origOpen = window.__ssOriginalXHROpen || XMLHttpRequest.prototype.open; + const origSend = window.__ssOriginalXHRSend || XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (method, url, ...rest) { this._ssUrl = url; diff --git a/src/content/injector.js b/src/content/injector.js index 9269750..edd9a0e 100644 --- a/src/content/injector.js +++ b/src/content/injector.js @@ -70,7 +70,32 @@ // Merge active profiles into a flat identity object for the content script const identity = mergeProfiles(identityData); - // Inject the main interception script into the page's world + // STEP 1: Inject a synchronous inline script that patches fetch/XHR + // IMMEDIATELY, before any page JS can store a reference to the originals. + // This thin proxy queues calls until the full content.js loads. + const earlyHook = document.createElement('script'); + earlyHook.textContent = `(function(){ + // Store the real fetch/XHR before any page script can + window.__ssOriginalFetch = window.fetch; + window.__ssOriginalXHROpen = XMLHttpRequest.prototype.open; + window.__ssOriginalXHRSend = XMLHttpRequest.prototype.send; + window.__ssReady = false; + window.__ssQueue = []; + + // Replace fetch with a proxy that queues until content.js is ready + window.fetch = function() { + if (window.__ssReady && window.__ssInterceptFetch) { + return window.__ssInterceptFetch.apply(this, arguments); + } + // If not ready yet, call original (no substitution possible) + return window.__ssOriginalFetch.apply(this, arguments); + }; + })();`; + (document.head || document.documentElement).appendChild(earlyHook); + earlyHook.remove(); + + // STEP 2: Load the full content.js which will use __ssOriginalFetch + // and set __ssReady = true when it's done hooking const script = document.createElement('script'); script.setAttribute('data-ss-config', JSON.stringify({ mappings, identity, settings })); script.src = api.runtime.getURL('src/content/content.js');