nothing-browser 0.0.15 → 0.0.17
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/piggy/pool/index.d.ts +23 -0
- package/dist/piggy/pool/index.d.ts.map +1 -0
- package/dist/piggy/register/index.d.ts +3 -1
- package/dist/piggy/register/index.d.ts.map +1 -1
- package/dist/piggy/server/index.d.ts +22 -1
- package/dist/piggy/server/index.d.ts.map +1 -1
- package/dist/piggy/store/index.d.ts +22 -0
- package/dist/piggy/store/index.d.ts.map +1 -0
- package/dist/piggy.d.ts +6 -218
- package/dist/piggy.d.ts.map +1 -1
- package/dist/piggy.js +7390 -202
- package/dist/register/index.js +6291 -205
- package/dist/server/index.js +6252 -79
- package/package.json +3 -1
- package/piggy/pool/index.d.ts +12 -0
- package/piggy/pool/index.ts +75 -0
- package/piggy/register/index.ts +231 -214
- package/piggy/server/index.d.ts +51 -14
- package/piggy/server/index.ts +68 -15
- package/piggy/store/index.d.ts +26 -0
- package/piggy/store/index.ts +230 -0
- package/piggy.ts +88 -359
package/piggy.ts
CHANGED
|
@@ -2,324 +2,26 @@
|
|
|
2
2
|
import { detectBinary, type BinaryMode } from "./piggy/launch/detect";
|
|
3
3
|
import { spawnBrowser, killBrowser, spawnBrowserOnSocket } from "./piggy/launch/spawn";
|
|
4
4
|
import { PiggyClient } from "./piggy/client";
|
|
5
|
-
import { setClient, setHumanMode, createSiteObject } from "./piggy/register";
|
|
5
|
+
import { setClient, setHumanMode, createSiteObject, type SiteObject } from "./piggy/register";
|
|
6
6
|
import { routeRegistry, keepAliveSites, startServer, stopServer } from "./piggy/server";
|
|
7
|
+
import { TabPool } from "./piggy/pool";
|
|
7
8
|
import logger from "./piggy/logger";
|
|
8
|
-
import type { Logger } from "ernest-logger";
|
|
9
|
-
import type { Elysia } from "elysia";
|
|
10
|
-
import type { RouteHandler, BeforeMiddleware, RouteConfig } from "./piggy/server";
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export type TabMode = "tab" | "process";
|
|
15
|
-
|
|
16
|
-
/** Returned by createSiteObject — full API surface of a registered site. */
|
|
17
|
-
export interface SiteObject {
|
|
18
|
-
/** Internal name used when registering. */
|
|
19
|
-
_name: string;
|
|
20
|
-
/** Internal CDP / socket tab ID. */
|
|
21
|
-
_tabId: string;
|
|
22
|
-
|
|
23
|
-
// ── Navigation ─────────────────────────────────────────────────────────────
|
|
24
|
-
navigate(url?: string, opts?: { retries?: number }): Promise<void>;
|
|
25
|
-
reload(): Promise<void>;
|
|
26
|
-
goBack(): Promise<void>;
|
|
27
|
-
goForward(): Promise<void>;
|
|
28
|
-
waitForNavigation(): Promise<void>;
|
|
29
|
-
|
|
30
|
-
title(): Promise<string>;
|
|
31
|
-
/** Returns the last known URL (synchronous — does NOT hit the browser). */
|
|
32
|
-
url(): string;
|
|
33
|
-
content(): Promise<string>;
|
|
34
|
-
|
|
35
|
-
// ── Timing ────────────────────────────────────────────────────────────────
|
|
36
|
-
wait(ms: number): Promise<void>;
|
|
37
|
-
waitForSelector(selector: string, timeout?: number): Promise<void>;
|
|
38
|
-
waitForVisible(selector: string, timeout?: number): Promise<void>;
|
|
39
|
-
waitForResponse(pattern: string, timeout?: number): Promise<void>;
|
|
40
|
-
|
|
41
|
-
// ── Init script ───────────────────────────────────────────────────────────
|
|
42
|
-
addInitScript(js: string | (() => void)): Promise<SiteObject>;
|
|
43
|
-
|
|
44
|
-
// ── Event emitter ─────────────────────────────────────────────────────────
|
|
45
|
-
on(event: "navigate", handler: (url: string) => void): () => void;
|
|
46
|
-
on(event: string, handler: (data: unknown) => void): () => void;
|
|
47
|
-
off(event: string, handler: (data: unknown) => void): void;
|
|
48
|
-
|
|
49
|
-
// ── Interactions ──────────────────────────────────────────────────────────
|
|
50
|
-
click(selector: string, opts?: { retries?: number; timeout?: number }): Promise<boolean>;
|
|
51
|
-
doubleClick(selector: string): Promise<boolean>;
|
|
52
|
-
hover(selector: string): Promise<boolean>;
|
|
53
|
-
type(
|
|
54
|
-
selector: string,
|
|
55
|
-
text: string,
|
|
56
|
-
opts?: { delay?: number; retries?: number; fact?: boolean; wpm?: number }
|
|
57
|
-
): Promise<boolean>;
|
|
58
|
-
select(selector: string, value: string): Promise<boolean>;
|
|
59
|
-
evaluate<T = unknown>(js: string | ((...args: unknown[]) => T), ...args: unknown[]): Promise<T>;
|
|
60
|
-
|
|
61
|
-
keyboard: {
|
|
62
|
-
press(key: string): Promise<boolean>;
|
|
63
|
-
combo(combo: string): Promise<boolean>;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
mouse: {
|
|
67
|
-
move(x: number, y: number): Promise<boolean>;
|
|
68
|
-
drag(from: { x: number; y: number }, to: { x: number; y: number }): Promise<boolean>;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
scroll: {
|
|
72
|
-
to(selector: string): Promise<boolean>;
|
|
73
|
-
by(px: number): Promise<boolean>;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
// ── Fetch ─────────────────────────────────────────────────────────────────
|
|
77
|
-
fetchText(selector: string): Promise<string | null>;
|
|
78
|
-
fetchLinks(selector: string): Promise<string[]>;
|
|
79
|
-
fetchImages(selector: string): Promise<string[]>;
|
|
80
|
-
|
|
81
|
-
search: {
|
|
82
|
-
css(query: string): Promise<unknown>;
|
|
83
|
-
id(query: string): Promise<unknown>;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
// ── Screenshot / PDF ──────────────────────────────────────────────────────
|
|
87
|
-
screenshot(filePath?: string): Promise<string>;
|
|
88
|
-
pdf(filePath?: string): Promise<string>;
|
|
89
|
-
blockImages(): Promise<void>;
|
|
90
|
-
unblockImages(): Promise<void>;
|
|
91
|
-
|
|
92
|
-
// ── Cookies ───────────────────────────────────────────────────────────────
|
|
93
|
-
cookies: {
|
|
94
|
-
set(name: string, value: string, domain: string, path?: string): Promise<void>;
|
|
95
|
-
get(name: string): Promise<unknown>;
|
|
96
|
-
delete(name: string): Promise<void>;
|
|
97
|
-
list(): Promise<unknown[]>;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
// ── Interception ──────────────────────────────────────────────────────────
|
|
101
|
-
intercept: {
|
|
102
|
-
block(pattern: string): Promise<void>;
|
|
103
|
-
redirect(pattern: string, redirectUrl: string): Promise<void>;
|
|
104
|
-
headers(pattern: string, headers: Record<string, string>): Promise<void>;
|
|
105
|
-
respond(
|
|
106
|
-
pattern: string,
|
|
107
|
-
handlerOrResponse:
|
|
108
|
-
| { status?: number; contentType?: string; body: string }
|
|
109
|
-
| ((req: { url: string; method: string }) => {
|
|
110
|
-
status?: number;
|
|
111
|
-
contentType?: string;
|
|
112
|
-
body: string;
|
|
113
|
-
})
|
|
114
|
-
): Promise<SiteObject>;
|
|
115
|
-
modifyResponse(
|
|
116
|
-
pattern: string,
|
|
117
|
-
handler: (response: {
|
|
118
|
-
body: string;
|
|
119
|
-
status: number;
|
|
120
|
-
headers: Record<string, string>;
|
|
121
|
-
}) => Promise<{ body?: string; status?: number; headers?: Record<string, string> } | void> | void
|
|
122
|
-
): Promise<SiteObject>;
|
|
123
|
-
clear(): Promise<void>;
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
// ── Network capture ───────────────────────────────────────────────────────
|
|
127
|
-
capture: {
|
|
128
|
-
start(): Promise<void>;
|
|
129
|
-
stop(): Promise<void>;
|
|
130
|
-
requests(): Promise<unknown[]>;
|
|
131
|
-
ws(): Promise<unknown[]>;
|
|
132
|
-
cookies(): Promise<unknown[]>;
|
|
133
|
-
storage(): Promise<unknown>;
|
|
134
|
-
clear(): Promise<void>;
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// ── Session ───────────────────────────────────────────────────────────────
|
|
138
|
-
session: {
|
|
139
|
-
export(): Promise<unknown>;
|
|
140
|
-
import(data: unknown): Promise<void>;
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
// ── Expose / unexpose functions ───────────────────────────────────────────
|
|
144
|
-
exposeFunction(
|
|
145
|
-
fnName: string,
|
|
146
|
-
handler: (data: unknown) => Promise<unknown> | unknown
|
|
147
|
-
): Promise<SiteObject>;
|
|
148
|
-
unexposeFunction(fnName: string): Promise<SiteObject>;
|
|
149
|
-
clearExposedFunctions(): Promise<SiteObject>;
|
|
150
|
-
exposeAndInject(
|
|
151
|
-
fnName: string,
|
|
152
|
-
handler: (data: unknown) => Promise<unknown> | unknown,
|
|
153
|
-
injectionJs: string | ((fnName: string) => string)
|
|
154
|
-
): Promise<SiteObject>;
|
|
155
|
-
|
|
156
|
-
// ── Elysia route registration ─────────────────────────────────────────────
|
|
157
|
-
api(
|
|
158
|
-
path: string,
|
|
159
|
-
handler: RouteHandler,
|
|
160
|
-
opts?: {
|
|
161
|
-
ttl?: number;
|
|
162
|
-
before?: BeforeMiddleware[];
|
|
163
|
-
method?: "GET" | "POST" | "PUT" | "DELETE";
|
|
164
|
-
}
|
|
165
|
-
): SiteObject;
|
|
166
|
-
|
|
167
|
-
noclose(): SiteObject;
|
|
168
|
-
close(): Promise<void>;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ── Route summary returned by piggy.routes() ─────────────────────────────────
|
|
172
|
-
|
|
173
|
-
export interface RouteSummary {
|
|
174
|
-
site: string;
|
|
175
|
-
method: RouteConfig["method"];
|
|
176
|
-
path: string;
|
|
177
|
-
ttl: number;
|
|
178
|
-
middlewareCount: number;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// ── Multi-site proxy helpers ──────────────────────────────────────────────────
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Proxy that runs the same method on every site in parallel (Promise.all).
|
|
185
|
-
* The return type mirrors the SiteObject API but every method returns
|
|
186
|
-
* `Promise<ReturnType[]>` instead of a single value.
|
|
187
|
-
*/
|
|
188
|
-
export type AllProxy = {
|
|
189
|
-
[K in keyof SiteObject]: SiteObject[K] extends (...args: infer A) => Promise<infer R>
|
|
190
|
-
? (...args: A) => Promise<R[]>
|
|
191
|
-
: SiteObject[K] extends (...args: infer A) => infer R
|
|
192
|
-
? (...args: A) => Promise<R[]>
|
|
193
|
-
: never;
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Proxy that runs the same method on every site in parallel and returns a
|
|
198
|
-
* Record keyed by site `_name`.
|
|
199
|
-
*/
|
|
200
|
-
export type DiffProxy = {
|
|
201
|
-
[K in keyof SiteObject]: SiteObject[K] extends (...args: infer A) => Promise<infer R>
|
|
202
|
-
? (...args: A) => Promise<Record<string, R>>
|
|
203
|
-
: SiteObject[K] extends (...args: infer A) => infer R
|
|
204
|
-
? (...args: A) => Promise<Record<string, R>>
|
|
205
|
-
: never;
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
// ── Launch / register option bags ────────────────────────────────────────────
|
|
209
|
-
|
|
210
|
-
export interface LaunchOptions {
|
|
211
|
-
/** Whether to open a separate browser process per registered site or share a single one via tabs. Default: `"tab"`. */
|
|
212
|
-
mode?: TabMode;
|
|
213
|
-
/** Whether to run the browser binary in headed or headless mode. Default: `"headless"`. */
|
|
214
|
-
binary?: BinaryMode;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export interface RegisterOptions {
|
|
218
|
-
/** Override the binary mode for this site's dedicated process (only used when `mode === "process"`). */
|
|
219
|
-
binary?: BinaryMode;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export interface ServeOptions {
|
|
223
|
-
hostname?: string;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// ── The Piggy object type ─────────────────────────────────────────────────────
|
|
227
|
-
|
|
228
|
-
export interface Piggy {
|
|
229
|
-
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
230
|
-
/**
|
|
231
|
-
* Spawns the Nothing Browser binary and connects the internal socket client.
|
|
232
|
-
* Must be called before any other method.
|
|
233
|
-
*/
|
|
234
|
-
launch(opts?: LaunchOptions): Promise<Piggy>;
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Registers a named site at the given URL.
|
|
238
|
-
* After registration `piggy[name]` is available as a `SiteObject`.
|
|
239
|
-
*/
|
|
240
|
-
register(name: string, url: string, opts?: RegisterOptions): Promise<Piggy>;
|
|
241
|
-
|
|
242
|
-
// ── Global controls ────────────────────────────────────────────────────────
|
|
243
|
-
/** Enables or disables human-simulation mode (random delays, typos, smooth scrolling). */
|
|
244
|
-
actHuman(enable: boolean): Piggy;
|
|
245
|
-
|
|
246
|
-
/** Changes the tab/process mode *before* the next `register()` call. */
|
|
247
|
-
mode(m: TabMode): Piggy;
|
|
248
|
-
|
|
249
|
-
// ── Global function exposure ───────────────────────────────────────────────
|
|
250
|
-
/** Exposes a Node.js function to the browser's global scope on the default (or specified) tab. */
|
|
251
|
-
expose(
|
|
252
|
-
name: string,
|
|
253
|
-
handler: (data: unknown) => Promise<unknown> | unknown,
|
|
254
|
-
tabId?: string
|
|
255
|
-
): Promise<Piggy>;
|
|
256
|
-
|
|
257
|
-
/** Removes a previously exposed function from the browser. */
|
|
258
|
-
unexpose(name: string, tabId?: string): Promise<Piggy>;
|
|
259
|
-
|
|
260
|
-
// ── Elysia server ─────────────────────────────────────────────────────────
|
|
261
|
-
/** Starts an Elysia HTTP server that exposes all registered `.api()` routes. */
|
|
262
|
-
serve(port: number, opts?: ServeOptions): Promise<Elysia>;
|
|
263
|
-
|
|
264
|
-
/** Stops the running Elysia server. */
|
|
265
|
-
stopServer(): void;
|
|
266
|
-
|
|
267
|
-
// ── Introspection ─────────────────────────────────────────────────────────
|
|
268
|
-
/** Returns a summary of every mounted HTTP route. */
|
|
269
|
-
routes(): RouteSummary[];
|
|
270
|
-
|
|
271
|
-
// ── Multi-site helpers ────────────────────────────────────────────────────
|
|
272
|
-
/**
|
|
273
|
-
* Returns a proxy that calls the same method on **all** given sites in
|
|
274
|
-
* parallel (via `Promise.all`) and returns an array of results.
|
|
275
|
-
*/
|
|
276
|
-
all(sites: SiteObject[]): AllProxy;
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Returns a proxy that calls the same method on **all** given sites in
|
|
280
|
-
* parallel and returns a `Record<siteName, result>` object.
|
|
281
|
-
*/
|
|
282
|
-
diff(sites: SiteObject[]): DiffProxy;
|
|
283
|
-
|
|
284
|
-
// ── Shutdown ──────────────────────────────────────────────────────────────
|
|
285
|
-
/**
|
|
286
|
-
* Gracefully shuts down all tabs and the browser process.
|
|
287
|
-
* Pass `{ force: true }` to skip graceful close and kill immediately.
|
|
288
|
-
*/
|
|
289
|
-
close(opts?: { force?: boolean }): Promise<void>;
|
|
290
|
-
|
|
291
|
-
// ── Utilities ─────────────────────────────────────────────────────────────
|
|
292
|
-
/** Detects the Nothing Browser binary path for the given mode. */
|
|
293
|
-
detect(mode?: BinaryMode): string | null;
|
|
294
|
-
|
|
295
|
-
/** The ernest-logger instance used internally. */
|
|
296
|
-
logger: Logger;
|
|
297
|
-
|
|
298
|
-
// ── Dynamic site keys ─────────────────────────────────────────────────────
|
|
299
|
-
/**
|
|
300
|
-
* After `piggy.register("mysite", url)` you can access the site as
|
|
301
|
-
* `piggy.mysite`. The index signature covers those dynamic properties.
|
|
302
|
-
*/
|
|
303
|
-
[site: string]: SiteObject | unknown;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// ── Module-private state ──────────────────────────────────────────────────────
|
|
10
|
+
type TabMode = "tab" | "process";
|
|
307
11
|
|
|
308
12
|
let _client: PiggyClient | null = null;
|
|
309
13
|
let _tabMode: TabMode = "tab";
|
|
310
14
|
const _extraProcs: { socket: string; client: PiggyClient }[] = [];
|
|
311
|
-
const _sites: Record<string, SiteObject> =
|
|
15
|
+
const _sites: Record<string, SiteObject> = [];
|
|
312
16
|
|
|
313
|
-
|
|
17
|
+
const piggy: any = {
|
|
18
|
+
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
314
19
|
|
|
315
|
-
|
|
316
|
-
// ── Lifecycle ───────────────────────────────────────────────────────────────
|
|
317
|
-
|
|
318
|
-
launch: async (opts?: LaunchOptions): Promise<Piggy> => {
|
|
20
|
+
launch: async (opts?: { mode?: TabMode; binary?: BinaryMode }) => {
|
|
319
21
|
_tabMode = opts?.mode ?? "tab";
|
|
320
22
|
const binaryMode: BinaryMode = opts?.binary ?? "headless";
|
|
321
23
|
await spawnBrowser(binaryMode);
|
|
322
|
-
await new Promise
|
|
24
|
+
await new Promise(r => setTimeout(r, 500));
|
|
323
25
|
_client = new PiggyClient();
|
|
324
26
|
await _client.connect();
|
|
325
27
|
setClient(_client);
|
|
@@ -327,81 +29,104 @@ const piggy: Piggy = {
|
|
|
327
29
|
return piggy;
|
|
328
30
|
},
|
|
329
31
|
|
|
330
|
-
register: async (
|
|
32
|
+
register: async (
|
|
33
|
+
name: string,
|
|
34
|
+
url: string,
|
|
35
|
+
opts?: {
|
|
36
|
+
binary?: BinaryMode;
|
|
37
|
+
pool?: number; // number of tabs to pool — enables concurrent requests
|
|
38
|
+
}
|
|
39
|
+
) => {
|
|
331
40
|
if (!url?.trim()) throw new Error(`No URL for site "${name}"`);
|
|
332
41
|
const binaryMode: BinaryMode = opts?.binary ?? "headless";
|
|
42
|
+
const poolSize = opts?.pool ?? 0;
|
|
333
43
|
|
|
334
|
-
let tabId = "default";
|
|
335
44
|
if (_tabMode === "tab") {
|
|
336
45
|
if (!_client) throw new Error("No client. Call piggy.launch() first.");
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
46
|
+
|
|
47
|
+
if (poolSize > 1) {
|
|
48
|
+
// Pool mode — create N tabs, wrap in TabPool
|
|
49
|
+
const pool = new TabPool(_client, poolSize, url, name);
|
|
50
|
+
await pool.init();
|
|
51
|
+
|
|
52
|
+
// Use first tab as the "default" tabId for event subscriptions
|
|
53
|
+
const firstTabId = "default";
|
|
54
|
+
const siteObj = createSiteObject(name, url, _client, firstTabId, pool);
|
|
55
|
+
_sites[name] = siteObj;
|
|
56
|
+
piggy[name] = siteObj;
|
|
57
|
+
logger.success(`[${name}] registered with pool of ${poolSize} tabs`);
|
|
58
|
+
} else {
|
|
59
|
+
// Single tab mode
|
|
60
|
+
const tabId = await _client.newTab();
|
|
61
|
+
const siteObj = createSiteObject(name, url, _client, tabId);
|
|
62
|
+
_sites[name] = siteObj;
|
|
63
|
+
piggy[name] = siteObj;
|
|
64
|
+
logger.success(`[${name}] registered as tab ${tabId}`);
|
|
65
|
+
}
|
|
342
66
|
} else {
|
|
343
67
|
const socketName = `piggy_${name}`;
|
|
344
68
|
await spawnBrowserOnSocket(socketName, binaryMode);
|
|
345
|
-
await new Promise
|
|
69
|
+
await new Promise(r => setTimeout(r, 500));
|
|
346
70
|
const c = new PiggyClient(socketName);
|
|
347
71
|
await c.connect();
|
|
348
72
|
_extraProcs.push({ socket: socketName, client: c });
|
|
349
|
-
const siteObj
|
|
73
|
+
const siteObj = createSiteObject(name, url, c, "default");
|
|
350
74
|
_sites[name] = siteObj;
|
|
351
|
-
|
|
75
|
+
piggy[name] = siteObj;
|
|
352
76
|
logger.success(`[${name}] registered as process on "${socketName}"`);
|
|
353
77
|
}
|
|
354
78
|
|
|
355
79
|
return piggy;
|
|
356
80
|
},
|
|
357
81
|
|
|
358
|
-
// ── Global controls
|
|
82
|
+
// ── Global controls ───────────────────────────────────────────────────────
|
|
359
83
|
|
|
360
|
-
actHuman: (enable: boolean)
|
|
84
|
+
actHuman: (enable: boolean) => {
|
|
361
85
|
setHumanMode(enable);
|
|
362
86
|
logger.info(`[piggy] actHuman: ${enable}`);
|
|
363
87
|
return piggy;
|
|
364
88
|
},
|
|
365
89
|
|
|
366
|
-
mode: (m: TabMode)
|
|
367
|
-
_tabMode = m;
|
|
368
|
-
return piggy;
|
|
369
|
-
},
|
|
90
|
+
mode: (m: TabMode) => { _tabMode = m; return piggy; },
|
|
370
91
|
|
|
371
|
-
// ── Expose Function
|
|
92
|
+
// ── Expose Function ───────────────────────────────────────────────────────
|
|
372
93
|
|
|
373
|
-
expose: async (
|
|
374
|
-
name: string,
|
|
375
|
-
handler: (data: unknown) => Promise<unknown> | unknown,
|
|
376
|
-
tabId = "default"
|
|
377
|
-
): Promise<Piggy> => {
|
|
94
|
+
expose: async (name: string, handler: (data: any) => Promise<any> | any, tabId = "default") => {
|
|
378
95
|
if (!_client) throw new Error("No client. Call piggy.launch() first.");
|
|
379
96
|
await _client.exposeFunction(name, handler, tabId);
|
|
380
97
|
logger.success(`[piggy] exposed global function: ${name}`);
|
|
381
98
|
return piggy;
|
|
382
99
|
},
|
|
383
100
|
|
|
384
|
-
unexpose: async (name: string, tabId = "default")
|
|
101
|
+
unexpose: async (name: string, tabId = "default") => {
|
|
385
102
|
if (!_client) throw new Error("No client. Call piggy.launch() first.");
|
|
386
103
|
await _client.unexposeFunction(name, tabId);
|
|
387
104
|
logger.info(`[piggy] unexposed function: ${name}`);
|
|
388
105
|
return piggy;
|
|
389
106
|
},
|
|
390
107
|
|
|
391
|
-
// ── Elysia server
|
|
108
|
+
// ── Elysia server ─────────────────────────────────────────────────────────
|
|
392
109
|
|
|
393
|
-
serve: (
|
|
394
|
-
|
|
110
|
+
serve: (
|
|
111
|
+
port: number,
|
|
112
|
+
opts?: {
|
|
113
|
+
hostname?: string;
|
|
114
|
+
title?: string;
|
|
115
|
+
version?: string;
|
|
116
|
+
description?: string;
|
|
117
|
+
path?: string;
|
|
118
|
+
}
|
|
119
|
+
) => startServer(port, opts?.hostname, opts),
|
|
395
120
|
|
|
396
121
|
stopServer,
|
|
397
122
|
|
|
398
|
-
// ── Route listing
|
|
123
|
+
// ── Route listing ─────────────────────────────────────────────────────────
|
|
399
124
|
|
|
400
|
-
routes: ()
|
|
401
|
-
Array.from(routeRegistry.entries()).map(([key, cfg])
|
|
125
|
+
routes: () =>
|
|
126
|
+
Array.from(routeRegistry.entries()).map(([key, cfg]) => {
|
|
402
127
|
const [site] = key.split(":");
|
|
403
128
|
return {
|
|
404
|
-
site
|
|
129
|
+
site,
|
|
405
130
|
method: cfg.method,
|
|
406
131
|
path: `/${site}${cfg.path}`,
|
|
407
132
|
ttl: cfg.ttl,
|
|
@@ -409,35 +134,26 @@ const piggy: Piggy = {
|
|
|
409
134
|
};
|
|
410
135
|
}),
|
|
411
136
|
|
|
412
|
-
// ── Multi-site
|
|
137
|
+
// ── Multi-site ────────────────────────────────────────────────────────────
|
|
413
138
|
|
|
414
|
-
all: (sites: SiteObject[])
|
|
415
|
-
new Proxy({} as
|
|
416
|
-
get: (
|
|
417
|
-
(...args:
|
|
418
|
-
Promise.all(
|
|
419
|
-
sites.map((s) =>
|
|
420
|
-
(s as unknown as Record<string, (...a: unknown[]) => unknown>)[method]?.(...args)
|
|
421
|
-
)
|
|
422
|
-
),
|
|
139
|
+
all: (sites: SiteObject[]) =>
|
|
140
|
+
new Proxy({} as any, {
|
|
141
|
+
get: (_, method: string) =>
|
|
142
|
+
(...args: any[]) => Promise.all(sites.map((s: any) => s[method]?.(...args))),
|
|
423
143
|
}),
|
|
424
144
|
|
|
425
|
-
diff: (sites: SiteObject[])
|
|
426
|
-
new Proxy({} as
|
|
427
|
-
get: (
|
|
428
|
-
async (...args:
|
|
429
|
-
const results = await Promise.all(
|
|
430
|
-
|
|
431
|
-
(s as unknown as Record<string, (...a: unknown[]) => unknown>)[method]?.(...args)
|
|
432
|
-
)
|
|
433
|
-
);
|
|
434
|
-
return Object.fromEntries(sites.map((s, i) => [s._name ?? i, results[i]]));
|
|
145
|
+
diff: (sites: SiteObject[]) =>
|
|
146
|
+
new Proxy({} as any, {
|
|
147
|
+
get: (_, method: string) =>
|
|
148
|
+
async (...args: any[]) => {
|
|
149
|
+
const results = await Promise.all(sites.map((s: any) => s[method]?.(...args)));
|
|
150
|
+
return Object.fromEntries(sites.map((s: any, i) => [s._name ?? i, results[i]]));
|
|
435
151
|
},
|
|
436
152
|
}),
|
|
437
153
|
|
|
438
|
-
// ── Shutdown
|
|
154
|
+
// ── Shutdown ──────────────────────────────────────────────────────────────
|
|
439
155
|
|
|
440
|
-
close: async (opts?: { force?: boolean })
|
|
156
|
+
close: async (opts?: { force?: boolean }) => {
|
|
441
157
|
stopServer();
|
|
442
158
|
if (opts?.force) {
|
|
443
159
|
for (const { client: c } of _extraProcs) c.disconnect();
|
|
@@ -447,7 +163,7 @@ const piggy: Piggy = {
|
|
|
447
163
|
keepAliveSites.clear();
|
|
448
164
|
} else {
|
|
449
165
|
for (const [name, site] of Object.entries(_sites)) {
|
|
450
|
-
if (!keepAliveSites.has(name)) await site.close?.();
|
|
166
|
+
if (!keepAliveSites.has(name)) await (site as any).close?.();
|
|
451
167
|
}
|
|
452
168
|
if (keepAliveSites.size === 0) {
|
|
453
169
|
for (const { client: c } of _extraProcs) c.disconnect();
|
|
@@ -465,5 +181,18 @@ const piggy: Piggy = {
|
|
|
465
181
|
logger,
|
|
466
182
|
};
|
|
467
183
|
|
|
184
|
+
// ── usePiggy ──────────────────────────────────────────────────────────────────
|
|
185
|
+
// Typed accessor — call AFTER register() so sites exist on piggy.
|
|
186
|
+
// const { amazon, ebay } = usePiggy<"amazon" | "ebay">()
|
|
187
|
+
|
|
188
|
+
type TypedPiggy<Sites extends string> = typeof piggy & {
|
|
189
|
+
[K in Sites]: SiteObject;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export function usePiggy<Sites extends string>(): TypedPiggy<Sites> {
|
|
193
|
+
return piggy as TypedPiggy<Sites>;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export type { SiteObject };
|
|
468
197
|
export default piggy;
|
|
469
198
|
export { piggy };
|