Skip to content

Commit 52396b2

Browse files
authored
chore(browser): extract network interceptor script (#2042)
1 parent 046712a commit 52396b2

2 files changed

Lines changed: 12 additions & 12 deletions

File tree

src/browser/network-interceptor.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Injected page-side network interceptor.
3+
*
4+
* Used when the session-level capture channel (CDP/extension) is unavailable.
5+
* It captures fetch/XHR response bodies while matching the CDP path's
6+
* truncation contract: bodies above the per-entry cap are stored as a string
7+
* prefix with `bodyTruncated: true` and `bodyFullSize` set.
8+
*
9+
* Keep this script dependency-free; it executes in the target page context.
10+
*/
11+
export const NETWORK_INTERCEPTOR_JS = `(function(){if(window.__opencli_net)return;window.__opencli_net=[];var M=200,B=1048576,F=window.fetch;function capture(url,method,status,text,ct){if(window.__opencli_net.length>=M)return;var full=text?text.length:0,trunc=full>B,stored=trunc?text.slice(0,B):text,body=null;if(stored){if(trunc){body=stored}else{try{body=JSON.parse(stored)}catch(e){body=stored}}}var e={url:url,method:method||'GET',status:status,size:full,ct:ct,body:body,timestamp:Date.now()};if(trunc){e.bodyTruncated=true;e.bodyFullSize=full}window.__opencli_net.push(e)}window.fetch=async function(){var r=await F.apply(this,arguments);try{var ct=r.headers.get('content-type')||'';if(ct.includes('json')||ct.includes('text')){var c=r.clone(),t=await c.text();capture(r.url||(arguments[0]&&arguments[0].url)||String(arguments[0]),(arguments[1]&&arguments[1].method)||'GET',r.status,t,ct)}}catch(e){}return r};var X=XMLHttpRequest.prototype,O=X.open,S=X.send;X.open=function(m,u){this._om=m;this._ou=u;return O.apply(this,arguments)};X.send=function(){var x=this;x.addEventListener('load',function(){try{var ct=x.getResponseHeader('content-type')||'';if(ct.includes('json')||ct.includes('text')){capture(x._ou,x._om||'GET',x.status,x.responseText||'',ct)}}catch(e){}});return S.apply(this,arguments)}})()`;

src/cli.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { buildFindJs, buildSemanticFindJs, isFindError, type FindResult, type Fi
2727
import { inferShape } from './browser/shape.js';
2828
import { assignKeys } from './browser/network-key.js';
2929
import { DEFAULT_TTL_MS, findEntry, loadNetworkCache, saveNetworkCache, type CachedNetworkEntry } from './browser/network-cache.js';
30+
import { NETWORK_INTERCEPTOR_JS } from './browser/network-interceptor.js';
3031
import { parseFilter, shapeMatchesFilter } from './browser/shape-filter.js';
3132
import { buildHtmlTreeJs, type HtmlTreeResult } from './browser/html-tree.js';
3233
import { buildExtractHtmlJs, runExtractFromHtml } from './browser/extract.js';
@@ -1154,18 +1155,6 @@ Examples:
11541155

11551156
// ── Navigation ──
11561157

1157-
/**
1158-
* Network interceptor JS — injected on every open/navigate to capture
1159-
* fetch/XHR bodies when the session-level capture channel (CDP/extension)
1160-
* isn't available. Keeps parity with the CDP path's truncation contract:
1161-
* when a body exceeds the per-entry cap, we keep a string prefix and set
1162-
* `bodyTruncated: true` + `bodyFullSize: <original length>` so `browser
1163-
* network` can propagate a visible signal to the agent instead of
1164-
* silently dropping the body. Per-entry cap is 1 MiB and the ring is
1165-
* capped at 200 entries, bounding worst-case in-page memory.
1166-
*/
1167-
const NETWORK_INTERCEPTOR_JS = `(function(){if(window.__opencli_net)return;window.__opencli_net=[];var M=200,B=1048576,F=window.fetch;function capture(url,method,status,text,ct){if(window.__opencli_net.length>=M)return;var full=text?text.length:0,trunc=full>B,stored=trunc?text.slice(0,B):text,body=null;if(stored){if(trunc){body=stored}else{try{body=JSON.parse(stored)}catch(e){body=stored}}}var e={url:url,method:method||'GET',status:status,size:full,ct:ct,body:body,timestamp:Date.now()};if(trunc){e.bodyTruncated=true;e.bodyFullSize=full}window.__opencli_net.push(e)}window.fetch=async function(){var r=await F.apply(this,arguments);try{var ct=r.headers.get('content-type')||'';if(ct.includes('json')||ct.includes('text')){var c=r.clone(),t=await c.text();capture(r.url||(arguments[0]&&arguments[0].url)||String(arguments[0]),(arguments[1]&&arguments[1].method)||'GET',r.status,t,ct)}}catch(e){}return r};var X=XMLHttpRequest.prototype,O=X.open,S=X.send;X.open=function(m,u){this._om=m;this._ou=u;return O.apply(this,arguments)};X.send=function(){var x=this;x.addEventListener('load',function(){try{var ct=x.getResponseHeader('content-type')||'';if(ct.includes('json')||ct.includes('text')){capture(x._ou,x._om||'GET',x.status,x.responseText||'',ct)}}catch(e){}});return S.apply(this,arguments)}})()`;
1168-
11691158
addBrowserTabOption(browser.command('open').argument('<url>').description('Open URL in the browser session'))
11701159
.action(browserAction(async (page, url, opts) => {
11711160
// Start session-level capture before navigation (catches initial requests)

0 commit comments

Comments
 (0)