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.
@@ -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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothing-browser",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "Browser automation library powered by Nothing Browser",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",