nothing-browser 0.0.16 → 0.0.18
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 +224 -40
- package/dist/piggy/client/index.d.ts +77 -2
- package/dist/piggy/client/index.d.ts.map +1 -1
- 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 -174
- package/dist/piggy.d.ts.map +1 -1
- package/dist/piggy.js +7736 -277
- package/dist/register/index.js +6291 -205
- package/dist/server/index.js +6252 -79
- package/package.json +3 -1
- package/piggy/client/index.ts +325 -54
- 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 +118 -320
package/piggy.ts
CHANGED
|
@@ -2,287 +2,25 @@
|
|
|
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
|
-
/** Full API surface of a registered site. */
|
|
17
|
-
export interface SiteObject {
|
|
18
|
-
readonly _name: string;
|
|
19
|
-
readonly _tabId: string;
|
|
20
|
-
|
|
21
|
-
// ── Navigation ─────────────────────────────────────────────────────────────
|
|
22
|
-
navigate(url?: string, opts?: { retries?: number }): Promise<void>;
|
|
23
|
-
reload(): Promise<void>;
|
|
24
|
-
goBack(): Promise<void>;
|
|
25
|
-
goForward(): Promise<void>;
|
|
26
|
-
waitForNavigation(): Promise<void>;
|
|
27
|
-
title(): Promise<string>;
|
|
28
|
-
/** Returns the last known URL (synchronous — does NOT hit the browser). */
|
|
29
|
-
url(): string;
|
|
30
|
-
content(): Promise<string>;
|
|
31
|
-
|
|
32
|
-
// ── Timing ────────────────────────────────────────────────────────────────
|
|
33
|
-
wait(ms: number): Promise<void>;
|
|
34
|
-
waitForSelector(selector: string, timeout?: number): Promise<void>;
|
|
35
|
-
waitForVisible(selector: string, timeout?: number): Promise<void>;
|
|
36
|
-
waitForResponse(pattern: string, timeout?: number): Promise<void>;
|
|
37
|
-
|
|
38
|
-
// ── Init script ───────────────────────────────────────────────────────────
|
|
39
|
-
addInitScript(js: string | (() => void)): Promise<SiteObject>;
|
|
40
|
-
|
|
41
|
-
// ── Event emitter ─────────────────────────────────────────────────────────
|
|
42
|
-
on(event: "navigate", handler: (url: string) => void): () => void;
|
|
43
|
-
on(event: string, handler: (data: unknown) => void): () => void;
|
|
44
|
-
off(event: string, handler: (data: unknown) => void): void;
|
|
45
|
-
|
|
46
|
-
// ── Interactions ──────────────────────────────────────────────────────────
|
|
47
|
-
click(selector: string, opts?: { retries?: number; timeout?: number }): Promise<boolean>;
|
|
48
|
-
doubleClick(selector: string): Promise<boolean>;
|
|
49
|
-
hover(selector: string): Promise<boolean>;
|
|
50
|
-
type(
|
|
51
|
-
selector: string,
|
|
52
|
-
text: string,
|
|
53
|
-
opts?: { delay?: number; retries?: number; fact?: boolean; wpm?: number }
|
|
54
|
-
): Promise<boolean>;
|
|
55
|
-
select(selector: string, value: string): Promise<boolean>;
|
|
56
|
-
evaluate<T = unknown>(js: string | ((...args: unknown[]) => T), ...args: unknown[]): Promise<T>;
|
|
57
|
-
|
|
58
|
-
keyboard: {
|
|
59
|
-
press(key: string): Promise<boolean>;
|
|
60
|
-
combo(combo: string): Promise<boolean>;
|
|
61
|
-
};
|
|
62
|
-
mouse: {
|
|
63
|
-
move(x: number, y: number): Promise<boolean>;
|
|
64
|
-
drag(from: { x: number; y: number }, to: { x: number; y: number }): Promise<boolean>;
|
|
65
|
-
};
|
|
66
|
-
scroll: {
|
|
67
|
-
to(selector: string): Promise<boolean>;
|
|
68
|
-
by(px: number): Promise<boolean>;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// ── Fetch ─────────────────────────────────────────────────────────────────
|
|
72
|
-
fetchText(selector: string): Promise<string | null>;
|
|
73
|
-
fetchLinks(selector: string): Promise<string[]>;
|
|
74
|
-
fetchImages(selector: string): Promise<string[]>;
|
|
75
|
-
search: {
|
|
76
|
-
css(query: string): Promise<unknown>;
|
|
77
|
-
id(query: string): Promise<unknown>;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
// ── Screenshot / PDF ──────────────────────────────────────────────────────
|
|
81
|
-
screenshot(filePath?: string): Promise<string>;
|
|
82
|
-
pdf(filePath?: string): Promise<string>;
|
|
83
|
-
blockImages(): Promise<void>;
|
|
84
|
-
unblockImages(): Promise<void>;
|
|
85
|
-
|
|
86
|
-
// ── Cookies ───────────────────────────────────────────────────────────────
|
|
87
|
-
cookies: {
|
|
88
|
-
set(name: string, value: string, domain: string, path?: string): Promise<void>;
|
|
89
|
-
get(name: string): Promise<unknown>;
|
|
90
|
-
delete(name: string): Promise<void>;
|
|
91
|
-
list(): Promise<unknown[]>;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
// ── Interception ──────────────────────────────────────────────────────────
|
|
95
|
-
intercept: {
|
|
96
|
-
block(pattern: string): Promise<void>;
|
|
97
|
-
redirect(pattern: string, redirectUrl: string): Promise<void>;
|
|
98
|
-
headers(pattern: string, headers: Record<string, string>): Promise<void>;
|
|
99
|
-
respond(
|
|
100
|
-
pattern: string,
|
|
101
|
-
handlerOrResponse:
|
|
102
|
-
| { status?: number; contentType?: string; body: string }
|
|
103
|
-
| ((req: { url: string; method: string }) => {
|
|
104
|
-
status?: number;
|
|
105
|
-
contentType?: string;
|
|
106
|
-
body: string;
|
|
107
|
-
})
|
|
108
|
-
): Promise<SiteObject>;
|
|
109
|
-
modifyResponse(
|
|
110
|
-
pattern: string,
|
|
111
|
-
handler: (response: {
|
|
112
|
-
body: string;
|
|
113
|
-
status: number;
|
|
114
|
-
headers: Record<string, string>;
|
|
115
|
-
}) => Promise<{ body?: string; status?: number; headers?: Record<string, string> } | void> | void
|
|
116
|
-
): Promise<SiteObject>;
|
|
117
|
-
clear(): Promise<void>;
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
// ── Network capture ───────────────────────────────────────────────────────
|
|
121
|
-
capture: {
|
|
122
|
-
start(): Promise<void>;
|
|
123
|
-
stop(): Promise<void>;
|
|
124
|
-
requests(): Promise<unknown[]>;
|
|
125
|
-
ws(): Promise<unknown[]>;
|
|
126
|
-
cookies(): Promise<unknown[]>;
|
|
127
|
-
storage(): Promise<unknown>;
|
|
128
|
-
clear(): Promise<void>;
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
// ── Session ───────────────────────────────────────────────────────────────
|
|
132
|
-
session: {
|
|
133
|
-
export(): Promise<unknown>;
|
|
134
|
-
import(data: unknown): Promise<void>;
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// ── Expose / unexpose ─────────────────────────────────────────────────────
|
|
138
|
-
exposeFunction(
|
|
139
|
-
fnName: string,
|
|
140
|
-
handler: (data: unknown) => Promise<unknown> | unknown
|
|
141
|
-
): Promise<SiteObject>;
|
|
142
|
-
unexposeFunction(fnName: string): Promise<SiteObject>;
|
|
143
|
-
clearExposedFunctions(): Promise<SiteObject>;
|
|
144
|
-
exposeAndInject(
|
|
145
|
-
fnName: string,
|
|
146
|
-
handler: (data: unknown) => Promise<unknown> | unknown,
|
|
147
|
-
injectionJs: string | ((fnName: string) => string)
|
|
148
|
-
): Promise<SiteObject>;
|
|
149
|
-
|
|
150
|
-
// ── Elysia route registration ─────────────────────────────────────────────
|
|
151
|
-
api(
|
|
152
|
-
path: string,
|
|
153
|
-
handler: RouteHandler,
|
|
154
|
-
opts?: {
|
|
155
|
-
ttl?: number;
|
|
156
|
-
before?: BeforeMiddleware[];
|
|
157
|
-
method?: "GET" | "POST" | "PUT" | "DELETE";
|
|
158
|
-
}
|
|
159
|
-
): SiteObject;
|
|
160
|
-
|
|
161
|
-
noclose(): SiteObject;
|
|
162
|
-
close(): Promise<void>;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// ── Route summary ─────────────────────────────────────────────────────────────
|
|
166
|
-
|
|
167
|
-
export interface RouteSummary {
|
|
168
|
-
site: string;
|
|
169
|
-
method: RouteConfig["method"];
|
|
170
|
-
path: string;
|
|
171
|
-
ttl: number;
|
|
172
|
-
middlewareCount: number;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// ── Multi-site proxy helpers ──────────────────────────────────────────────────
|
|
176
|
-
|
|
177
|
-
export type AllProxy = {
|
|
178
|
-
[K in keyof SiteObject]: SiteObject[K] extends (...args: infer A) => Promise<infer R>
|
|
179
|
-
? (...args: A) => Promise<R[]>
|
|
180
|
-
: SiteObject[K] extends (...args: infer A) => infer R
|
|
181
|
-
? (...args: A) => Promise<R[]>
|
|
182
|
-
: never;
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
export type DiffProxy = {
|
|
186
|
-
[K in keyof SiteObject]: SiteObject[K] extends (...args: infer A) => Promise<infer R>
|
|
187
|
-
? (...args: A) => Promise<Record<string, R>>
|
|
188
|
-
: SiteObject[K] extends (...args: infer A) => infer R
|
|
189
|
-
? (...args: A) => Promise<Record<string, R>>
|
|
190
|
-
: never;
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
// ── Option bags ───────────────────────────────────────────────────────────────
|
|
194
|
-
|
|
195
|
-
export interface LaunchOptions {
|
|
196
|
-
mode?: TabMode;
|
|
197
|
-
binary?: BinaryMode;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export interface RegisterOptions {
|
|
201
|
-
binary?: BinaryMode;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export interface ServeOptions {
|
|
205
|
-
hostname?: string;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// ── PiggyBase — named methods, no index signature ─────────────────────────────
|
|
209
|
-
|
|
210
|
-
export interface PiggyBase {
|
|
211
|
-
launch(opts?: LaunchOptions): Promise<PiggyBase>;
|
|
212
|
-
register(name: string, url: string, opts?: RegisterOptions): Promise<PiggyBase>;
|
|
213
|
-
actHuman(enable: boolean): PiggyBase;
|
|
214
|
-
mode(m: TabMode): PiggyBase;
|
|
215
|
-
expose(
|
|
216
|
-
name: string,
|
|
217
|
-
handler: (data: unknown) => Promise<unknown> | unknown,
|
|
218
|
-
tabId?: string
|
|
219
|
-
): Promise<PiggyBase>;
|
|
220
|
-
unexpose(name: string, tabId?: string): Promise<PiggyBase>;
|
|
221
|
-
serve(port: number, opts?: ServeOptions): Promise<Elysia>;
|
|
222
|
-
stopServer(): void;
|
|
223
|
-
routes(): RouteSummary[];
|
|
224
|
-
all(sites: SiteObject[]): AllProxy;
|
|
225
|
-
diff(sites: SiteObject[]): DiffProxy;
|
|
226
|
-
close(opts?: { force?: boolean }): Promise<void>;
|
|
227
|
-
detect(mode?: BinaryMode): string | null;
|
|
228
|
-
logger: Logger;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// ── PiggyWithSites<S> — PiggyBase + typed site keys ──────────────────────────
|
|
232
|
-
|
|
233
|
-
export type PiggyWithSites<S extends string> = PiggyBase & {
|
|
234
|
-
[K in S]: SiteObject;
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
// ── usePiggy — takes the piggy import, returns it typed ──────────────────────
|
|
238
|
-
//
|
|
239
|
-
// The variable is ALWAYS called `piggy`. No rename. No new variable.
|
|
240
|
-
// Just pass the import in, get it back fully typed.
|
|
241
|
-
//
|
|
242
|
-
// import piggy, { usePiggy } from 'nothing-browser';
|
|
243
|
-
// usePiggy<"amazon" | "walmart" | "ebay">(piggy);
|
|
244
|
-
//
|
|
245
|
-
// await piggy.launch({ mode: "tab" });
|
|
246
|
-
// await piggy.register("amazon", "https://www.amazon.com/");
|
|
247
|
-
// await piggy.register("walmart", "https://www.walmart.com/");
|
|
248
|
-
// await piggy.register("ebay", "https://www.ebay.com/");
|
|
249
|
-
//
|
|
250
|
-
// await piggy.amazon.navigate(); // ✅ full autocomplete
|
|
251
|
-
// await piggy.walmart.click("..."); // ✅
|
|
252
|
-
// await piggy.ebay.screenshot(); // ✅
|
|
253
|
-
//
|
|
254
|
-
// How it works: TypeScript narrows the type of `piggy` at the call site
|
|
255
|
-
// via the returned typed reference. Assign back to `piggy` with `let`:
|
|
256
|
-
//
|
|
257
|
-
// import piggyRaw, { usePiggy } from 'nothing-browser';
|
|
258
|
-
// const piggy = usePiggy<"amazon" | "walmart" | "ebay">(piggyRaw);
|
|
259
|
-
//
|
|
260
|
-
// OR use the reassignment pattern with `let` for maximum cleanliness:
|
|
261
|
-
//
|
|
262
|
-
// import { piggy as _piggy, usePiggy } from 'nothing-browser';
|
|
263
|
-
// const piggy = usePiggy<"amazon" | "walmart" | "ebay">(_piggy);
|
|
264
|
-
//
|
|
265
|
-
// Either way: `piggy` is the word in your code, always.
|
|
266
|
-
|
|
267
|
-
export function usePiggy<S extends string>(): PiggyWithSites<S> {
|
|
268
|
-
return piggy as unknown as PiggyWithSites<S>;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// ── Module-private state ──────────────────────────────────────────────────────
|
|
10
|
+
type TabMode = "tab" | "process";
|
|
272
11
|
|
|
273
12
|
let _client: PiggyClient | null = null;
|
|
274
13
|
let _tabMode: TabMode = "tab";
|
|
275
14
|
const _extraProcs: { socket: string; client: PiggyClient }[] = [];
|
|
276
|
-
const _sites: Record<string, SiteObject> =
|
|
15
|
+
const _sites: Record<string, SiteObject> = [];
|
|
277
16
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
launch: async (opts?: LaunchOptions): Promise<PiggyBase> => {
|
|
17
|
+
const piggy: any = {
|
|
18
|
+
// ── Local launch (socket) ─────────────────────────────────────────────────
|
|
19
|
+
launch: async (opts?: { mode?: TabMode; binary?: BinaryMode }) => {
|
|
282
20
|
_tabMode = opts?.mode ?? "tab";
|
|
283
21
|
const binaryMode: BinaryMode = opts?.binary ?? "headless";
|
|
284
22
|
await spawnBrowser(binaryMode);
|
|
285
|
-
await new Promise
|
|
23
|
+
await new Promise(r => setTimeout(r, 500));
|
|
286
24
|
_client = new PiggyClient();
|
|
287
25
|
await _client.connect();
|
|
288
26
|
setClient(_client);
|
|
@@ -290,72 +28,126 @@ const piggy: PiggyBase = {
|
|
|
290
28
|
return piggy;
|
|
291
29
|
},
|
|
292
30
|
|
|
293
|
-
|
|
31
|
+
// ── Remote connect (HTTP) ─────────────────────────────────────────────────
|
|
32
|
+
connect: async (opts: { host: string; key: string }) => {
|
|
33
|
+
_tabMode = "tab";
|
|
34
|
+
_client = new PiggyClient({ host: opts.host, key: opts.key });
|
|
35
|
+
await _client.connect();
|
|
36
|
+
setClient(_client);
|
|
37
|
+
logger.info(`[piggy] connected (HTTP) → ${opts.host}`);
|
|
38
|
+
return piggy;
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// ── Register ──────────────────────────────────────────────────────────────
|
|
42
|
+
register: async (
|
|
43
|
+
name: string,
|
|
44
|
+
url: string,
|
|
45
|
+
opts?: {
|
|
46
|
+
binary?: BinaryMode;
|
|
47
|
+
pool?: number;
|
|
48
|
+
}
|
|
49
|
+
) => {
|
|
294
50
|
if (!url?.trim()) throw new Error(`No URL for site "${name}"`);
|
|
295
51
|
const binaryMode: BinaryMode = opts?.binary ?? "headless";
|
|
296
|
-
|
|
297
|
-
let siteObj: SiteObject;
|
|
52
|
+
const poolSize = opts?.pool ?? 0;
|
|
298
53
|
|
|
299
54
|
if (_tabMode === "tab") {
|
|
300
|
-
if (!_client) throw new Error("No client. Call piggy.launch() first.");
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
55
|
+
if (!_client) throw new Error("No client. Call piggy.launch() or piggy.connect() first.");
|
|
56
|
+
|
|
57
|
+
if (poolSize > 1) {
|
|
58
|
+
const pool = new TabPool(_client, poolSize, url, name);
|
|
59
|
+
await pool.init();
|
|
60
|
+
const siteObj = createSiteObject(name, url, _client, "default", pool);
|
|
61
|
+
_sites[name] = siteObj;
|
|
62
|
+
piggy[name] = siteObj;
|
|
63
|
+
logger.success(`[${name}] registered with pool of ${poolSize} tabs`);
|
|
64
|
+
} else {
|
|
65
|
+
const tabId = await _client.newTab();
|
|
66
|
+
const siteObj = createSiteObject(name, url, _client, tabId);
|
|
67
|
+
_sites[name] = siteObj;
|
|
68
|
+
piggy[name] = siteObj;
|
|
69
|
+
logger.success(`[${name}] registered as tab ${tabId}`);
|
|
70
|
+
}
|
|
304
71
|
} else {
|
|
305
72
|
const socketName = `piggy_${name}`;
|
|
306
73
|
await spawnBrowserOnSocket(socketName, binaryMode);
|
|
307
|
-
await new Promise
|
|
74
|
+
await new Promise(r => setTimeout(r, 500));
|
|
308
75
|
const c = new PiggyClient(socketName);
|
|
309
76
|
await c.connect();
|
|
310
77
|
_extraProcs.push({ socket: socketName, client: c });
|
|
311
|
-
siteObj = createSiteObject(name, url, c, "default")
|
|
78
|
+
const siteObj = createSiteObject(name, url, c, "default");
|
|
79
|
+
_sites[name] = siteObj;
|
|
80
|
+
piggy[name] = siteObj;
|
|
312
81
|
logger.success(`[${name}] registered as process on "${socketName}"`);
|
|
313
82
|
}
|
|
314
83
|
|
|
315
|
-
_sites[name] = siteObj;
|
|
316
|
-
(piggy as unknown as Record<string, unknown>)[name] = siteObj;
|
|
317
84
|
return piggy;
|
|
318
85
|
},
|
|
319
86
|
|
|
320
|
-
|
|
87
|
+
// ── Global controls ───────────────────────────────────────────────────────
|
|
88
|
+
actHuman: (enable: boolean) => {
|
|
321
89
|
setHumanMode(enable);
|
|
322
90
|
logger.info(`[piggy] actHuman: ${enable}`);
|
|
323
91
|
return piggy;
|
|
324
92
|
},
|
|
325
93
|
|
|
326
|
-
mode: (m: TabMode)
|
|
327
|
-
_tabMode = m;
|
|
328
|
-
return piggy;
|
|
329
|
-
},
|
|
94
|
+
mode: (m: TabMode) => { _tabMode = m; return piggy; },
|
|
330
95
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
tabId = "default"
|
|
335
|
-
): Promise<PiggyBase> => {
|
|
336
|
-
if (!_client) throw new Error("No client. Call piggy.launch() first.");
|
|
96
|
+
// ── Expose Function ───────────────────────────────────────────────────────
|
|
97
|
+
expose: async (name: string, handler: (data: any) => Promise<any> | any, tabId = "default") => {
|
|
98
|
+
if (!_client) throw new Error("No client. Call piggy.launch() or piggy.connect() first.");
|
|
337
99
|
await _client.exposeFunction(name, handler, tabId);
|
|
338
100
|
logger.success(`[piggy] exposed global function: ${name}`);
|
|
339
101
|
return piggy;
|
|
340
102
|
},
|
|
341
103
|
|
|
342
|
-
unexpose: async (name: string, tabId = "default")
|
|
343
|
-
if (!_client) throw new Error("No client. Call piggy.launch() first.");
|
|
104
|
+
unexpose: async (name: string, tabId = "default") => {
|
|
105
|
+
if (!_client) throw new Error("No client. Call piggy.launch() or piggy.connect() first.");
|
|
344
106
|
await _client.unexposeFunction(name, tabId);
|
|
345
107
|
logger.info(`[piggy] unexposed function: ${name}`);
|
|
346
108
|
return piggy;
|
|
347
109
|
},
|
|
348
110
|
|
|
349
|
-
|
|
350
|
-
|
|
111
|
+
// ── Proxy ─────────────────────────────────────────────────────────────────
|
|
112
|
+
proxy: {
|
|
113
|
+
load: (path: string) => { if (!_client) throw new Error("No client"); return _client.proxyLoad(path); },
|
|
114
|
+
fetch: (url: string) => { if (!_client) throw new Error("No client"); return _client.proxyFetch(url); },
|
|
115
|
+
ovpn: (path: string) => { if (!_client) throw new Error("No client"); return _client.proxyOvpn(path); },
|
|
116
|
+
set: (opts: Parameters<PiggyClient["proxySet"]>[0]) => { if (!_client) throw new Error("No client"); return _client.proxySet(opts); },
|
|
117
|
+
test: () => { if (!_client) throw new Error("No client"); return _client.proxyTest(); },
|
|
118
|
+
testStop: () => { if (!_client) throw new Error("No client"); return _client.proxyTestStop(); },
|
|
119
|
+
next: () => { if (!_client) throw new Error("No client"); return _client.proxyNext(); },
|
|
120
|
+
disable: () => { if (!_client) throw new Error("No client"); return _client.proxyDisable(); },
|
|
121
|
+
enable: () => { if (!_client) throw new Error("No client"); return _client.proxyEnable(); },
|
|
122
|
+
current: () => { if (!_client) throw new Error("No client"); return _client.proxyCurrent(); },
|
|
123
|
+
stats: () => { if (!_client) throw new Error("No client"); return _client.proxyStats(); },
|
|
124
|
+
list: (limit?: number) => { if (!_client) throw new Error("No client"); return _client.proxyList(limit); },
|
|
125
|
+
rotation: (mode: "none" | "timed" | "perrequest", interval?: number) => { if (!_client) throw new Error("No client"); return _client.proxyRotation(mode, interval); },
|
|
126
|
+
config: (opts: { skipDead?: boolean; autoCheck?: boolean }) => { if (!_client) throw new Error("No client"); return _client.proxyConfig(opts); },
|
|
127
|
+
save: (path: string, filter?: "alive" | "dead" | "all") => { if (!_client) throw new Error("No client"); return _client.proxySave(path, filter); },
|
|
128
|
+
on: (event: string, handler: (data: any) => void) => { if (!_client) throw new Error("No client"); return _client.onProxyEvent(event, handler); },
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
// ── Elysia server ─────────────────────────────────────────────────────────
|
|
132
|
+
serve: (
|
|
133
|
+
port: number,
|
|
134
|
+
opts?: {
|
|
135
|
+
hostname?: string;
|
|
136
|
+
title?: string;
|
|
137
|
+
version?: string;
|
|
138
|
+
description?: string;
|
|
139
|
+
path?: string;
|
|
140
|
+
}
|
|
141
|
+
) => startServer(port, opts?.hostname, opts),
|
|
351
142
|
|
|
352
143
|
stopServer,
|
|
353
144
|
|
|
354
|
-
|
|
355
|
-
|
|
145
|
+
// ── Route listing ─────────────────────────────────────────────────────────
|
|
146
|
+
routes: () =>
|
|
147
|
+
Array.from(routeRegistry.entries()).map(([key, cfg]) => {
|
|
356
148
|
const [site] = key.split(":");
|
|
357
149
|
return {
|
|
358
|
-
site
|
|
150
|
+
site,
|
|
359
151
|
method: cfg.method,
|
|
360
152
|
path: `/${site}${cfg.path}`,
|
|
361
153
|
ttl: cfg.ttl,
|
|
@@ -363,31 +155,24 @@ const piggy: PiggyBase = {
|
|
|
363
155
|
};
|
|
364
156
|
}),
|
|
365
157
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
sites.map((s) =>
|
|
372
|
-
(s as unknown as Record<string, (...a: unknown[]) => unknown>)[method]?.(...args)
|
|
373
|
-
)
|
|
374
|
-
),
|
|
158
|
+
// ── Multi-site ────────────────────────────────────────────────────────────
|
|
159
|
+
all: (sites: SiteObject[]) =>
|
|
160
|
+
new Proxy({} as any, {
|
|
161
|
+
get: (_, method: string) =>
|
|
162
|
+
(...args: any[]) => Promise.all(sites.map((s: any) => s[method]?.(...args))),
|
|
375
163
|
}),
|
|
376
164
|
|
|
377
|
-
diff: (sites: SiteObject[])
|
|
378
|
-
new Proxy({} as
|
|
379
|
-
get: (
|
|
380
|
-
async (...args:
|
|
381
|
-
const results = await Promise.all(
|
|
382
|
-
|
|
383
|
-
(s as unknown as Record<string, (...a: unknown[]) => unknown>)[method]?.(...args)
|
|
384
|
-
)
|
|
385
|
-
);
|
|
386
|
-
return Object.fromEntries(sites.map((s, i) => [s._name ?? i, results[i]]));
|
|
165
|
+
diff: (sites: SiteObject[]) =>
|
|
166
|
+
new Proxy({} as any, {
|
|
167
|
+
get: (_, method: string) =>
|
|
168
|
+
async (...args: any[]) => {
|
|
169
|
+
const results = await Promise.all(sites.map((s: any) => s[method]?.(...args)));
|
|
170
|
+
return Object.fromEntries(sites.map((s: any, i) => [s._name ?? i, results[i]]));
|
|
387
171
|
},
|
|
388
172
|
}),
|
|
389
173
|
|
|
390
|
-
|
|
174
|
+
// ── Shutdown ──────────────────────────────────────────────────────────────
|
|
175
|
+
close: async (opts?: { force?: boolean }) => {
|
|
391
176
|
stopServer();
|
|
392
177
|
if (opts?.force) {
|
|
393
178
|
for (const { client: c } of _extraProcs) c.disconnect();
|
|
@@ -397,7 +182,7 @@ const piggy: PiggyBase = {
|
|
|
397
182
|
keepAliveSites.clear();
|
|
398
183
|
} else {
|
|
399
184
|
for (const [name, site] of Object.entries(_sites)) {
|
|
400
|
-
if (!keepAliveSites.has(name)) await site.close?.();
|
|
185
|
+
if (!keepAliveSites.has(name)) await (site as any).close?.();
|
|
401
186
|
}
|
|
402
187
|
if (keepAliveSites.size === 0) {
|
|
403
188
|
for (const { client: c } of _extraProcs) c.disconnect();
|
|
@@ -415,5 +200,18 @@ const piggy: PiggyBase = {
|
|
|
415
200
|
logger,
|
|
416
201
|
};
|
|
417
202
|
|
|
203
|
+
// ── usePiggy ──────────────────────────────────────────────────────────────────
|
|
204
|
+
// Typed accessor — call AFTER register() so sites exist on piggy.
|
|
205
|
+
// const { amazon, ebay } = usePiggy<"amazon" | "ebay">()
|
|
206
|
+
|
|
207
|
+
type TypedPiggy<Sites extends string> = typeof piggy & {
|
|
208
|
+
[K in Sites]: SiteObject;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
export function usePiggy<Sites extends string>(): TypedPiggy<Sites> {
|
|
212
|
+
return piggy as TypedPiggy<Sites>;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export type { SiteObject };
|
|
418
216
|
export default piggy;
|
|
419
217
|
export { piggy };
|