nothing-browser 0.0.10 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/index.js +46 -20
- package/dist/piggy.js +288 -20
- package/dist/register/index.js +242 -0
- package/package.json +1 -1
- package/piggy/client/index.ts +124 -248
- package/piggy/intercept/scripts.ts +153 -0
- package/piggy/register/index.ts +182 -36
package/dist/client/index.js
CHANGED
|
@@ -746,6 +746,7 @@ class PiggyClient {
|
|
|
746
746
|
buf = "";
|
|
747
747
|
eventBuffer = "";
|
|
748
748
|
eventHandlers = new Map;
|
|
749
|
+
globalEventHandlers = new Map;
|
|
749
750
|
constructor(socketPath = SOCKET_PATH) {
|
|
750
751
|
this.socketPath = socketPath;
|
|
751
752
|
this.eventHandlers.set("default", new Map);
|
|
@@ -804,23 +805,20 @@ class PiggyClient {
|
|
|
804
805
|
const handlers = this.eventHandlers.get(effectiveTabId);
|
|
805
806
|
const handler = handlers?.get(name);
|
|
806
807
|
if (handler) {
|
|
807
|
-
|
|
808
|
+
let parsedData;
|
|
809
|
+
try {
|
|
810
|
+
parsedData = JSON.parse(data || "null");
|
|
811
|
+
} catch {
|
|
812
|
+
parsedData = data;
|
|
813
|
+
}
|
|
814
|
+
Promise.resolve(handler(parsedData)).then((response) => {
|
|
808
815
|
if (response && typeof response === "object" && "success" in response) {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
}).catch((e) => logger_default.error(`Failed to send exposed result: ${e}`));
|
|
816
|
-
} else {
|
|
817
|
-
this.send("exposed.result", {
|
|
818
|
-
tabId: effectiveTabId,
|
|
819
|
-
callId,
|
|
820
|
-
result: response.error || "Unknown error",
|
|
821
|
-
isError: true
|
|
822
|
-
}).catch((e) => logger_default.error(`Failed to send exposed error: ${e}`));
|
|
823
|
-
}
|
|
816
|
+
this.send("exposed.result", {
|
|
817
|
+
tabId: effectiveTabId,
|
|
818
|
+
callId,
|
|
819
|
+
result: response.success ? JSON.stringify(response.result) : response.error || "Unknown error",
|
|
820
|
+
isError: !response.success
|
|
821
|
+
}).catch((e) => logger_default.error(`Failed to send exposed result: ${e}`));
|
|
824
822
|
} else {
|
|
825
823
|
this.send("exposed.result", {
|
|
826
824
|
tabId: effectiveTabId,
|
|
@@ -840,8 +838,38 @@ class PiggyClient {
|
|
|
840
838
|
} else {
|
|
841
839
|
logger_default.warn(`No handler for exposed function: ${name} in tab ${effectiveTabId}`);
|
|
842
840
|
}
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
if (event.event === "navigate") {
|
|
844
|
+
const handlers = this.globalEventHandlers.get(`navigate:${event.tabId}`);
|
|
845
|
+
if (handlers) {
|
|
846
|
+
for (const h of handlers) {
|
|
847
|
+
try {
|
|
848
|
+
h(event.url);
|
|
849
|
+
} catch (e) {
|
|
850
|
+
logger_default.error(`navigate handler error: ${e}`);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
const wildcard = this.globalEventHandlers.get("navigate:*");
|
|
855
|
+
if (wildcard) {
|
|
856
|
+
for (const h of wildcard) {
|
|
857
|
+
try {
|
|
858
|
+
h({ url: event.url, tabId: event.tabId });
|
|
859
|
+
} catch {}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return;
|
|
843
863
|
}
|
|
844
864
|
}
|
|
865
|
+
onEvent(eventName, tabId, handler) {
|
|
866
|
+
const key = `${eventName}:${tabId}`;
|
|
867
|
+
if (!this.globalEventHandlers.has(key)) {
|
|
868
|
+
this.globalEventHandlers.set(key, new Set);
|
|
869
|
+
}
|
|
870
|
+
this.globalEventHandlers.get(key).add(handler);
|
|
871
|
+
return () => this.globalEventHandlers.get(key)?.delete(handler);
|
|
872
|
+
}
|
|
845
873
|
disconnect() {
|
|
846
874
|
this.socket?.destroy();
|
|
847
875
|
this.socket = null;
|
|
@@ -1023,15 +1051,13 @@ class PiggyClient {
|
|
|
1023
1051
|
await this.send("session.import", { data, tabId });
|
|
1024
1052
|
}
|
|
1025
1053
|
async exposeFunction(name, handler, tabId = "default") {
|
|
1026
|
-
if (!this.eventHandlers.has(tabId))
|
|
1054
|
+
if (!this.eventHandlers.has(tabId))
|
|
1027
1055
|
this.eventHandlers.set(tabId, new Map);
|
|
1028
|
-
}
|
|
1029
1056
|
this.eventHandlers.get(tabId).set(name, async (data) => {
|
|
1030
1057
|
try {
|
|
1031
1058
|
const result = await handler(data);
|
|
1032
|
-
if (result && typeof result === "object" && (("success" in result) || ("error" in result)))
|
|
1059
|
+
if (result && typeof result === "object" && (("success" in result) || ("error" in result)))
|
|
1033
1060
|
return result;
|
|
1034
|
-
}
|
|
1035
1061
|
return { success: true, result };
|
|
1036
1062
|
} catch (err) {
|
|
1037
1063
|
return { success: false, error: err.message || String(err) };
|
package/dist/piggy.js
CHANGED
|
@@ -6274,6 +6274,7 @@ class PiggyClient {
|
|
|
6274
6274
|
buf = "";
|
|
6275
6275
|
eventBuffer = "";
|
|
6276
6276
|
eventHandlers = new Map;
|
|
6277
|
+
globalEventHandlers = new Map;
|
|
6277
6278
|
constructor(socketPath = SOCKET_PATH) {
|
|
6278
6279
|
this.socketPath = socketPath;
|
|
6279
6280
|
this.eventHandlers.set("default", new Map);
|
|
@@ -6332,23 +6333,20 @@ class PiggyClient {
|
|
|
6332
6333
|
const handlers = this.eventHandlers.get(effectiveTabId);
|
|
6333
6334
|
const handler = handlers?.get(name);
|
|
6334
6335
|
if (handler) {
|
|
6335
|
-
|
|
6336
|
+
let parsedData;
|
|
6337
|
+
try {
|
|
6338
|
+
parsedData = JSON.parse(data || "null");
|
|
6339
|
+
} catch {
|
|
6340
|
+
parsedData = data;
|
|
6341
|
+
}
|
|
6342
|
+
Promise.resolve(handler(parsedData)).then((response) => {
|
|
6336
6343
|
if (response && typeof response === "object" && "success" in response) {
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
}).catch((e) => logger_default.error(`Failed to send exposed result: ${e}`));
|
|
6344
|
-
} else {
|
|
6345
|
-
this.send("exposed.result", {
|
|
6346
|
-
tabId: effectiveTabId,
|
|
6347
|
-
callId,
|
|
6348
|
-
result: response.error || "Unknown error",
|
|
6349
|
-
isError: true
|
|
6350
|
-
}).catch((e) => logger_default.error(`Failed to send exposed error: ${e}`));
|
|
6351
|
-
}
|
|
6344
|
+
this.send("exposed.result", {
|
|
6345
|
+
tabId: effectiveTabId,
|
|
6346
|
+
callId,
|
|
6347
|
+
result: response.success ? JSON.stringify(response.result) : response.error || "Unknown error",
|
|
6348
|
+
isError: !response.success
|
|
6349
|
+
}).catch((e) => logger_default.error(`Failed to send exposed result: ${e}`));
|
|
6352
6350
|
} else {
|
|
6353
6351
|
this.send("exposed.result", {
|
|
6354
6352
|
tabId: effectiveTabId,
|
|
@@ -6368,8 +6366,38 @@ class PiggyClient {
|
|
|
6368
6366
|
} else {
|
|
6369
6367
|
logger_default.warn(`No handler for exposed function: ${name} in tab ${effectiveTabId}`);
|
|
6370
6368
|
}
|
|
6369
|
+
return;
|
|
6370
|
+
}
|
|
6371
|
+
if (event.event === "navigate") {
|
|
6372
|
+
const handlers = this.globalEventHandlers.get(`navigate:${event.tabId}`);
|
|
6373
|
+
if (handlers) {
|
|
6374
|
+
for (const h of handlers) {
|
|
6375
|
+
try {
|
|
6376
|
+
h(event.url);
|
|
6377
|
+
} catch (e) {
|
|
6378
|
+
logger_default.error(`navigate handler error: ${e}`);
|
|
6379
|
+
}
|
|
6380
|
+
}
|
|
6381
|
+
}
|
|
6382
|
+
const wildcard = this.globalEventHandlers.get("navigate:*");
|
|
6383
|
+
if (wildcard) {
|
|
6384
|
+
for (const h of wildcard) {
|
|
6385
|
+
try {
|
|
6386
|
+
h({ url: event.url, tabId: event.tabId });
|
|
6387
|
+
} catch {}
|
|
6388
|
+
}
|
|
6389
|
+
}
|
|
6390
|
+
return;
|
|
6371
6391
|
}
|
|
6372
6392
|
}
|
|
6393
|
+
onEvent(eventName, tabId, handler) {
|
|
6394
|
+
const key = `${eventName}:${tabId}`;
|
|
6395
|
+
if (!this.globalEventHandlers.has(key)) {
|
|
6396
|
+
this.globalEventHandlers.set(key, new Set);
|
|
6397
|
+
}
|
|
6398
|
+
this.globalEventHandlers.get(key).add(handler);
|
|
6399
|
+
return () => this.globalEventHandlers.get(key)?.delete(handler);
|
|
6400
|
+
}
|
|
6373
6401
|
disconnect() {
|
|
6374
6402
|
this.socket?.destroy();
|
|
6375
6403
|
this.socket = null;
|
|
@@ -6551,15 +6579,13 @@ class PiggyClient {
|
|
|
6551
6579
|
await this.send("session.import", { data, tabId });
|
|
6552
6580
|
}
|
|
6553
6581
|
async exposeFunction(name, handler, tabId = "default") {
|
|
6554
|
-
if (!this.eventHandlers.has(tabId))
|
|
6582
|
+
if (!this.eventHandlers.has(tabId))
|
|
6555
6583
|
this.eventHandlers.set(tabId, new Map);
|
|
6556
|
-
}
|
|
6557
6584
|
this.eventHandlers.get(tabId).set(name, async (data) => {
|
|
6558
6585
|
try {
|
|
6559
6586
|
const result = await handler(data);
|
|
6560
|
-
if (result && typeof result === "object" && (("success" in result) || ("error" in result)))
|
|
6587
|
+
if (result && typeof result === "object" && (("success" in result) || ("error" in result)))
|
|
6561
6588
|
return result;
|
|
6562
|
-
}
|
|
6563
6589
|
return { success: true, result };
|
|
6564
6590
|
} catch (err) {
|
|
6565
6591
|
return { success: false, error: err.message || String(err) };
|
|
@@ -21191,6 +21217,139 @@ function humanTypeSequence(text) {
|
|
|
21191
21217
|
return actions;
|
|
21192
21218
|
}
|
|
21193
21219
|
|
|
21220
|
+
// piggy/intercept/scripts.ts
|
|
21221
|
+
function buildRespondScript(pattern, status2, contentType, body) {
|
|
21222
|
+
const safePattern = pattern.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
21223
|
+
const safeBody = body.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
21224
|
+
const safeContentType = contentType.replace(/'/g, "\\'");
|
|
21225
|
+
return `
|
|
21226
|
+
(function() {
|
|
21227
|
+
'use strict';
|
|
21228
|
+
if (!window.__PIGGY_RESPOND_RULES__) window.__PIGGY_RESPOND_RULES__ = [];
|
|
21229
|
+
window.__PIGGY_RESPOND_RULES__.push({
|
|
21230
|
+
pattern: '${safePattern}',
|
|
21231
|
+
status: ${status2},
|
|
21232
|
+
contentType: '${safeContentType}',
|
|
21233
|
+
body: \`${safeBody}\`
|
|
21234
|
+
});
|
|
21235
|
+
|
|
21236
|
+
function _piggyMatchUrl(url, pattern) {
|
|
21237
|
+
try { return url.includes(pattern) || new RegExp(pattern).test(url); }
|
|
21238
|
+
catch { return url.includes(pattern); }
|
|
21239
|
+
}
|
|
21240
|
+
|
|
21241
|
+
// Only install wrappers once per page
|
|
21242
|
+
if (window.__PIGGY_RESPOND_INSTALLED__) return;
|
|
21243
|
+
window.__PIGGY_RESPOND_INSTALLED__ = true;
|
|
21244
|
+
|
|
21245
|
+
// ── fetch wrapper ──────────────────────────────────────────────────────────
|
|
21246
|
+
const _origFetch = window.fetch;
|
|
21247
|
+
window.fetch = function(input, init) {
|
|
21248
|
+
const url = typeof input === 'string' ? input : (input?.url ?? String(input));
|
|
21249
|
+
const rules = window.__PIGGY_RESPOND_RULES__ || [];
|
|
21250
|
+
for (const rule of rules) {
|
|
21251
|
+
if (_piggyMatchUrl(url, rule.pattern)) {
|
|
21252
|
+
return Promise.resolve(new Response(rule.body, {
|
|
21253
|
+
status: rule.status,
|
|
21254
|
+
headers: { 'Content-Type': rule.contentType }
|
|
21255
|
+
}));
|
|
21256
|
+
}
|
|
21257
|
+
}
|
|
21258
|
+
return _origFetch.apply(this, arguments);
|
|
21259
|
+
};
|
|
21260
|
+
|
|
21261
|
+
// ── XHR wrapper ────────────────────────────────────────────────────────────
|
|
21262
|
+
const _origOpen = XMLHttpRequest.prototype.open;
|
|
21263
|
+
const _origSend = XMLHttpRequest.prototype.send;
|
|
21264
|
+
|
|
21265
|
+
XMLHttpRequest.prototype.open = function(method, url) {
|
|
21266
|
+
this.__piggy_url__ = String(url);
|
|
21267
|
+
return _origOpen.apply(this, arguments);
|
|
21268
|
+
};
|
|
21269
|
+
|
|
21270
|
+
XMLHttpRequest.prototype.send = function() {
|
|
21271
|
+
const url = this.__piggy_url__ || '';
|
|
21272
|
+
const rules = window.__PIGGY_RESPOND_RULES__ || [];
|
|
21273
|
+
for (const rule of rules) {
|
|
21274
|
+
if (_piggyMatchUrl(url, rule.pattern)) {
|
|
21275
|
+
const self = this;
|
|
21276
|
+
Object.defineProperty(self, 'readyState', { get: () => 4, configurable: true });
|
|
21277
|
+
Object.defineProperty(self, 'status', { get: () => rule.status, configurable: true });
|
|
21278
|
+
Object.defineProperty(self, 'responseText', { get: () => rule.body, configurable: true });
|
|
21279
|
+
Object.defineProperty(self, 'response', { get: () => rule.body, configurable: true });
|
|
21280
|
+
setTimeout(() => {
|
|
21281
|
+
if (typeof self.onreadystatechange === 'function') self.onreadystatechange();
|
|
21282
|
+
self.dispatchEvent(new Event('readystatechange'));
|
|
21283
|
+
self.dispatchEvent(new Event('load'));
|
|
21284
|
+
self.dispatchEvent(new Event('loadend'));
|
|
21285
|
+
}, 0);
|
|
21286
|
+
return;
|
|
21287
|
+
}
|
|
21288
|
+
}
|
|
21289
|
+
return _origSend.apply(this, arguments);
|
|
21290
|
+
};
|
|
21291
|
+
})();
|
|
21292
|
+
`;
|
|
21293
|
+
}
|
|
21294
|
+
function buildModifyResponseScript(pattern, exposedFnName) {
|
|
21295
|
+
const safePattern = pattern.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
21296
|
+
const safeFnName = exposedFnName.replace(/'/g, "\\'");
|
|
21297
|
+
return `
|
|
21298
|
+
(function() {
|
|
21299
|
+
'use strict';
|
|
21300
|
+
if (!window.__PIGGY_MODIFY_RULES__) window.__PIGGY_MODIFY_RULES__ = [];
|
|
21301
|
+
window.__PIGGY_MODIFY_RULES__.push({ pattern: '${safePattern}', fn: '${safeFnName}' });
|
|
21302
|
+
|
|
21303
|
+
function _piggyMatchUrl(url, pattern) {
|
|
21304
|
+
try { return url.includes(pattern) || new RegExp(pattern).test(url); }
|
|
21305
|
+
catch { return url.includes(pattern); }
|
|
21306
|
+
}
|
|
21307
|
+
|
|
21308
|
+
// Only install wrappers once per page
|
|
21309
|
+
if (window.__PIGGY_MODIFY_INSTALLED__) return;
|
|
21310
|
+
window.__PIGGY_MODIFY_INSTALLED__ = true;
|
|
21311
|
+
|
|
21312
|
+
const _origFetch = window.fetch;
|
|
21313
|
+
window.fetch = async function(input, init) {
|
|
21314
|
+
const url = typeof input === 'string' ? input : (input?.url ?? String(input));
|
|
21315
|
+
const rules = window.__PIGGY_MODIFY_RULES__ || [];
|
|
21316
|
+
|
|
21317
|
+
let matchedFn = null;
|
|
21318
|
+
for (const rule of rules) {
|
|
21319
|
+
if (_piggyMatchUrl(url, rule.pattern)) { matchedFn = rule.fn; break; }
|
|
21320
|
+
}
|
|
21321
|
+
|
|
21322
|
+
// No match — pass through untouched
|
|
21323
|
+
const resp = await _origFetch.apply(this, arguments);
|
|
21324
|
+
if (!matchedFn) return resp;
|
|
21325
|
+
|
|
21326
|
+
try {
|
|
21327
|
+
const bodyText = await resp.clone().text();
|
|
21328
|
+
const headers = {};
|
|
21329
|
+
resp.headers.forEach((v, k) => { headers[k] = v; });
|
|
21330
|
+
|
|
21331
|
+
const handlerFn = window[matchedFn];
|
|
21332
|
+
if (typeof handlerFn !== 'function') return resp;
|
|
21333
|
+
|
|
21334
|
+
// Call Node.js handler via exposeFunction bridge
|
|
21335
|
+
const mod = await handlerFn({ body: bodyText, status: resp.status, headers });
|
|
21336
|
+
if (!mod || typeof mod !== 'object' || Object.keys(mod).length === 0) return resp;
|
|
21337
|
+
|
|
21338
|
+
return new Response(
|
|
21339
|
+
mod.body !== undefined ? mod.body : bodyText,
|
|
21340
|
+
{
|
|
21341
|
+
status: mod.status !== undefined ? mod.status : resp.status,
|
|
21342
|
+
headers: mod.headers !== undefined ? mod.headers : headers,
|
|
21343
|
+
}
|
|
21344
|
+
);
|
|
21345
|
+
} catch {
|
|
21346
|
+
return resp; // On any error, pass through original response
|
|
21347
|
+
}
|
|
21348
|
+
};
|
|
21349
|
+
})();
|
|
21350
|
+
`;
|
|
21351
|
+
}
|
|
21352
|
+
|
|
21194
21353
|
// piggy/register/index.ts
|
|
21195
21354
|
var globalClient = null;
|
|
21196
21355
|
var humanMode = false;
|
|
@@ -21217,6 +21376,20 @@ async function retry(label, fn, retries = 2, backoff = 150) {
|
|
|
21217
21376
|
}
|
|
21218
21377
|
function createSiteObject(name, registeredUrl, client, tabId) {
|
|
21219
21378
|
let _currentUrl = registeredUrl;
|
|
21379
|
+
const _eventListeners = new Map;
|
|
21380
|
+
const _unsubNavigate = client.onEvent("navigate", tabId, (url) => {
|
|
21381
|
+
_currentUrl = url;
|
|
21382
|
+
const handlers = _eventListeners.get("navigate");
|
|
21383
|
+
if (handlers) {
|
|
21384
|
+
for (const h of handlers) {
|
|
21385
|
+
try {
|
|
21386
|
+
h(url);
|
|
21387
|
+
} catch (e) {
|
|
21388
|
+
logger_default.error(`[${name}] navigate handler error: ${e}`);
|
|
21389
|
+
}
|
|
21390
|
+
}
|
|
21391
|
+
}
|
|
21392
|
+
});
|
|
21220
21393
|
const withErrScreen = async (fn, label) => {
|
|
21221
21394
|
try {
|
|
21222
21395
|
return await fn();
|
|
@@ -21231,6 +21404,7 @@ function createSiteObject(name, registeredUrl, client, tabId) {
|
|
|
21231
21404
|
throw err;
|
|
21232
21405
|
}
|
|
21233
21406
|
};
|
|
21407
|
+
let _modifyRuleCounter = 0;
|
|
21234
21408
|
const site = {
|
|
21235
21409
|
_name: name,
|
|
21236
21410
|
_tabId: tabId,
|
|
@@ -21269,6 +21443,19 @@ function createSiteObject(name, registeredUrl, client, tabId) {
|
|
|
21269
21443
|
logger_default.success(`[${name}] init script added`);
|
|
21270
21444
|
return site;
|
|
21271
21445
|
},
|
|
21446
|
+
on: (event, handler) => {
|
|
21447
|
+
if (!_eventListeners.has(event))
|
|
21448
|
+
_eventListeners.set(event, new Set);
|
|
21449
|
+
_eventListeners.get(event).add(handler);
|
|
21450
|
+
logger_default.debug(`[${name}] on('${event}') registered`);
|
|
21451
|
+
return () => {
|
|
21452
|
+
_eventListeners.get(event)?.delete(handler);
|
|
21453
|
+
logger_default.debug(`[${name}] on('${event}') unsubscribed`);
|
|
21454
|
+
};
|
|
21455
|
+
},
|
|
21456
|
+
off: (event, handler) => {
|
|
21457
|
+
_eventListeners.get(event)?.delete(handler);
|
|
21458
|
+
},
|
|
21272
21459
|
click: (selector, opts) => withErrScreen(() => retry(name, async () => {
|
|
21273
21460
|
if (humanMode)
|
|
21274
21461
|
await randomDelay(80, 220);
|
|
@@ -21402,6 +21589,86 @@ function createSiteObject(name, registeredUrl, client, tabId) {
|
|
|
21402
21589
|
await client.addInterceptRule("modifyHeaders", pattern, { headers }, tabId);
|
|
21403
21590
|
logger_default.info(`[${name}] intercept modifyHeaders: ${pattern}`);
|
|
21404
21591
|
},
|
|
21592
|
+
respond: async (pattern, handlerOrResponse) => {
|
|
21593
|
+
const isStatic = typeof handlerOrResponse === "object";
|
|
21594
|
+
const response = isStatic ? handlerOrResponse : { status: 200, contentType: "application/json", body: "" };
|
|
21595
|
+
if (!isStatic) {
|
|
21596
|
+
const fnName = `__piggy_respond_${name}_${++_modifyRuleCounter}__`;
|
|
21597
|
+
await client.exposeFunction(fnName, async (req) => {
|
|
21598
|
+
try {
|
|
21599
|
+
const result = handlerOrResponse(req);
|
|
21600
|
+
return {
|
|
21601
|
+
success: true,
|
|
21602
|
+
result: {
|
|
21603
|
+
status: result.status ?? 200,
|
|
21604
|
+
contentType: result.contentType ?? "application/json",
|
|
21605
|
+
body: result.body ?? ""
|
|
21606
|
+
}
|
|
21607
|
+
};
|
|
21608
|
+
} catch (e) {
|
|
21609
|
+
return { success: false, error: e.message };
|
|
21610
|
+
}
|
|
21611
|
+
}, tabId);
|
|
21612
|
+
const dynamicScript = `
|
|
21613
|
+
(function() {
|
|
21614
|
+
'use strict';
|
|
21615
|
+
if (!window.__PIGGY_DYNAMIC_RESPOND__) window.__PIGGY_DYNAMIC_RESPOND__ = [];
|
|
21616
|
+
window.__PIGGY_DYNAMIC_RESPOND__.push({ pattern: ${JSON.stringify(pattern)}, fn: ${JSON.stringify(fnName)} });
|
|
21617
|
+
|
|
21618
|
+
function matchUrl(url, pattern) {
|
|
21619
|
+
try { return url.includes(pattern) || new RegExp(pattern).test(url); }
|
|
21620
|
+
catch { return url.includes(pattern); }
|
|
21621
|
+
}
|
|
21622
|
+
|
|
21623
|
+
if (window.__PIGGY_DYN_INSTALLED__) return;
|
|
21624
|
+
window.__PIGGY_DYN_INSTALLED__ = true;
|
|
21625
|
+
|
|
21626
|
+
const _origFetch = window.fetch;
|
|
21627
|
+
window.fetch = async function(input, init) {
|
|
21628
|
+
const url = typeof input === 'string' ? input : (input?.url ?? String(input));
|
|
21629
|
+
const method = (init?.method ?? 'GET').toUpperCase();
|
|
21630
|
+
const rules = window.__PIGGY_DYNAMIC_RESPOND__ || [];
|
|
21631
|
+
for (const rule of rules) {
|
|
21632
|
+
if (matchUrl(url, rule.pattern) && typeof window[rule.fn] === 'function') {
|
|
21633
|
+
try {
|
|
21634
|
+
const r = await window[rule.fn]({ url, method });
|
|
21635
|
+
return new Response(r.body ?? '', {
|
|
21636
|
+
status: r.status ?? 200,
|
|
21637
|
+
headers: { 'Content-Type': r.contentType ?? 'application/json' }
|
|
21638
|
+
});
|
|
21639
|
+
} catch { break; }
|
|
21640
|
+
}
|
|
21641
|
+
}
|
|
21642
|
+
return _origFetch.apply(this, arguments);
|
|
21643
|
+
};
|
|
21644
|
+
})();`;
|
|
21645
|
+
await client.addInitScript(dynamicScript, tabId);
|
|
21646
|
+
await client.evaluate(dynamicScript, tabId);
|
|
21647
|
+
logger_default.success(`[${name}] intercept.respond (dynamic): ${pattern}`);
|
|
21648
|
+
return site;
|
|
21649
|
+
}
|
|
21650
|
+
const script = buildRespondScript(pattern, response.status ?? 200, response.contentType ?? "application/json", response.body);
|
|
21651
|
+
await client.addInitScript(script, tabId);
|
|
21652
|
+
await client.evaluate(script, tabId);
|
|
21653
|
+
logger_default.success(`[${name}] intercept.respond (static): ${pattern} → ${response.status ?? 200}`);
|
|
21654
|
+
return site;
|
|
21655
|
+
},
|
|
21656
|
+
modifyResponse: async (pattern, handler) => {
|
|
21657
|
+
const fnName = `__piggy_modres_${name}_${++_modifyRuleCounter}__`;
|
|
21658
|
+
await client.exposeFunction(fnName, async (response) => {
|
|
21659
|
+
try {
|
|
21660
|
+
const mod = await handler(response);
|
|
21661
|
+
return { success: true, result: mod ?? {} };
|
|
21662
|
+
} catch (e) {
|
|
21663
|
+
return { success: false, error: e.message };
|
|
21664
|
+
}
|
|
21665
|
+
}, tabId);
|
|
21666
|
+
const script = buildModifyResponseScript(pattern, fnName);
|
|
21667
|
+
await client.addInitScript(script, tabId);
|
|
21668
|
+
await client.evaluate(script, tabId);
|
|
21669
|
+
logger_default.success(`[${name}] intercept.modifyResponse: ${pattern}`);
|
|
21670
|
+
return site;
|
|
21671
|
+
},
|
|
21405
21672
|
clear: async () => {
|
|
21406
21673
|
await client.clearInterceptRules(tabId);
|
|
21407
21674
|
logger_default.info(`[${name}] intercept rules cleared`);
|
|
@@ -21480,6 +21747,7 @@ function createSiteObject(name, registeredUrl, client, tabId) {
|
|
|
21480
21747
|
return site;
|
|
21481
21748
|
},
|
|
21482
21749
|
close: async () => {
|
|
21750
|
+
_unsubNavigate();
|
|
21483
21751
|
keepAliveSites.delete(name);
|
|
21484
21752
|
if (tabId !== "default") {
|
|
21485
21753
|
await client.closeTab(tabId);
|