im_wower
·
2026-03-29
page-interceptor.js
1(function () {
2 const previousRuntime = window.__baaFirefoxIntercepted__;
3 const hasManagedRuntime = !!previousRuntime
4 && typeof previousRuntime === "object"
5 && typeof previousRuntime.teardown === "function";
6 const legacyIntercepted = previousRuntime === true
7 || (hasManagedRuntime && previousRuntime.legacyIntercepted === true);
8
9 if (hasManagedRuntime) {
10 try {
11 previousRuntime.teardown();
12 } catch (_) {}
13 }
14
15 const BODY_LIMIT = 5000;
16 const originalFetch = hasManagedRuntime ? previousRuntime.originalFetch : window.fetch;
17 const originalXhrOpen = hasManagedRuntime ? previousRuntime.originalXhrOpen : XMLHttpRequest.prototype.open;
18 const originalXhrSend = hasManagedRuntime ? previousRuntime.originalXhrSend : XMLHttpRequest.prototype.send;
19 const originalXhrSetRequestHeader = hasManagedRuntime ? previousRuntime.originalXhrSetRequestHeader : XMLHttpRequest.prototype.setRequestHeader;
20 const activeProxyControllers = new Map();
21 const cleanupHandlers = [];
22
23 function addWindowListener(type, listener) {
24 window.addEventListener(type, listener);
25 cleanupHandlers.push(() => {
26 window.removeEventListener(type, listener);
27 });
28 }
29
30 function hostnameMatches(hostname, hosts) {
31 return hosts.some((host) => hostname === host || hostname.endsWith(`.${host}`));
32 }
33
34 function isLikelyStaticPath(pathname = "") {
35 const lower = pathname.toLowerCase();
36 return lower.startsWith("/_next/")
37 || lower.startsWith("/assets/")
38 || lower.startsWith("/static/")
39 || lower.startsWith("/images/")
40 || lower.startsWith("/fonts/")
41 || lower.startsWith("/favicon")
42 || /\.(?:js|mjs|css|map|png|jpe?g|gif|svg|webp|ico|woff2?|ttf|otf|mp4|webm|txt)$/i.test(lower);
43 }
44
45 const PLATFORM_RULES = [
46 {
47 platform: "claude",
48 pageHosts: ["claude.ai"],
49 requestHosts: ["claude.ai"],
50 matchesPageHost(hostname) {
51 return hostnameMatches(hostname, this.pageHosts);
52 },
53 matchesRequestHost(hostname) {
54 return hostnameMatches(hostname, this.requestHosts);
55 },
56 shouldTrack(pathname) {
57 return pathname.includes("/api/");
58 },
59 isSse(pathname, contentType) {
60 return contentType.includes("text/event-stream") || pathname.endsWith("/completion");
61 }
62 },
63 {
64 platform: "chatgpt",
65 pageHosts: ["chatgpt.com", "chat.openai.com"],
66 requestHosts: ["chatgpt.com", "chat.openai.com", "openai.com", "oaiusercontent.com"],
67 matchesPageHost(hostname) {
68 return hostnameMatches(hostname, this.pageHosts);
69 },
70 matchesRequestHost(hostname) {
71 return hostnameMatches(hostname, this.requestHosts);
72 },
73 shouldTrack(pathname) {
74 const lower = pathname.toLowerCase();
75 return pathname.includes("/backend-api/")
76 || pathname.includes("/backend-anon/")
77 || pathname.includes("/public-api/")
78 || lower.includes("/conversation")
79 || lower.includes("/models")
80 || lower.includes("/files")
81 || (!isLikelyStaticPath(pathname) && (lower.includes("/api/") || lower.includes("/backend")));
82 },
83 isSse(pathname, contentType) {
84 const lower = pathname.toLowerCase();
85 return contentType.includes("text/event-stream")
86 || lower.includes("/conversation")
87 || lower.includes("/backend-api/conversation");
88 }
89 },
90 {
91 platform: "gemini",
92 pageHosts: ["gemini.google.com"],
93 requestHosts: ["gemini.google.com"],
94 matchesPageHost(hostname) {
95 return hostnameMatches(hostname, this.pageHosts);
96 },
97 matchesRequestHost(hostname) {
98 return hostnameMatches(hostname, this.requestHosts);
99 },
100 shouldTrack(pathname) {
101 const lower = pathname.toLowerCase();
102 return pathname.startsWith("/_/")
103 || pathname.includes("/api/")
104 || lower.includes("bardchatui")
105 || lower.includes("streamgenerate")
106 || lower.includes("generatecontent")
107 || lower.includes("modelresponse")
108 || (!isLikelyStaticPath(pathname) && lower.includes("assistant"));
109 },
110 isSse(pathname, contentType) {
111 return contentType.includes("text/event-stream") || pathname.toLowerCase().includes("streamgenerate");
112 }
113 }
114 ];
115
116 function findPageRule(hostname) {
117 return PLATFORM_RULES.find((rule) => rule.matchesPageHost(hostname)) || null;
118 }
119
120 function findRequestRule(hostname) {
121 return PLATFORM_RULES.find((rule) => rule.matchesRequestHost(hostname)) || null;
122 }
123
124 const pageRule = findPageRule(location.hostname);
125 if (!pageRule) return;
126
127 function getRequestContext(url) {
128 try {
129 const parsed = new URL(url, location.href);
130 const rule = findRequestRule(parsed.hostname);
131 if (!rule || rule.platform !== pageRule.platform) return null;
132 return { parsed, rule };
133 } catch (_) {
134 return null;
135 }
136 }
137
138 function shouldTrack(url) {
139 const context = getRequestContext(url);
140 return context ? context.rule.shouldTrack(context.parsed.pathname) : false;
141 }
142
143 function readHeaders(headersLike) {
144 const out = {};
145 try {
146 const headers = new Headers(headersLike || {});
147 headers.forEach((value, key) => {
148 out[key] = value;
149 });
150 } catch (_) {}
151 return out;
152 }
153
154 function readRawHeaders(rawHeaders) {
155 const out = {};
156 for (const line of String(rawHeaders || "").split(/\r?\n/)) {
157 const index = line.indexOf(":");
158 if (index <= 0) continue;
159 const name = line.slice(0, index).trim().toLowerCase();
160 const value = line.slice(index + 1).trim();
161 if (name) out[name] = value;
162 }
163 return out;
164 }
165
166 function trim(text) {
167 if (text == null) return null;
168 return text.length > BODY_LIMIT ? text.slice(0, BODY_LIMIT) : text;
169 }
170
171 function trimToNull(value) {
172 if (typeof value !== "string") return null;
173 const normalized = value.trim();
174 return normalized ? normalized : null;
175 }
176
177 function describeError(error) {
178 if (typeof error === "string") {
179 return trimToNull(error) || "unknown_error";
180 }
181
182 return trimToNull(error?.message) || String(error || "unknown_error");
183 }
184
185 function emit(type, detail, rule = pageRule) {
186 window.dispatchEvent(new CustomEvent(type, {
187 detail: {
188 platform: rule.platform,
189 ...detail
190 }
191 }));
192 }
193
194 function emitNet(detail, rule = pageRule) {
195 emit("__baa_net__", detail, rule);
196 }
197
198 function emitSse(detail, rule = pageRule) {
199 emit("__baa_sse__", detail, rule);
200 }
201
202 function emitDiagnostic(event, detail = {}, rule = pageRule) {
203 emit("__baa_diagnostic__", {
204 event,
205 ...detail
206 }, rule);
207 }
208
209 function isForbiddenProxyHeader(name) {
210 const lower = String(name || "").toLowerCase();
211 return lower === "accept-encoding"
212 || lower === "connection"
213 || lower === "content-length"
214 || lower === "cookie"
215 || lower === "host"
216 || lower === "origin"
217 || lower === "referer"
218 || lower === "user-agent"
219 || lower.startsWith("sec-");
220 }
221
222 emit("__baa_ready__", {
223 platform: pageRule.platform,
224 url: location.href,
225 source: "page-interceptor"
226 }, pageRule);
227
228 try { console.log("[BAA]", "interceptor_active", pageRule.platform, location.href.slice(0, 120)); } catch (_) {}
229 emitDiagnostic("interceptor_active", {
230 source: "page-interceptor",
231 url: location.href
232 }, pageRule);
233
234 function trimBodyValue(body) {
235 try {
236 if (body == null) return null;
237 if (typeof body === "string") return trim(body);
238 if (body instanceof URLSearchParams) return trim(body.toString());
239 if (body instanceof FormData) {
240 const pairs = [];
241 for (const [key, value] of body.entries()) {
242 pairs.push([key, typeof value === "string" ? value : `[${value?.constructor?.name || "binary"}]`]);
243 }
244 return trim(JSON.stringify(pairs));
245 }
246 if (body instanceof Blob) return `[blob ${body.type || "application/octet-stream"} ${body.size}]`;
247 if (body instanceof ArrayBuffer) return `[arraybuffer ${body.byteLength}]`;
248 if (ArrayBuffer.isView(body)) return `[typedarray ${body.byteLength}]`;
249 return trim(JSON.stringify(body));
250 } catch (_) {
251 return null;
252 }
253 }
254
255 async function readRequestBody(input, init) {
256 try {
257 if (init && Object.prototype.hasOwnProperty.call(init, "body")) {
258 return trimBodyValue(init.body);
259 }
260 if (input instanceof Request && !input.bodyUsed) {
261 return trim(await input.clone().text());
262 }
263 } catch (_) {}
264 return null;
265 }
266
267 async function readResponseText(response) {
268 try {
269 return trim(await response.clone().text());
270 } catch (_) {
271 return null;
272 }
273 }
274
275 async function streamSse(url, method, requestBody, response, startedAt, rule) {
276 try { console.log("[BAA]", "sse_stream_start", method, url.slice(0, 120)); } catch (_) {}
277 emitDiagnostic("sse_stream_start", {
278 method,
279 source: "page-interceptor",
280 url
281 }, rule);
282 let buffer = "";
283 const decoder = new TextDecoder();
284 const emitBufferedChunk = () => {
285 if (!buffer.trim()) return false;
286
287 emitSse({
288 url,
289 method,
290 reqBody: requestBody,
291 chunk: buffer,
292 ts: Date.now()
293 }, rule);
294 buffer = "";
295 return true;
296 };
297
298 try {
299 const clone = response.clone();
300 if (!clone.body) return;
301
302 const reader = clone.body.getReader();
303
304 while (true) {
305 const { done, value } = await reader.read();
306 if (done) break;
307 buffer += decoder.decode(value, { stream: true });
308
309 const chunks = buffer.split("\n\n");
310 buffer = chunks.pop() || "";
311
312 for (const chunk of chunks) {
313 if (!chunk.trim()) continue;
314 emitSse({
315 url,
316 method,
317 reqBody: requestBody,
318 chunk,
319 ts: Date.now()
320 }, rule);
321 }
322 }
323
324 buffer += decoder.decode();
325 emitBufferedChunk();
326
327 const duration = Date.now() - startedAt;
328 try { console.log("[BAA]", "sse_stream_done", method, url.slice(0, 120), "duration=" + duration + "ms"); } catch (_) {}
329 emitDiagnostic("sse_stream_done", {
330 duration,
331 method,
332 source: "page-interceptor",
333 url
334 }, rule);
335 emitSse({
336 url,
337 method,
338 reqBody: requestBody,
339 done: true,
340 ts: Date.now(),
341 duration
342 }, rule);
343 } catch (error) {
344 buffer += decoder.decode();
345 emitBufferedChunk();
346
347 emitSse({
348 url,
349 method,
350 reqBody: requestBody,
351 error: describeError(error),
352 ts: Date.now(),
353 duration: Date.now() - startedAt
354 }, rule);
355 }
356 }
357
358 async function streamProxyResponse(detail, response, startedAt, rule, requestBody) {
359 const contentType = response.headers.get("content-type") || "";
360 const shouldSplitChunks = contentType.includes("text/event-stream");
361 const streamId = detail.stream_id || detail.streamId || detail.id;
362 const proxySource = trimToNull(detail.source) || "proxy";
363 let seq = 0;
364
365 emitSse({
366 id: detail.id,
367 stream_id: streamId,
368 method: detail.method,
369 open: true,
370 reqBody: requestBody,
371 source: proxySource,
372 status: response.status,
373 ts: Date.now(),
374 url: detail.url
375 }, rule);
376
377 try {
378 if (!response.body) {
379 emitSse(
380 response.status >= 400
381 ? {
382 error: `upstream_status_${response.status}`,
383 id: detail.id,
384 method: detail.method,
385 reqBody: requestBody,
386 source: proxySource,
387 status: response.status,
388 stream_id: streamId,
389 ts: Date.now(),
390 url: detail.url
391 }
392 : {
393 id: detail.id,
394 stream_id: streamId,
395 done: true,
396 duration: Date.now() - startedAt,
397 method: detail.method,
398 reqBody: requestBody,
399 source: proxySource,
400 status: response.status,
401 ts: Date.now(),
402 url: detail.url
403 },
404 rule
405 );
406 return;
407 }
408
409 const reader = response.body.getReader();
410 const decoder = new TextDecoder();
411 let buffer = "";
412
413 while (true) {
414 const { done, value } = await reader.read();
415 if (done) break;
416
417 buffer += decoder.decode(value, { stream: true });
418 const chunks = shouldSplitChunks ? buffer.split("\n\n") : [buffer];
419 buffer = shouldSplitChunks ? (chunks.pop() || "") : "";
420
421 for (const chunk of chunks) {
422 if (!chunk.trim()) continue;
423 seq += 1;
424 emitSse({
425 chunk,
426 id: detail.id,
427 method: detail.method,
428 reqBody: requestBody,
429 seq,
430 source: proxySource,
431 status: response.status,
432 stream_id: streamId,
433 ts: Date.now(),
434 url: detail.url
435 }, rule);
436 }
437 }
438
439 buffer += decoder.decode();
440 if (buffer.trim()) {
441 seq += 1;
442 emitSse({
443 chunk: buffer,
444 id: detail.id,
445 method: detail.method,
446 reqBody: requestBody,
447 seq,
448 source: proxySource,
449 status: response.status,
450 stream_id: streamId,
451 ts: Date.now(),
452 url: detail.url
453 }, rule);
454 }
455
456 emitSse(
457 response.status >= 400
458 ? {
459 error: `upstream_status_${response.status}`,
460 id: detail.id,
461 method: detail.method,
462 reqBody: requestBody,
463 seq,
464 source: proxySource,
465 status: response.status,
466 stream_id: streamId,
467 ts: Date.now(),
468 url: detail.url
469 }
470 : {
471 done: true,
472 duration: Date.now() - startedAt,
473 id: detail.id,
474 method: detail.method,
475 reqBody: requestBody,
476 seq,
477 source: proxySource,
478 status: response.status,
479 stream_id: streamId,
480 ts: Date.now(),
481 url: detail.url
482 },
483 rule
484 );
485 } catch (error) {
486 emitSse({
487 error: error.message,
488 id: detail.id,
489 method: detail.method,
490 reqBody: requestBody,
491 source: proxySource,
492 seq,
493 status: response.status,
494 stream_id: streamId,
495 ts: Date.now(),
496 url: detail.url
497 }, rule);
498 }
499 }
500
501 if (!legacyIntercepted) {
502 addWindowListener("__baa_proxy_request__", async (event) => {
503 let detail = event.detail || {};
504 if (typeof detail === "string") {
505 try {
506 detail = JSON.parse(detail);
507 } catch (_) {
508 detail = {};
509 }
510 }
511
512 const id = detail.id;
513 const method = String(detail.method || "GET").toUpperCase();
514 const rawPath = detail.path || detail.url || location.href;
515 const responseMode = String(detail.response_mode || detail.responseMode || "buffered").toLowerCase();
516 const proxySource = trimToNull(detail.source) || "proxy";
517
518 if (!id) return;
519
520 const proxyAbortController = new AbortController();
521 activeProxyControllers.set(id, proxyAbortController);
522
523 try {
524 const url = new URL(rawPath, location.origin).href;
525 try { console.log("[BAA]", "proxy_fetch", method, new URL(url).pathname); } catch (_) {}
526 const context = getRequestContext(url);
527 const headers = new Headers();
528 const startedAt = Date.now();
529
530 for (const [name, value] of Object.entries(detail.headers || {})) {
531 if (!name || value == null || value === "") continue;
532 if (isForbiddenProxyHeader(name)) continue;
533 headers.set(String(name).toLowerCase(), String(value));
534 }
535
536 let body = null;
537 if (method !== "GET" && method !== "HEAD" && Object.prototype.hasOwnProperty.call(detail, "body")) {
538 if (typeof detail.body === "string") {
539 body = detail.body;
540 } else if (detail.body != null) {
541 if (!headers.has("content-type")) headers.set("content-type", "application/json");
542 body = JSON.stringify(detail.body);
543 }
544 }
545
546 const response = await originalFetch.call(window, url, {
547 method,
548 headers,
549 body,
550 credentials: "include",
551 signal: proxyAbortController.signal
552 });
553 const resHeaders = readHeaders(response.headers);
554 const contentType = response.headers.get("content-type") || "";
555 const isSse = context ? context.rule.isSse(context.parsed.pathname, contentType) : false;
556 const reqHeaders = readHeaders(headers);
557 const reqBody = typeof body === "string" ? trim(body) : null;
558
559 if (responseMode === "sse") {
560 emitNet({
561 url,
562 method,
563 reqHeaders,
564 reqBody,
565 status: response.status,
566 resHeaders,
567 resBody: null,
568 duration: Date.now() - startedAt,
569 sse: true,
570 source: proxySource
571 }, pageRule);
572
573 const replayDetail = {
574 ...detail,
575 id,
576 method,
577 stream_id: detail.stream_id || detail.streamId || id,
578 source: proxySource,
579 url
580 };
581 await streamProxyResponse(replayDetail, response, startedAt, pageRule, reqBody);
582 return;
583 }
584
585 const responseBody = await response.text();
586 const trimmedResponseBody = trim(responseBody);
587
588 emitNet({
589 url,
590 method,
591 reqHeaders,
592 reqBody,
593 status: response.status,
594 resHeaders,
595 resBody: isSse && pageRule.platform !== "gemini" ? null : trimmedResponseBody,
596 duration: Date.now() - startedAt,
597 sse: isSse,
598 source: proxySource
599 }, pageRule);
600
601 if (isSse && trimmedResponseBody) {
602 emitSse({
603 url,
604 method,
605 reqBody,
606 chunk: trimmedResponseBody,
607 source: proxySource,
608 ts: Date.now()
609 }, pageRule);
610 emitSse({
611 url,
612 method,
613 reqBody,
614 done: true,
615 source: proxySource,
616 ts: Date.now(),
617 duration: Date.now() - startedAt
618 }, pageRule);
619 }
620
621 emit("__baa_proxy_response__", {
622 id,
623 platform: pageRule.platform,
624 url,
625 method,
626 ok: response.ok,
627 status: response.status,
628 body: responseBody
629 }, pageRule);
630 } catch (error) {
631 emitNet({
632 url: rawPath,
633 method,
634 reqHeaders: readHeaders(detail.headers || {}),
635 reqBody: typeof detail.body === "string" ? trim(detail.body) : trimBodyValue(detail.body),
636 error: error.message,
637 source: proxySource
638 }, pageRule);
639
640 if (responseMode === "sse") {
641 emitSse({
642 error: error.message,
643 id,
644 method,
645 reqBody: typeof detail.body === "string" ? trim(detail.body) : trimBodyValue(detail.body),
646 source: proxySource,
647 status: null,
648 stream_id: detail.stream_id || detail.streamId || id,
649 ts: Date.now(),
650 url: rawPath
651 }, pageRule);
652 return;
653 }
654
655 emit("__baa_proxy_response__", {
656 id,
657 platform: pageRule.platform,
658 url: rawPath,
659 method,
660 ok: false,
661 error: error.message
662 }, pageRule);
663 } finally {
664 activeProxyControllers.delete(id);
665 }
666 });
667
668 addWindowListener("__baa_proxy_cancel__", (event) => {
669 let detail = event.detail || {};
670
671 if (typeof detail === "string") {
672 try {
673 detail = JSON.parse(detail);
674 } catch (_) {
675 detail = {};
676 }
677 }
678
679 const id = detail?.id || detail?.requestId;
680 if (!id) return;
681
682 const controller = activeProxyControllers.get(id);
683 if (!controller) return;
684
685 activeProxyControllers.delete(id);
686 controller.abort(detail?.reason || "browser_request_cancelled");
687 });
688 }
689
690 window.fetch = async function patchedFetch(input, init) {
691 const url = input instanceof Request ? input.url : String(input);
692 const context = getRequestContext(url);
693 if (!context || !context.rule.shouldTrack(context.parsed.pathname)) {
694 return originalFetch.apply(this, arguments);
695 }
696
697 const method = ((init && init.method) || (input instanceof Request ? input.method : "GET")).toUpperCase();
698 const startedAt = Date.now();
699 const reqHeaders = readHeaders(init && init.headers ? init.headers : (input instanceof Request ? input.headers : null));
700 const reqBody = await readRequestBody(input, init);
701 emitDiagnostic("fetch_intercepted", {
702 method,
703 source: "page-interceptor",
704 url
705 }, context.rule);
706
707 try {
708 const response = await originalFetch.apply(this, arguments);
709 const resHeaders = readHeaders(response.headers);
710 const contentType = response.headers.get("content-type") || "";
711 const isSse = context.rule.isSse(context.parsed.pathname, contentType);
712 try { console.log("[BAA]", "fetch", method, context.parsed.pathname, isSse ? "SSE" : "buffered", response.status); } catch (_) {}
713
714 if (isSse) {
715 emitNet({
716 url,
717 method,
718 reqHeaders,
719 reqBody,
720 status: response.status,
721 resHeaders,
722 duration: Date.now() - startedAt,
723 sse: true,
724 source: "page"
725 }, context.rule);
726 streamSse(url, method, reqBody, response, startedAt, context.rule);
727 return response;
728 }
729
730 const resBody = await readResponseText(response);
731 emitNet({
732 url,
733 method,
734 reqHeaders,
735 reqBody,
736 status: response.status,
737 resHeaders,
738 resBody,
739 duration: Date.now() - startedAt,
740 source: "page"
741 }, context.rule);
742 return response;
743 } catch (error) {
744 emitNet({
745 url,
746 method,
747 reqHeaders,
748 reqBody,
749 error: error.message,
750 duration: Date.now() - startedAt,
751 source: "page"
752 }, context.rule);
753 throw error;
754 }
755 };
756
757 XMLHttpRequest.prototype.open = function patchedOpen(method, url) {
758 this.__baaMethod = String(method || "GET").toUpperCase();
759 this.__baaUrl = typeof url === "string" ? url : String(url);
760 this.__baaRequestHeaders = {};
761 return originalXhrOpen.apply(this, arguments);
762 };
763
764 XMLHttpRequest.prototype.setRequestHeader = function patchedSetRequestHeader(name, value) {
765 if (this.__baaRequestHeaders && name) {
766 this.__baaRequestHeaders[String(name).toLowerCase()] = String(value || "");
767 }
768 return originalXhrSetRequestHeader.apply(this, arguments);
769 };
770
771 XMLHttpRequest.prototype.send = function patchedSend(body) {
772 const url = this.__baaUrl;
773 const context = getRequestContext(url);
774 if (!context || !context.rule.shouldTrack(context.parsed.pathname)) {
775 return originalXhrSend.apply(this, arguments);
776 }
777
778 const method = this.__baaMethod || "GET";
779 try { console.log("[BAA]", "xhr", method, context.parsed.pathname); } catch (_) {}
780 const reqBody = trimBodyValue(body);
781 const reqHeaders = { ...(this.__baaRequestHeaders || {}) };
782 const startedAt = Date.now();
783 let finalized = false;
784 let terminalError = null;
785
786 const finalize = () => {
787 if (finalized) return;
788 finalized = true;
789
790 const resHeaders = readRawHeaders(this.getAllResponseHeaders());
791 const contentType = String(this.getResponseHeader("content-type") || "");
792 const duration = Date.now() - startedAt;
793 const error = terminalError || (this.status === 0 ? "xhr_failed" : null);
794 const isSse = context.rule.isSse(context.parsed.pathname, contentType);
795 let responseText = null;
796
797 try {
798 responseText = typeof this.responseText === "string" ? trim(this.responseText) : null;
799 } catch (_) {
800 responseText = null;
801 }
802
803 emitNet({
804 url,
805 method,
806 reqHeaders,
807 reqBody,
808 status: this.status || null,
809 resHeaders,
810 resBody: isSse && context.rule.platform !== "gemini" ? null : responseText,
811 error,
812 duration,
813 sse: isSse,
814 source: "xhr"
815 }, context.rule);
816
817 if (isSse && responseText) {
818 emitSse({
819 url,
820 method,
821 reqBody,
822 chunk: responseText,
823 ts: Date.now()
824 }, context.rule);
825 }
826
827 if (isSse) {
828 emitSse({
829 url,
830 method,
831 reqBody,
832 done: true,
833 ts: Date.now(),
834 duration
835 }, context.rule);
836 }
837 };
838
839 this.addEventListener("error", () => {
840 terminalError = "xhr_error";
841 }, { once: true });
842 this.addEventListener("abort", () => {
843 terminalError = "xhr_aborted";
844 }, { once: true });
845 this.addEventListener("timeout", () => {
846 terminalError = "xhr_timeout";
847 }, { once: true });
848 this.addEventListener("loadend", finalize, { once: true });
849
850 return originalXhrSend.apply(this, arguments);
851 };
852
853 const runtime = {
854 legacyIntercepted,
855 originalFetch,
856 originalXhrOpen,
857 originalXhrSend,
858 originalXhrSetRequestHeader,
859 teardown() {
860 for (const controller of activeProxyControllers.values()) {
861 try {
862 controller.abort("observer_reinject");
863 } catch (_) {}
864 }
865 activeProxyControllers.clear();
866
867 window.fetch = originalFetch;
868 XMLHttpRequest.prototype.open = originalXhrOpen;
869 XMLHttpRequest.prototype.send = originalXhrSend;
870 XMLHttpRequest.prototype.setRequestHeader = originalXhrSetRequestHeader;
871
872 while (cleanupHandlers.length > 0) {
873 const cleanup = cleanupHandlers.pop();
874 try {
875 cleanup();
876 } catch (_) {}
877 }
878
879 if (window.__baaFirefoxIntercepted__ === runtime) {
880 if (legacyIntercepted) {
881 window.__baaFirefoxIntercepted__ = true;
882 return;
883 }
884
885 try {
886 delete window.__baaFirefoxIntercepted__;
887 } catch (_) {
888 window.__baaFirefoxIntercepted__ = null;
889 }
890 }
891 }
892 };
893
894 window.__baaFirefoxIntercepted__ = runtime;
895})();