nothing-browser 0.0.9 → 0.0.11
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 +39 -19
- package/dist/piggy.js +289 -23
- package/dist/register/index.js +242 -0
- package/package.json +1 -1
- package/piggy/client/index.ts +117 -247
- package/piggy/intercept/scripts.ts +153 -0
- package/piggy/register/index.ts +182 -36
- package/piggy.ts +11 -5
package/dist/register/index.js
CHANGED
|
@@ -16703,6 +16703,139 @@ function humanTypeSequence(text) {
|
|
|
16703
16703
|
return actions;
|
|
16704
16704
|
}
|
|
16705
16705
|
|
|
16706
|
+
// piggy/intercept/scripts.ts
|
|
16707
|
+
function buildRespondScript(pattern, status2, contentType, body) {
|
|
16708
|
+
const safePattern = pattern.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
16709
|
+
const safeBody = body.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
16710
|
+
const safeContentType = contentType.replace(/'/g, "\\'");
|
|
16711
|
+
return `
|
|
16712
|
+
(function() {
|
|
16713
|
+
'use strict';
|
|
16714
|
+
if (!window.__PIGGY_RESPOND_RULES__) window.__PIGGY_RESPOND_RULES__ = [];
|
|
16715
|
+
window.__PIGGY_RESPOND_RULES__.push({
|
|
16716
|
+
pattern: '${safePattern}',
|
|
16717
|
+
status: ${status2},
|
|
16718
|
+
contentType: '${safeContentType}',
|
|
16719
|
+
body: \`${safeBody}\`
|
|
16720
|
+
});
|
|
16721
|
+
|
|
16722
|
+
function _piggyMatchUrl(url, pattern) {
|
|
16723
|
+
try { return url.includes(pattern) || new RegExp(pattern).test(url); }
|
|
16724
|
+
catch { return url.includes(pattern); }
|
|
16725
|
+
}
|
|
16726
|
+
|
|
16727
|
+
// Only install wrappers once per page
|
|
16728
|
+
if (window.__PIGGY_RESPOND_INSTALLED__) return;
|
|
16729
|
+
window.__PIGGY_RESPOND_INSTALLED__ = true;
|
|
16730
|
+
|
|
16731
|
+
// ── fetch wrapper ──────────────────────────────────────────────────────────
|
|
16732
|
+
const _origFetch = window.fetch;
|
|
16733
|
+
window.fetch = function(input, init) {
|
|
16734
|
+
const url = typeof input === 'string' ? input : (input?.url ?? String(input));
|
|
16735
|
+
const rules = window.__PIGGY_RESPOND_RULES__ || [];
|
|
16736
|
+
for (const rule of rules) {
|
|
16737
|
+
if (_piggyMatchUrl(url, rule.pattern)) {
|
|
16738
|
+
return Promise.resolve(new Response(rule.body, {
|
|
16739
|
+
status: rule.status,
|
|
16740
|
+
headers: { 'Content-Type': rule.contentType }
|
|
16741
|
+
}));
|
|
16742
|
+
}
|
|
16743
|
+
}
|
|
16744
|
+
return _origFetch.apply(this, arguments);
|
|
16745
|
+
};
|
|
16746
|
+
|
|
16747
|
+
// ── XHR wrapper ────────────────────────────────────────────────────────────
|
|
16748
|
+
const _origOpen = XMLHttpRequest.prototype.open;
|
|
16749
|
+
const _origSend = XMLHttpRequest.prototype.send;
|
|
16750
|
+
|
|
16751
|
+
XMLHttpRequest.prototype.open = function(method, url) {
|
|
16752
|
+
this.__piggy_url__ = String(url);
|
|
16753
|
+
return _origOpen.apply(this, arguments);
|
|
16754
|
+
};
|
|
16755
|
+
|
|
16756
|
+
XMLHttpRequest.prototype.send = function() {
|
|
16757
|
+
const url = this.__piggy_url__ || '';
|
|
16758
|
+
const rules = window.__PIGGY_RESPOND_RULES__ || [];
|
|
16759
|
+
for (const rule of rules) {
|
|
16760
|
+
if (_piggyMatchUrl(url, rule.pattern)) {
|
|
16761
|
+
const self = this;
|
|
16762
|
+
Object.defineProperty(self, 'readyState', { get: () => 4, configurable: true });
|
|
16763
|
+
Object.defineProperty(self, 'status', { get: () => rule.status, configurable: true });
|
|
16764
|
+
Object.defineProperty(self, 'responseText', { get: () => rule.body, configurable: true });
|
|
16765
|
+
Object.defineProperty(self, 'response', { get: () => rule.body, configurable: true });
|
|
16766
|
+
setTimeout(() => {
|
|
16767
|
+
if (typeof self.onreadystatechange === 'function') self.onreadystatechange();
|
|
16768
|
+
self.dispatchEvent(new Event('readystatechange'));
|
|
16769
|
+
self.dispatchEvent(new Event('load'));
|
|
16770
|
+
self.dispatchEvent(new Event('loadend'));
|
|
16771
|
+
}, 0);
|
|
16772
|
+
return;
|
|
16773
|
+
}
|
|
16774
|
+
}
|
|
16775
|
+
return _origSend.apply(this, arguments);
|
|
16776
|
+
};
|
|
16777
|
+
})();
|
|
16778
|
+
`;
|
|
16779
|
+
}
|
|
16780
|
+
function buildModifyResponseScript(pattern, exposedFnName) {
|
|
16781
|
+
const safePattern = pattern.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
16782
|
+
const safeFnName = exposedFnName.replace(/'/g, "\\'");
|
|
16783
|
+
return `
|
|
16784
|
+
(function() {
|
|
16785
|
+
'use strict';
|
|
16786
|
+
if (!window.__PIGGY_MODIFY_RULES__) window.__PIGGY_MODIFY_RULES__ = [];
|
|
16787
|
+
window.__PIGGY_MODIFY_RULES__.push({ pattern: '${safePattern}', fn: '${safeFnName}' });
|
|
16788
|
+
|
|
16789
|
+
function _piggyMatchUrl(url, pattern) {
|
|
16790
|
+
try { return url.includes(pattern) || new RegExp(pattern).test(url); }
|
|
16791
|
+
catch { return url.includes(pattern); }
|
|
16792
|
+
}
|
|
16793
|
+
|
|
16794
|
+
// Only install wrappers once per page
|
|
16795
|
+
if (window.__PIGGY_MODIFY_INSTALLED__) return;
|
|
16796
|
+
window.__PIGGY_MODIFY_INSTALLED__ = true;
|
|
16797
|
+
|
|
16798
|
+
const _origFetch = window.fetch;
|
|
16799
|
+
window.fetch = async function(input, init) {
|
|
16800
|
+
const url = typeof input === 'string' ? input : (input?.url ?? String(input));
|
|
16801
|
+
const rules = window.__PIGGY_MODIFY_RULES__ || [];
|
|
16802
|
+
|
|
16803
|
+
let matchedFn = null;
|
|
16804
|
+
for (const rule of rules) {
|
|
16805
|
+
if (_piggyMatchUrl(url, rule.pattern)) { matchedFn = rule.fn; break; }
|
|
16806
|
+
}
|
|
16807
|
+
|
|
16808
|
+
// No match — pass through untouched
|
|
16809
|
+
const resp = await _origFetch.apply(this, arguments);
|
|
16810
|
+
if (!matchedFn) return resp;
|
|
16811
|
+
|
|
16812
|
+
try {
|
|
16813
|
+
const bodyText = await resp.clone().text();
|
|
16814
|
+
const headers = {};
|
|
16815
|
+
resp.headers.forEach((v, k) => { headers[k] = v; });
|
|
16816
|
+
|
|
16817
|
+
const handlerFn = window[matchedFn];
|
|
16818
|
+
if (typeof handlerFn !== 'function') return resp;
|
|
16819
|
+
|
|
16820
|
+
// Call Node.js handler via exposeFunction bridge
|
|
16821
|
+
const mod = await handlerFn({ body: bodyText, status: resp.status, headers });
|
|
16822
|
+
if (!mod || typeof mod !== 'object' || Object.keys(mod).length === 0) return resp;
|
|
16823
|
+
|
|
16824
|
+
return new Response(
|
|
16825
|
+
mod.body !== undefined ? mod.body : bodyText,
|
|
16826
|
+
{
|
|
16827
|
+
status: mod.status !== undefined ? mod.status : resp.status,
|
|
16828
|
+
headers: mod.headers !== undefined ? mod.headers : headers,
|
|
16829
|
+
}
|
|
16830
|
+
);
|
|
16831
|
+
} catch {
|
|
16832
|
+
return resp; // On any error, pass through original response
|
|
16833
|
+
}
|
|
16834
|
+
};
|
|
16835
|
+
})();
|
|
16836
|
+
`;
|
|
16837
|
+
}
|
|
16838
|
+
|
|
16706
16839
|
// piggy/register/index.ts
|
|
16707
16840
|
var globalClient = null;
|
|
16708
16841
|
var humanMode = false;
|
|
@@ -16729,6 +16862,20 @@ async function retry(label, fn, retries = 2, backoff = 150) {
|
|
|
16729
16862
|
}
|
|
16730
16863
|
function createSiteObject(name, registeredUrl, client, tabId) {
|
|
16731
16864
|
let _currentUrl = registeredUrl;
|
|
16865
|
+
const _eventListeners = new Map;
|
|
16866
|
+
const _unsubNavigate = client.onEvent("navigate", tabId, (url) => {
|
|
16867
|
+
_currentUrl = url;
|
|
16868
|
+
const handlers = _eventListeners.get("navigate");
|
|
16869
|
+
if (handlers) {
|
|
16870
|
+
for (const h of handlers) {
|
|
16871
|
+
try {
|
|
16872
|
+
h(url);
|
|
16873
|
+
} catch (e) {
|
|
16874
|
+
logger_default.error(`[${name}] navigate handler error: ${e}`);
|
|
16875
|
+
}
|
|
16876
|
+
}
|
|
16877
|
+
}
|
|
16878
|
+
});
|
|
16732
16879
|
const withErrScreen = async (fn, label) => {
|
|
16733
16880
|
try {
|
|
16734
16881
|
return await fn();
|
|
@@ -16743,6 +16890,7 @@ function createSiteObject(name, registeredUrl, client, tabId) {
|
|
|
16743
16890
|
throw err;
|
|
16744
16891
|
}
|
|
16745
16892
|
};
|
|
16893
|
+
let _modifyRuleCounter = 0;
|
|
16746
16894
|
const site = {
|
|
16747
16895
|
_name: name,
|
|
16748
16896
|
_tabId: tabId,
|
|
@@ -16781,6 +16929,19 @@ function createSiteObject(name, registeredUrl, client, tabId) {
|
|
|
16781
16929
|
logger_default.success(`[${name}] init script added`);
|
|
16782
16930
|
return site;
|
|
16783
16931
|
},
|
|
16932
|
+
on: (event, handler) => {
|
|
16933
|
+
if (!_eventListeners.has(event))
|
|
16934
|
+
_eventListeners.set(event, new Set);
|
|
16935
|
+
_eventListeners.get(event).add(handler);
|
|
16936
|
+
logger_default.debug(`[${name}] on('${event}') registered`);
|
|
16937
|
+
return () => {
|
|
16938
|
+
_eventListeners.get(event)?.delete(handler);
|
|
16939
|
+
logger_default.debug(`[${name}] on('${event}') unsubscribed`);
|
|
16940
|
+
};
|
|
16941
|
+
},
|
|
16942
|
+
off: (event, handler) => {
|
|
16943
|
+
_eventListeners.get(event)?.delete(handler);
|
|
16944
|
+
},
|
|
16784
16945
|
click: (selector, opts) => withErrScreen(() => retry(name, async () => {
|
|
16785
16946
|
if (humanMode)
|
|
16786
16947
|
await randomDelay(80, 220);
|
|
@@ -16914,6 +17075,86 @@ function createSiteObject(name, registeredUrl, client, tabId) {
|
|
|
16914
17075
|
await client.addInterceptRule("modifyHeaders", pattern, { headers }, tabId);
|
|
16915
17076
|
logger_default.info(`[${name}] intercept modifyHeaders: ${pattern}`);
|
|
16916
17077
|
},
|
|
17078
|
+
respond: async (pattern, handlerOrResponse) => {
|
|
17079
|
+
const isStatic = typeof handlerOrResponse === "object";
|
|
17080
|
+
const response = isStatic ? handlerOrResponse : { status: 200, contentType: "application/json", body: "" };
|
|
17081
|
+
if (!isStatic) {
|
|
17082
|
+
const fnName = `__piggy_respond_${name}_${++_modifyRuleCounter}__`;
|
|
17083
|
+
await client.exposeFunction(fnName, async (req) => {
|
|
17084
|
+
try {
|
|
17085
|
+
const result = handlerOrResponse(req);
|
|
17086
|
+
return {
|
|
17087
|
+
success: true,
|
|
17088
|
+
result: {
|
|
17089
|
+
status: result.status ?? 200,
|
|
17090
|
+
contentType: result.contentType ?? "application/json",
|
|
17091
|
+
body: result.body ?? ""
|
|
17092
|
+
}
|
|
17093
|
+
};
|
|
17094
|
+
} catch (e) {
|
|
17095
|
+
return { success: false, error: e.message };
|
|
17096
|
+
}
|
|
17097
|
+
}, tabId);
|
|
17098
|
+
const dynamicScript = `
|
|
17099
|
+
(function() {
|
|
17100
|
+
'use strict';
|
|
17101
|
+
if (!window.__PIGGY_DYNAMIC_RESPOND__) window.__PIGGY_DYNAMIC_RESPOND__ = [];
|
|
17102
|
+
window.__PIGGY_DYNAMIC_RESPOND__.push({ pattern: ${JSON.stringify(pattern)}, fn: ${JSON.stringify(fnName)} });
|
|
17103
|
+
|
|
17104
|
+
function matchUrl(url, pattern) {
|
|
17105
|
+
try { return url.includes(pattern) || new RegExp(pattern).test(url); }
|
|
17106
|
+
catch { return url.includes(pattern); }
|
|
17107
|
+
}
|
|
17108
|
+
|
|
17109
|
+
if (window.__PIGGY_DYN_INSTALLED__) return;
|
|
17110
|
+
window.__PIGGY_DYN_INSTALLED__ = true;
|
|
17111
|
+
|
|
17112
|
+
const _origFetch = window.fetch;
|
|
17113
|
+
window.fetch = async function(input, init) {
|
|
17114
|
+
const url = typeof input === 'string' ? input : (input?.url ?? String(input));
|
|
17115
|
+
const method = (init?.method ?? 'GET').toUpperCase();
|
|
17116
|
+
const rules = window.__PIGGY_DYNAMIC_RESPOND__ || [];
|
|
17117
|
+
for (const rule of rules) {
|
|
17118
|
+
if (matchUrl(url, rule.pattern) && typeof window[rule.fn] === 'function') {
|
|
17119
|
+
try {
|
|
17120
|
+
const r = await window[rule.fn]({ url, method });
|
|
17121
|
+
return new Response(r.body ?? '', {
|
|
17122
|
+
status: r.status ?? 200,
|
|
17123
|
+
headers: { 'Content-Type': r.contentType ?? 'application/json' }
|
|
17124
|
+
});
|
|
17125
|
+
} catch { break; }
|
|
17126
|
+
}
|
|
17127
|
+
}
|
|
17128
|
+
return _origFetch.apply(this, arguments);
|
|
17129
|
+
};
|
|
17130
|
+
})();`;
|
|
17131
|
+
await client.addInitScript(dynamicScript, tabId);
|
|
17132
|
+
await client.evaluate(dynamicScript, tabId);
|
|
17133
|
+
logger_default.success(`[${name}] intercept.respond (dynamic): ${pattern}`);
|
|
17134
|
+
return site;
|
|
17135
|
+
}
|
|
17136
|
+
const script = buildRespondScript(pattern, response.status ?? 200, response.contentType ?? "application/json", response.body);
|
|
17137
|
+
await client.addInitScript(script, tabId);
|
|
17138
|
+
await client.evaluate(script, tabId);
|
|
17139
|
+
logger_default.success(`[${name}] intercept.respond (static): ${pattern} → ${response.status ?? 200}`);
|
|
17140
|
+
return site;
|
|
17141
|
+
},
|
|
17142
|
+
modifyResponse: async (pattern, handler) => {
|
|
17143
|
+
const fnName = `__piggy_modres_${name}_${++_modifyRuleCounter}__`;
|
|
17144
|
+
await client.exposeFunction(fnName, async (response) => {
|
|
17145
|
+
try {
|
|
17146
|
+
const mod = await handler(response);
|
|
17147
|
+
return { success: true, result: mod ?? {} };
|
|
17148
|
+
} catch (e) {
|
|
17149
|
+
return { success: false, error: e.message };
|
|
17150
|
+
}
|
|
17151
|
+
}, tabId);
|
|
17152
|
+
const script = buildModifyResponseScript(pattern, fnName);
|
|
17153
|
+
await client.addInitScript(script, tabId);
|
|
17154
|
+
await client.evaluate(script, tabId);
|
|
17155
|
+
logger_default.success(`[${name}] intercept.modifyResponse: ${pattern}`);
|
|
17156
|
+
return site;
|
|
17157
|
+
},
|
|
16917
17158
|
clear: async () => {
|
|
16918
17159
|
await client.clearInterceptRules(tabId);
|
|
16919
17160
|
logger_default.info(`[${name}] intercept rules cleared`);
|
|
@@ -16992,6 +17233,7 @@ function createSiteObject(name, registeredUrl, client, tabId) {
|
|
|
16992
17233
|
return site;
|
|
16993
17234
|
},
|
|
16994
17235
|
close: async () => {
|
|
17236
|
+
_unsubNavigate();
|
|
16995
17237
|
keepAliveSites.delete(name);
|
|
16996
17238
|
if (tabId !== "default") {
|
|
16997
17239
|
await client.closeTab(tabId);
|