nothing-browser 0.1.2 → 0.1.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"piggy.d.ts","sourceRoot":"","sources":["../piggy.ts"],"names":[],"mappings":"AAKA,OAAO,EAA6C,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AA2C9F,QAAA,MAAM,KAAK,EAAE,GAmNZ,CAAC;AAIF,KAAK,UAAU,CAAC,KAAK,SAAS,MAAM,IAAI,OAAO,KAAK,GAAG;KACpD,CAAC,IAAI,KAAK,GAAG,UAAU;CACzB,CAAC;AAEF,wBAAgB,QAAQ,CAAC,KAAK,SAAS,MAAM,KAAK,UAAU,CAAC,KAAK,CAAC,CAElE;AAED,YAAY,EAAE,UAAU,EAAE,CAAC;AAC3B,eAAe,KAAK,CAAC;AACrB,OAAO,EAAE,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"piggy.d.ts","sourceRoot":"","sources":["../piggy.ts"],"names":[],"mappings":"AAIA,OAAO,EAA6C,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAkD9F,QAAA,MAAM,KAAK,EAAE,GA+QZ,CAAC;AAIF,KAAK,UAAU,CAAC,KAAK,SAAS,MAAM,IAAI,OAAO,KAAK,GAAG;KACpD,CAAC,IAAI,KAAK,GAAG,UAAU;CACzB,CAAC;AAEF,wBAAgB,QAAQ,CAAC,KAAK,SAAS,MAAM,KAAK,UAAU,CAAC,KAAK,CAAC,CAElE;AAED,YAAY,EAAE,UAAU,EAAE,CAAC;AAC3B,eAAe,KAAK,CAAC;AACrB,OAAO,EAAE,KAAK,EAAE,CAAC"}
package/dist/piggy.js CHANGED
@@ -27311,7 +27311,7 @@ function finalize(ctx, schema) {
27311
27311
  result.$schema = "http://json-schema.org/draft-07/schema#";
27312
27312
  } else if (ctx.target === "draft-04") {
27313
27313
  result.$schema = "http://json-schema.org/draft-04/schema#";
27314
- } else if (ctx.target === "openapi-3.0") {} else {}
27314
+ } else if (ctx.target === "openapi-3.0") {}
27315
27315
  if (ctx.external?.uri) {
27316
27316
  const id = ctx.external.registry.get(schema)?.id;
27317
27317
  if (!id)
@@ -29966,6 +29966,7 @@ var _router = null;
29966
29966
  var _tabMode = "tab";
29967
29967
  var _extraProcs = [];
29968
29968
  var _sites = {};
29969
+ var _singleSiteName = null;
29969
29970
  function guardClient() {
29970
29971
  if (!_client)
29971
29972
  throw new Error("No client. Call piggy.launch() or piggy.connect() first.");
@@ -30002,9 +30003,15 @@ var piggy = {
30002
30003
  throw new Error(`No URL for site "${name}"`);
30003
30004
  const binaryMode = opts?.binary ?? "headless";
30004
30005
  const poolSize = opts?.pool ?? 0;
30006
+ const isSingle = opts?.single === true;
30007
+ if (isSingle && _singleSiteName && _singleSiteName !== name) {
30008
+ throw new Error(`piggy: site "${_singleSiteName}" is already registered as single. ` + `Only one site may use { single: true } at a time.`);
30009
+ }
30005
30010
  if (_tabMode === "tab") {
30006
30011
  const client = guardClient();
30007
30012
  if (poolSize > 1) {
30013
+ if (isSingle)
30014
+ throw new Error("piggy: { single: true } is incompatible with pool > 1");
30008
30015
  const pool = new TabPool(client, poolSize, url2, name);
30009
30016
  await pool.init();
30010
30017
  const siteObj = createSiteObject(name, url2, client, "default", pool);
@@ -30012,13 +30019,20 @@ var piggy = {
30012
30019
  piggy[name] = siteObj;
30013
30020
  logger_default.success(`[${name}] registered with pool of ${poolSize} tabs`);
30014
30021
  } else {
30015
- const tabId = await client.newTab();
30022
+ const tabId = isSingle ? "default" : await client.newTab();
30016
30023
  const siteObj = createSiteObject(name, url2, client, tabId);
30017
30024
  _sites[name] = siteObj;
30018
30025
  piggy[name] = siteObj;
30019
- logger_default.success(`[${name}] registered as tab ${tabId}`);
30026
+ if (isSingle) {
30027
+ _singleSiteName = name;
30028
+ logger_default.success(`[${name}] registered as single-tab site (default tab)`);
30029
+ } else {
30030
+ logger_default.success(`[${name}] registered as tab ${tabId}`);
30031
+ }
30020
30032
  }
30021
30033
  } else {
30034
+ if (isSingle)
30035
+ throw new Error("piggy: { single: true } is only supported in tab mode");
30022
30036
  const socketName = `piggy_${name}`;
30023
30037
  await spawnBrowserOnSocket(socketName, binaryMode);
30024
30038
  await new Promise((r) => setTimeout(r, 500));
@@ -30032,6 +30046,28 @@ var piggy = {
30032
30046
  }
30033
30047
  return piggy;
30034
30048
  },
30049
+ extend: async (...installers) => {
30050
+ if (!_singleSiteName) {
30051
+ throw new Error(`piggy.extend() requires a site registered with { single: true }.
30052
+ ` + 'Example: await piggy.register("mysite", url, { single: true })');
30053
+ }
30054
+ if (installers.length === 0) {
30055
+ logger_default.warn("[piggy] extend() called with no plugins — nothing to do");
30056
+ return piggy;
30057
+ }
30058
+ const site = _sites[_singleSiteName];
30059
+ if (!site) {
30060
+ throw new Error(`piggy.extend(): site "${_singleSiteName}" not found — register it first`);
30061
+ }
30062
+ for (const installer of installers) {
30063
+ if (typeof installer !== "function") {
30064
+ throw new Error("piggy.extend(): each argument must be a plugin installer function");
30065
+ }
30066
+ await installer(site);
30067
+ }
30068
+ logger_default.success(`[piggy] ${installers.length} plugin(s) installed on "${_singleSiteName}"`);
30069
+ return piggy;
30070
+ },
30035
30071
  get tabs() {
30036
30072
  return _router?.tabs ?? createTabsAPI(guardClient());
30037
30073
  },
@@ -30147,6 +30183,7 @@ var piggy = {
30147
30183
  }),
30148
30184
  close: async (opts) => {
30149
30185
  stopServer();
30186
+ _singleSiteName = null;
30150
30187
  if (opts?.force) {
30151
30188
  for (const { client: c } of _extraProcs)
30152
30189
  c.disconnect();
@@ -21800,7 +21800,7 @@ function finalize(ctx, schema) {
21800
21800
  result.$schema = "http://json-schema.org/draft-07/schema#";
21801
21801
  } else if (ctx.target === "draft-04") {
21802
21802
  result.$schema = "http://json-schema.org/draft-04/schema#";
21803
- } else if (ctx.target === "openapi-3.0") {} else {}
21803
+ } else if (ctx.target === "openapi-3.0") {}
21804
21804
  if (ctx.external?.uri) {
21805
21805
  const id = ctx.external.registry.get(schema)?.id;
21806
21806
  if (!id)
@@ -25742,7 +25742,7 @@ function finalize(ctx, schema) {
25742
25742
  result.$schema = "http://json-schema.org/draft-07/schema#";
25743
25743
  } else if (ctx.target === "draft-04") {
25744
25744
  result.$schema = "http://json-schema.org/draft-04/schema#";
25745
- } else if (ctx.target === "openapi-3.0") {} else {}
25745
+ } else if (ctx.target === "openapi-3.0") {}
25746
25746
  if (ctx.external?.uri) {
25747
25747
  const id = ctx.external.registry.get(schema)?.id;
25748
25748
  if (!id)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothing-browser",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Browser automation library powered by Nothing Browser",
5
5
  "homepage": "https://github.com/ernest-tech-house-co-operation/nothing-browser#readme",
6
6
  "repository": {
package/piggy.ts CHANGED
@@ -1,5 +1,4 @@
1
- // piggy.ts
2
- //the main file
1
+ // piggy.ts — patched: adds single:true + piggy.extend()
3
2
  import { detectBinary, type BinaryMode } from "./piggy/launch/detect";
4
3
  import { spawnBrowser, killBrowser, spawnBrowserOnSocket } from "./piggy/launch/spawn";
5
4
  import { PiggyClient } from "./piggy/client";
@@ -27,12 +26,19 @@ import logger from "./piggy/logger";
27
26
 
28
27
  type TabMode = "tab" | "process";
29
28
 
29
+ // A plugin installer is an async function that receives a site and enriches it.
30
+ type PluginInstaller = (site: SiteObject) => Promise<any>;
31
+
30
32
  let _client: PiggyClient | null = null;
31
33
  let _router: PiggyRouter | null = null;
32
34
  let _tabMode: TabMode = "tab";
33
35
  const _extraProcs: { socket: string; client: PiggyClient }[] = [];
34
36
  const _sites: Record<string, SiteObject> = {};
35
37
 
38
+ // ── Single-site tracking for extend() ────────────────────────────────────────
39
+ // Only one site may be registered with { single: true } at a time.
40
+ let _singleSiteName: string | null = null;
41
+
36
42
  // ── Internal guard ────────────────────────────────────────────────────────────
37
43
 
38
44
  function guardClient(): PiggyClient {
@@ -74,37 +80,57 @@ const piggy: any = {
74
80
  },
75
81
 
76
82
  // ── HTTP client (port 2005 direct) ────────────────────────────────────────
77
- // Use when you want to talk to the browser over HTTP without a socket client.
78
83
  http: (opts: HttpClientOptions) => createHttpClient(opts),
79
84
 
80
85
  // ── Register ──────────────────────────────────────────────────────────────
81
86
  register: async (
82
87
  name: string,
83
88
  url: string,
84
- opts?: { binary?: BinaryMode; pool?: number }
89
+ opts?: { binary?: BinaryMode; pool?: number; single?: boolean }
85
90
  ) => {
86
91
  if (!url?.trim()) throw new Error(`No URL for site "${name}"`);
92
+
87
93
  const binaryMode: BinaryMode = opts?.binary ?? "headless";
88
- const poolSize = opts?.pool ?? 0;
94
+ const poolSize = opts?.pool ?? 0;
95
+ const isSingle = opts?.single === true;
96
+
97
+ // ── single: true enforcement ───────────────────────────────────────────
98
+ // A single-site registration uses the default tab and blocks tab.new so
99
+ // the binary stays strictly single-tab. Only one site may be single.
100
+ if (isSingle && _singleSiteName && _singleSiteName !== name) {
101
+ throw new Error(
102
+ `piggy: site "${_singleSiteName}" is already registered as single. ` +
103
+ `Only one site may use { single: true } at a time.`
104
+ );
105
+ }
89
106
 
90
107
  if (_tabMode === "tab") {
91
108
  const client = guardClient();
92
109
 
93
110
  if (poolSize > 1) {
111
+ if (isSingle) throw new Error('piggy: { single: true } is incompatible with pool > 1');
94
112
  const pool = new TabPool(client, poolSize, url, name);
95
113
  await pool.init();
96
114
  const siteObj = createSiteObject(name, url, client, "default", pool);
97
115
  _sites[name] = siteObj;
98
- piggy[name] = siteObj;
116
+ piggy[name] = siteObj;
99
117
  logger.success(`[${name}] registered with pool of ${poolSize} tabs`);
100
118
  } else {
101
- const tabId = await client.newTab();
119
+ // single: true → reuse the default tab, never call newTab()
120
+ const tabId = isSingle ? "default" : await client.newTab();
102
121
  const siteObj = createSiteObject(name, url, client, tabId);
103
122
  _sites[name] = siteObj;
104
- piggy[name] = siteObj;
105
- logger.success(`[${name}] registered as tab ${tabId}`);
123
+ piggy[name] = siteObj;
124
+
125
+ if (isSingle) {
126
+ _singleSiteName = name;
127
+ logger.success(`[${name}] registered as single-tab site (default tab)`);
128
+ } else {
129
+ logger.success(`[${name}] registered as tab ${tabId}`);
130
+ }
106
131
  }
107
132
  } else {
133
+ if (isSingle) throw new Error('piggy: { single: true } is only supported in tab mode');
108
134
  const socketName = `piggy_${name}`;
109
135
  await spawnBrowserOnSocket(socketName, binaryMode);
110
136
  await new Promise(r => setTimeout(r, 500));
@@ -113,17 +139,56 @@ const piggy: any = {
113
139
  _extraProcs.push({ socket: socketName, client: c });
114
140
  const siteObj = createSiteObject(name, url, c, "default");
115
141
  _sites[name] = siteObj;
116
- piggy[name] = siteObj;
142
+ piggy[name] = siteObj;
117
143
  logger.success(`[${name}] registered as process on "${socketName}"`);
118
144
  }
119
145
 
120
146
  return piggy;
121
147
  },
122
148
 
149
+ // ── extend() — installs plugins onto the single-flagged site ─────────────
150
+ //
151
+ // Each installer is an async function returned by a plugin factory, e.g.:
152
+ // innerstorage({ path: './wa-storage.json' }) → installer fn
153
+ //
154
+ // Usage:
155
+ // await piggy.extend(
156
+ // innerstorage({ path: './wa-storage.json' }),
157
+ // cookiesinject({ cookieFile: './wa-cookies.json' }),
158
+ // mediacapture({ downloadDir: './wa-media/' })
159
+ // );
160
+ extend: async (...installers: PluginInstaller[]) => {
161
+ if (!_singleSiteName) {
162
+ throw new Error(
163
+ 'piggy.extend() requires a site registered with { single: true }.\n' +
164
+ 'Example: await piggy.register("mysite", url, { single: true })'
165
+ );
166
+ }
167
+ if (installers.length === 0) {
168
+ logger.warn('[piggy] extend() called with no plugins — nothing to do');
169
+ return piggy;
170
+ }
171
+
172
+ const site = _sites[_singleSiteName];
173
+ if (!site) {
174
+ throw new Error(`piggy.extend(): site "${_singleSiteName}" not found — register it first`);
175
+ }
176
+
177
+ for (const installer of installers) {
178
+ if (typeof installer !== 'function') {
179
+ throw new Error('piggy.extend(): each argument must be a plugin installer function');
180
+ }
181
+ await installer(site);
182
+ }
183
+
184
+ logger.success(`[piggy] ${installers.length} plugin(s) installed on "${_singleSiteName}"`);
185
+ return piggy;
186
+ },
187
+
123
188
  // ── Sub-APIs (1:1 with C++ files, available after launch/connect) ─────────
124
189
 
125
- get tabs() { return _router?.tabs ?? createTabsAPI(guardClient()); },
126
- get tab() { return _router?.tabs ?? createTabsAPI(guardClient()); },
190
+ get tabs() { return _router?.tabs ?? createTabsAPI(guardClient()); },
191
+ get tab() { return _router?.tabs ?? createTabsAPI(guardClient()); },
127
192
  get navigation() { return _router?.navigation ?? createNavigationAPI(guardClient()); },
128
193
  get interactions() { return _router?.interactions ?? createInteractionsAPI(guardClient()); },
129
194
  get media() { return _router?.media ?? createMediaAPI(guardClient()); },
@@ -146,7 +211,7 @@ const piggy: any = {
146
211
  return {
147
212
  load: (path: string) => api.load(path),
148
213
  fetch: (url: string) => api.fetch(url),
149
- ovpn: (path: string) => api.ovpn(path),
214
+ ovpn: (path: string) => api.ovpn(path),
150
215
  set: (opts: Parameters<typeof api.set>[0]) => api.set(opts),
151
216
  test: () => api.test(),
152
217
  testStop: () => api.testStop(),
@@ -160,7 +225,7 @@ const piggy: any = {
160
225
  rotation: (mode: "none" | "timed" | "perrequest", interval?: number) => api.rotation(mode, interval),
161
226
  config: (opts: { skipDead?: boolean; autoCheck?: boolean }) => api.config(opts),
162
227
  save: (path: string, filter?: "alive" | "dead" | "all") => api.save(path, filter),
163
- on: (event: string, handler: (data: any) => void) => guardClient().onProxyEvent(event, handler),
228
+ on: (event: string, handler: (data: any) => void) => guardClient().onProxyEvent(event, handler),
164
229
  };
165
230
  },
166
231
 
@@ -232,6 +297,7 @@ const piggy: any = {
232
297
  // ── Shutdown ──────────────────────────────────────────────────────────────
233
298
  close: async (opts?: { force?: boolean }) => {
234
299
  stopServer();
300
+ _singleSiteName = null;
235
301
  if (opts?.force) {
236
302
  for (const { client: c } of _extraProcs) c.disconnect();
237
303
  _client?.disconnect();