@avasapp/agent-bridge 0.1.0 → 0.3.0
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/README.md +168 -18
- package/dist/adapters/ably.cjs +344 -0
- package/dist/adapters/ably.d.cts +37 -0
- package/dist/adapters/expo-router.d.cts +1 -1
- package/dist/adapters/react-native-mmkv.d.cts +1 -1
- package/dist/adapters/socket-io.cjs +317 -0
- package/dist/adapters/socket-io.d.cts +21 -0
- package/dist/adapters/tanstack-query.d.cts +1 -1
- package/dist/adapters/zustand.d.cts +1 -1
- package/dist/{chunk-UEWFQWCY.js → chunk-S2FYN2R5.js} +122 -2
- package/dist/cli.js +34 -45
- package/dist/client/index.cjs +109 -4
- package/dist/client/index.d.ts +44 -1
- package/dist/client/index.js +5 -3
- package/dist/expo/index.d.cts +1 -1
- package/dist/network/index.cjs +186 -21
- package/dist/network/index.d.cts +45 -2
- package/dist/noop/ably.cjs +26 -0
- package/dist/noop/index.cjs +9 -0
- package/dist/noop/network.cjs +7 -1
- package/dist/noop/realtime.cjs +40 -0
- package/dist/noop/socket-io.cjs +26 -0
- package/dist/realtime/index.cjs +279 -0
- package/dist/realtime/index.d.cts +138 -0
- package/dist/runtime/index.cjs +209 -13
- package/dist/runtime/index.d.cts +23 -3
- package/dist/{types-C6DUUHnB.d.cts → types-6kKQsLC1.d.cts} +40 -1
- package/entries/ably.cjs +9 -0
- package/entries/realtime.cjs +9 -0
- package/entries/socket.io.cjs +9 -0
- package/package.json +21 -4
- package/skills/agent-bridge/SKILL.md +68 -5
package/dist/client/index.cjs
CHANGED
|
@@ -34,7 +34,8 @@ __export(client_exports, {
|
|
|
34
34
|
connect: () => connect,
|
|
35
35
|
connectSession: () => connectSession,
|
|
36
36
|
listDevices: () => listDevices,
|
|
37
|
-
listSessions: () => listSessions
|
|
37
|
+
listSessions: () => listSessions,
|
|
38
|
+
runFlow: () => runFlow
|
|
38
39
|
});
|
|
39
40
|
module.exports = __toCommonJS(client_exports);
|
|
40
41
|
|
|
@@ -213,6 +214,8 @@ async function connectExpo(metro, device, discoveryMs = 600) {
|
|
|
213
214
|
|
|
214
215
|
// src/client/cdp.ts
|
|
215
216
|
var import_ws2 = __toESM(require("ws"), 1);
|
|
217
|
+
var CannotEvaluate = class extends Error {
|
|
218
|
+
};
|
|
216
219
|
var targetLabel = (t) => `${t.title}${t.deviceName ? ` [${t.deviceName}]` : ""}`;
|
|
217
220
|
async function connectCdp(metro, device) {
|
|
218
221
|
const target = pickOne(await listCdpTargets(metro), device, targetLabel);
|
|
@@ -259,6 +262,7 @@ async function connectCdp(metro, device) {
|
|
|
259
262
|
expression,
|
|
260
263
|
returnByValue: true
|
|
261
264
|
});
|
|
265
|
+
if (message.error?.code === -32601) throw new CannotEvaluate();
|
|
262
266
|
const details = message.result?.exceptionDetails;
|
|
263
267
|
if (details) {
|
|
264
268
|
const text = details.exception?.description ?? details.text ?? "evaluation failed";
|
|
@@ -273,10 +277,10 @@ async function connectCdp(metro, device) {
|
|
|
273
277
|
info = JSON.parse(
|
|
274
278
|
String(await evaluate(`${CDP_GLOBAL}.info()`))
|
|
275
279
|
);
|
|
276
|
-
} catch {
|
|
280
|
+
} catch (error) {
|
|
277
281
|
ws.close();
|
|
278
282
|
throw new Error(
|
|
279
|
-
`agent-bridge isn't running in ${targetLabel(target)}. Is useAgentBridge mounted in a dev build?`
|
|
283
|
+
error instanceof CannotEvaluate ? `The debugger for ${targetLabel(target)} can't run code (no Runtime.evaluate, as in Expo Go on Android). Use --transport expo, or a dev build.` : `agent-bridge isn't running in ${targetLabel(target)}. Is useAgentBridge mounted in a dev build?`
|
|
280
284
|
);
|
|
281
285
|
}
|
|
282
286
|
return {
|
|
@@ -327,6 +331,106 @@ async function openConnection(options = {}) {
|
|
|
327
331
|
}
|
|
328
332
|
}
|
|
329
333
|
|
|
334
|
+
// src/client/log-lines.ts
|
|
335
|
+
function logLine(entry) {
|
|
336
|
+
const where = entry.during ? ` during ${entry.during}` : entry.after ? ` after ${entry.after}` : "";
|
|
337
|
+
const first = entry.message.split("\n")[0] ?? "";
|
|
338
|
+
const text = first.length > 300 ? `${first.slice(0, 300)}...` : first;
|
|
339
|
+
return `! ${entry.level}${where}: ${text}`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/client/flow.ts
|
|
343
|
+
function flowScenarios(flow) {
|
|
344
|
+
const declared = [
|
|
345
|
+
...flow.scenario === void 0 ? [] : [flow.scenario],
|
|
346
|
+
...flow.scenarios ?? []
|
|
347
|
+
];
|
|
348
|
+
return declared.map((s) => {
|
|
349
|
+
if (typeof s === "string") return { name: s };
|
|
350
|
+
if (s && typeof s === "object" && typeof s.name === "string") return s;
|
|
351
|
+
throw new Error(
|
|
352
|
+
`A flow's scenario is a name or { name, options }, not ${JSON.stringify(s)}`
|
|
353
|
+
);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
function mergeScenarios(declared, extra) {
|
|
357
|
+
const names = new Set(extra.map((s) => s.name));
|
|
358
|
+
return [...declared.filter((s) => !names.has(s.name)), ...extra];
|
|
359
|
+
}
|
|
360
|
+
var failedLogs = (error) => error instanceof AgentBridgeCallError ? error.logs : [];
|
|
361
|
+
async function runFlow(bridge, flow, options = {}) {
|
|
362
|
+
const print = options.print ?? console.log;
|
|
363
|
+
const needs = mergeScenarios(flowScenarios(flow), options.scenarios ?? []);
|
|
364
|
+
let n = 0;
|
|
365
|
+
let total = 0;
|
|
366
|
+
let errors = 0;
|
|
367
|
+
const t0 = performance.now();
|
|
368
|
+
const report = (logs) => {
|
|
369
|
+
errors += logs.length;
|
|
370
|
+
for (const e of logs) print(` ${logLine(e)}`);
|
|
371
|
+
};
|
|
372
|
+
const timed = async (tool, ...args) => {
|
|
373
|
+
try {
|
|
374
|
+
const result = await bridge.timed(tool, ...args);
|
|
375
|
+
report(result.logs);
|
|
376
|
+
return result;
|
|
377
|
+
} catch (error) {
|
|
378
|
+
report(failedLogs(error));
|
|
379
|
+
throw error;
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
const call = async (tool, ...args) => (await timed(tool, ...args)).value;
|
|
383
|
+
const step = async (label, tool, ...args) => {
|
|
384
|
+
const { value, ms, logs } = await bridge.timed(tool, ...args).catch((error) => {
|
|
385
|
+
report(failedLogs(error));
|
|
386
|
+
throw error;
|
|
387
|
+
});
|
|
388
|
+
total += ms;
|
|
389
|
+
print(
|
|
390
|
+
`${String(++n).padStart(2, "0")} ${label.padEnd(30)} ${ms.toFixed(1).padStart(7)} ms`
|
|
391
|
+
);
|
|
392
|
+
report(logs);
|
|
393
|
+
return value;
|
|
394
|
+
};
|
|
395
|
+
const restoreErrors = [];
|
|
396
|
+
const applied = {};
|
|
397
|
+
if (needs.length && !bridge.tools().some((t) => t.name === "scenario.apply"))
|
|
398
|
+
throw new Error(
|
|
399
|
+
`The flow needs scenario "${needs[0].name}", but the app defines none: pass \`scenarios\` to useAgentBridge.`
|
|
400
|
+
);
|
|
401
|
+
try {
|
|
402
|
+
for (const { name, options: options2 } of needs) {
|
|
403
|
+
const args = options2 === void 0 ? [name] : [name, options2];
|
|
404
|
+
applied[name] = await step(`scenario ${name}`, "scenario.apply", ...args);
|
|
405
|
+
}
|
|
406
|
+
await flow.default({
|
|
407
|
+
bridge: { ...bridge, timed, call },
|
|
408
|
+
call,
|
|
409
|
+
step,
|
|
410
|
+
scenarios: applied
|
|
411
|
+
});
|
|
412
|
+
} finally {
|
|
413
|
+
if (needs.length) {
|
|
414
|
+
try {
|
|
415
|
+
const result = await step("restore", "bridge.restore");
|
|
416
|
+
for (const [name, value] of Object.entries(result ?? {})) {
|
|
417
|
+
const error = value?.error;
|
|
418
|
+
if (error !== void 0) restoreErrors.push(`${name}: ${String(error)}`);
|
|
419
|
+
}
|
|
420
|
+
} catch (error) {
|
|
421
|
+
restoreErrors.push(
|
|
422
|
+
`bridge.restore: ${error instanceof Error ? error.message : String(error)}`
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
for (const e of restoreErrors) print(` ! restore failed: ${e}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
print(
|
|
429
|
+
`${n} steps, ${total.toFixed(1)} ms in calls, ${(performance.now() - t0).toFixed(0)} ms wall (${bridge.transport})${errors ? `, ${errors} error${errors === 1 ? "" : "s"}` : ""}`
|
|
430
|
+
);
|
|
431
|
+
return { steps: n, errors, restoreErrors };
|
|
432
|
+
}
|
|
433
|
+
|
|
330
434
|
// src/client/session/client.ts
|
|
331
435
|
var import_node_net = require("net");
|
|
332
436
|
var import_node_readline = require("readline");
|
|
@@ -564,5 +668,6 @@ async function listDevices(options = {}) {
|
|
|
564
668
|
connect,
|
|
565
669
|
connectSession,
|
|
566
670
|
listDevices,
|
|
567
|
-
listSessions
|
|
671
|
+
listSessions,
|
|
672
|
+
runFlow
|
|
568
673
|
});
|
package/dist/client/index.d.ts
CHANGED
|
@@ -25,6 +25,49 @@ type DeviceInfo = {
|
|
|
25
25
|
|
|
26
26
|
type TransportName = 'expo' | 'cdp';
|
|
27
27
|
|
|
28
|
+
/** A scenario a flow needs: a name, or a name with options. */
|
|
29
|
+
type FlowScenario = string | {
|
|
30
|
+
name: string;
|
|
31
|
+
options?: unknown;
|
|
32
|
+
};
|
|
33
|
+
type FlowApi = {
|
|
34
|
+
bridge: AgentBridge;
|
|
35
|
+
call: AgentBridge['call'];
|
|
36
|
+
step: (label: string, tool: string, ...args: unknown[]) => Promise<unknown>;
|
|
37
|
+
/** What each declared scenario's apply returned, by name. */
|
|
38
|
+
scenarios: Record<string, unknown>;
|
|
39
|
+
};
|
|
40
|
+
/** A flow file: `export default async ({ step, call }) => {}`, plus what it needs. */
|
|
41
|
+
type FlowModule = {
|
|
42
|
+
default: (api: FlowApi) => Promise<void>;
|
|
43
|
+
/** Applied before the flow and undone after it, even when it fails. */
|
|
44
|
+
scenario?: FlowScenario;
|
|
45
|
+
scenarios?: FlowScenario[];
|
|
46
|
+
};
|
|
47
|
+
type FlowResult = {
|
|
48
|
+
steps: number;
|
|
49
|
+
/** Errors the app logged during the flow. */
|
|
50
|
+
errors: number;
|
|
51
|
+
/** Restorers that failed in the closing bridge.restore, with why. */
|
|
52
|
+
restoreErrors: string[];
|
|
53
|
+
};
|
|
54
|
+
type RunFlowOptions = {
|
|
55
|
+
/** Where step lines go. Default: console.log. */
|
|
56
|
+
print?: (line: string) => void;
|
|
57
|
+
/** More scenarios to apply, e.g. from `--scenario`; one of the same name replaces the flow's. */
|
|
58
|
+
scenarios?: Array<{
|
|
59
|
+
name: string;
|
|
60
|
+
options?: unknown;
|
|
61
|
+
}>;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Runs a flow module you imported: applies its scenarios, runs it, and when
|
|
65
|
+
* there were any, undoes everything with bridge.restore afterwards, even
|
|
66
|
+
* when it fails. Throws what the flow threw; otherwise returns how it went.
|
|
67
|
+
* It runs only the code you hand it, in this process.
|
|
68
|
+
*/
|
|
69
|
+
declare function runFlow(bridge: AgentBridge, flow: FlowModule, options?: RunFlowOptions): Promise<FlowResult>;
|
|
70
|
+
|
|
28
71
|
/** What a running session writes next to its socket. Readable only by the user. */
|
|
29
72
|
type SessionState = {
|
|
30
73
|
name: string;
|
|
@@ -109,4 +152,4 @@ type ListedDevice = {
|
|
|
109
152
|
/** Apps with the bridge on Expo's socket, plus every React Native page Metro knows. */
|
|
110
153
|
declare function listDevices(options?: Pick<ConnectOptions, 'metro'>): Promise<ListedDevice[]>;
|
|
111
154
|
|
|
112
|
-
export { type AgentBridge, AgentBridgeCallError, type ConnectOptions, type DeviceInfo, type ListedDevice, type LogEntry, type SessionConnectOptions, type SessionState, type Timed, type ToolInfo, type TransportName, connect, connectSession, listDevices, listSessions };
|
|
155
|
+
export { type AgentBridge, AgentBridgeCallError, type ConnectOptions, type DeviceInfo, type FlowApi, type FlowModule, type FlowResult, type FlowScenario, type ListedDevice, type LogEntry, type RunFlowOptions, type SessionConnectOptions, type SessionState, type Timed, type ToolInfo, type TransportName, connect, connectSession, listDevices, listSessions, runFlow };
|
package/dist/client/index.js
CHANGED
|
@@ -3,12 +3,14 @@ import {
|
|
|
3
3
|
connect,
|
|
4
4
|
connectSession,
|
|
5
5
|
listDevices,
|
|
6
|
-
listSessions
|
|
7
|
-
|
|
6
|
+
listSessions,
|
|
7
|
+
runFlow
|
|
8
|
+
} from "../chunk-S2FYN2R5.js";
|
|
8
9
|
export {
|
|
9
10
|
AgentBridgeCallError,
|
|
10
11
|
connect,
|
|
11
12
|
connectSession,
|
|
12
13
|
listDevices,
|
|
13
|
-
listSessions
|
|
14
|
+
listSessions,
|
|
15
|
+
runFlow
|
|
14
16
|
};
|
package/dist/expo/index.d.cts
CHANGED
package/dist/network/index.cjs
CHANGED
|
@@ -22,11 +22,78 @@ var network_exports = {};
|
|
|
22
22
|
__export(network_exports, {
|
|
23
23
|
installNetwork: () => installNetwork,
|
|
24
24
|
mock: () => mock,
|
|
25
|
+
mockApi: () => mockApi,
|
|
25
26
|
mockRequests: () => mockRequests,
|
|
26
|
-
networkTools: () => networkTools
|
|
27
|
+
networkTools: () => networkTools,
|
|
28
|
+
strictNetwork: () => strictNetwork
|
|
27
29
|
});
|
|
28
30
|
module.exports = __toCommonJS(network_exports);
|
|
29
31
|
|
|
32
|
+
// src/network/api.ts
|
|
33
|
+
var escape = (s) => s.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
34
|
+
var ROUTE = /^(?:([A-Za-z]+)\s+)?(\/\S*)$/;
|
|
35
|
+
function compileRoute(baseUrl, route) {
|
|
36
|
+
const parsed = ROUTE.exec(route.trim());
|
|
37
|
+
if (!parsed)
|
|
38
|
+
throw new Error(`Bad route "${route}": use "METHOD /path" or "/path"`);
|
|
39
|
+
const [, method, path] = parsed;
|
|
40
|
+
const names = [];
|
|
41
|
+
const pattern = path.split(/(:[A-Za-z_][A-Za-z0-9_]*|\*)/).map((part) => {
|
|
42
|
+
if (part === "*") {
|
|
43
|
+
names.push("*");
|
|
44
|
+
return "([^?#]*)";
|
|
45
|
+
}
|
|
46
|
+
if (part.startsWith(":")) {
|
|
47
|
+
names.push(part.slice(1));
|
|
48
|
+
return "([^/?#]+)";
|
|
49
|
+
}
|
|
50
|
+
return escape(part);
|
|
51
|
+
}).join("");
|
|
52
|
+
const base = baseUrl ? escape(baseUrl.replace(/\/+$/, "")) : "(?:[a-z][a-z0-9+.-]*://[^/?#]*)?";
|
|
53
|
+
return {
|
|
54
|
+
method: method?.toUpperCase(),
|
|
55
|
+
regex: new RegExp(`^${base}${pattern}(?:[?#].*)?$`, "i"),
|
|
56
|
+
names
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function queryOf(url) {
|
|
60
|
+
const search = /\?([^#]*)/.exec(url)?.[1];
|
|
61
|
+
const out = {};
|
|
62
|
+
if (!search) return out;
|
|
63
|
+
for (const pair of search.split("&")) {
|
|
64
|
+
if (!pair) continue;
|
|
65
|
+
const [key, value = ""] = pair.split("=");
|
|
66
|
+
const decode = (s) => {
|
|
67
|
+
try {
|
|
68
|
+
return decodeURIComponent(s.replace(/\+/g, " "));
|
|
69
|
+
} catch {
|
|
70
|
+
return s;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
out[decode(key)] = decode(value);
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
function apiRoutes(baseUrl, routes, options = {}) {
|
|
78
|
+
const { id, ...mockOptions } = options;
|
|
79
|
+
return Object.entries(routes).map(([route, response]) => {
|
|
80
|
+
const { method, regex, names } = compileRoute(baseUrl, route);
|
|
81
|
+
const match = { url: regex, method };
|
|
82
|
+
const handler = typeof response === "function" ? (request) => {
|
|
83
|
+
const values = regex.exec(request.url)?.slice(1) ?? [];
|
|
84
|
+
const params = Object.fromEntries(
|
|
85
|
+
names.map((name, i) => [name, decodeURIComponent(values[i] ?? "")])
|
|
86
|
+
);
|
|
87
|
+
return response({ ...request, params, query: queryOf(request.url) });
|
|
88
|
+
} : response;
|
|
89
|
+
return {
|
|
90
|
+
match,
|
|
91
|
+
response: handler,
|
|
92
|
+
options: { ...mockOptions, ...id && { id: `${id} ${route}` } }
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
30
97
|
// src/network/body.ts
|
|
31
98
|
var MAX_BODY = 2048;
|
|
32
99
|
function formatBody(text) {
|
|
@@ -74,7 +141,10 @@ function networkState() {
|
|
|
74
141
|
insideFetch: 0,
|
|
75
142
|
devHost: null,
|
|
76
143
|
skip: [],
|
|
77
|
-
uninstall: []
|
|
144
|
+
uninstall: [],
|
|
145
|
+
strictApp: [],
|
|
146
|
+
strictAgent: null,
|
|
147
|
+
blocked: /* @__PURE__ */ new Map()
|
|
78
148
|
});
|
|
79
149
|
return g[KEY];
|
|
80
150
|
}
|
|
@@ -207,6 +277,41 @@ function responseParts(response) {
|
|
|
207
277
|
};
|
|
208
278
|
return { status, text: r.body ?? "", headers: { ...r.headers } };
|
|
209
279
|
}
|
|
280
|
+
function strictRule(options = {}) {
|
|
281
|
+
const allow = options.allow ?? [];
|
|
282
|
+
if (!Array.isArray(allow))
|
|
283
|
+
throw new Error("allow must be a list of URL substrings or regexes");
|
|
284
|
+
return {
|
|
285
|
+
allow: allow.map((rule) => urlTest(rule)),
|
|
286
|
+
allowShown: allow.map(
|
|
287
|
+
(rule) => typeof rule === "string" ? rule : String(rule instanceof RegExp ? rule : new RegExp(rule.regex, rule.flags))
|
|
288
|
+
),
|
|
289
|
+
status: options.status ?? 501,
|
|
290
|
+
offline: options.offline === true
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function strictRules(state) {
|
|
294
|
+
if (state.strictAgent === false) return [];
|
|
295
|
+
return state.strictAgent ? [state.strictAgent, ...state.strictApp] : state.strictApp;
|
|
296
|
+
}
|
|
297
|
+
var BLOCKED_HEADER = "x-agent-bridge";
|
|
298
|
+
function blockedResponse(state, method, url) {
|
|
299
|
+
const rules = strictRules(state);
|
|
300
|
+
if (!rules.length || rules.some((r) => r.allow.some((test) => test(url))))
|
|
301
|
+
return null;
|
|
302
|
+
const key = `${method} ${url}`;
|
|
303
|
+
const seen = state.blocked.get(key);
|
|
304
|
+
if (seen) seen.count += 1;
|
|
305
|
+
else state.blocked.set(key, { method, url, count: 1 });
|
|
306
|
+
const error = `agent-bridge strict network: no mock for ${key}`;
|
|
307
|
+
console.error(`${error}. Mock it, or allow it in strict mode.`);
|
|
308
|
+
if (rules[0].offline) return { offline: true };
|
|
309
|
+
return {
|
|
310
|
+
status: rules[0].status,
|
|
311
|
+
json: { error },
|
|
312
|
+
headers: { [BLOCKED_HEADER]: "blocked" }
|
|
313
|
+
};
|
|
314
|
+
}
|
|
210
315
|
var isOffline = (response) => response.offline === true;
|
|
211
316
|
var OFFLINE_MESSAGE = "Network request failed";
|
|
212
317
|
var sleep = (ms) => ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
|
|
@@ -302,8 +407,20 @@ function patchFetch(state) {
|
|
|
302
407
|
}
|
|
303
408
|
);
|
|
304
409
|
};
|
|
410
|
+
const passOn = () => {
|
|
411
|
+
const blocked = blockedResponse(state, method, url);
|
|
412
|
+
if (!blocked) return real();
|
|
413
|
+
entry.blocked = true;
|
|
414
|
+
if (isOffline(blocked)) {
|
|
415
|
+
finishEntry(entry, { error: `${OFFLINE_MESSAGE} (strict network)` });
|
|
416
|
+
return Promise.reject(new TypeError(OFFLINE_MESSAGE));
|
|
417
|
+
}
|
|
418
|
+
const { status, text } = responseParts(blocked);
|
|
419
|
+
finishEntry(entry, { status, responseBody: formatBody(text) });
|
|
420
|
+
return Promise.resolve(mockedResponse(blocked));
|
|
421
|
+
};
|
|
305
422
|
const candidates = matchingMocks(state, method, url);
|
|
306
|
-
if (!candidates.length) return
|
|
423
|
+
if (!candidates.length) return passOn();
|
|
307
424
|
const request = async () => {
|
|
308
425
|
let body = requestBody;
|
|
309
426
|
if (body === void 0 && init?.body === void 0 && req.clone) {
|
|
@@ -322,7 +439,7 @@ function patchFetch(state) {
|
|
|
322
439
|
throw error;
|
|
323
440
|
});
|
|
324
441
|
return answered.then(async (hit) => {
|
|
325
|
-
if (!hit) return
|
|
442
|
+
if (!hit) return passOn();
|
|
326
443
|
entry.mocked = true;
|
|
327
444
|
await sleep(hit.mock.delayMs);
|
|
328
445
|
if (init?.signal?.aborted) {
|
|
@@ -373,9 +490,9 @@ function fire(xhr, type) {
|
|
|
373
490
|
const Ctor = (type === "readystatechange" ? void 0 : globalThis.ProgressEvent) ?? globalThis.Event;
|
|
374
491
|
xhr.dispatchEvent(new Ctor(type));
|
|
375
492
|
}
|
|
376
|
-
function respond(xhr,
|
|
377
|
-
const offline = isOffline(
|
|
378
|
-
const { status, text, headers } = responseParts(
|
|
493
|
+
function respond(xhr, mocked, url) {
|
|
494
|
+
const offline = isOffline(mocked);
|
|
495
|
+
const { status, text, headers } = responseParts(mocked);
|
|
379
496
|
if (isRN(xhr)) {
|
|
380
497
|
const id = -++fakeRequestId;
|
|
381
498
|
xhr.__didCreateRequest(id);
|
|
@@ -454,18 +571,28 @@ function patchXhr(state) {
|
|
|
454
571
|
url,
|
|
455
572
|
requestBody: formatBody(text)
|
|
456
573
|
});
|
|
574
|
+
const passOn = () => {
|
|
575
|
+
const blocked = blockedResponse(state, method, url);
|
|
576
|
+
if (!blocked) {
|
|
577
|
+
track(this, entry);
|
|
578
|
+
return send.call(this, body);
|
|
579
|
+
}
|
|
580
|
+
const { status, text: out } = responseParts(blocked);
|
|
581
|
+
entry.blocked = true;
|
|
582
|
+
finishEntry(
|
|
583
|
+
entry,
|
|
584
|
+
isOffline(blocked) ? { error: `${OFFLINE_MESSAGE} (strict network)` } : { status, responseBody: formatBody(out) }
|
|
585
|
+
);
|
|
586
|
+
void Promise.resolve().then(() => {
|
|
587
|
+
if (!meta.aborted) respond(this, blocked, url);
|
|
588
|
+
});
|
|
589
|
+
};
|
|
457
590
|
const candidates = matchingMocks(state, method, url);
|
|
458
|
-
if (!candidates.length)
|
|
459
|
-
track(this, entry);
|
|
460
|
-
return send.call(this, body);
|
|
461
|
-
}
|
|
591
|
+
if (!candidates.length) return passOn();
|
|
462
592
|
const request = async () => mockRequest(method, url, meta.headers, text);
|
|
463
593
|
answer(state, candidates, request).then(
|
|
464
594
|
async (hit) => {
|
|
465
|
-
if (!hit)
|
|
466
|
-
track(this, entry);
|
|
467
|
-
return send.call(this, body);
|
|
468
|
-
}
|
|
595
|
+
if (!hit) return passOn();
|
|
469
596
|
entry.mocked = true;
|
|
470
597
|
await sleep(hit.mock.delayMs);
|
|
471
598
|
if (meta.aborted) {
|
|
@@ -478,12 +605,12 @@ function patchXhr(state) {
|
|
|
478
605
|
entry,
|
|
479
606
|
offline ? { error: `${OFFLINE_MESSAGE} (mocked offline)` } : { status, responseBody: formatBody(out) }
|
|
480
607
|
);
|
|
481
|
-
respond(this, hit, url);
|
|
608
|
+
respond(this, hit.response, url);
|
|
482
609
|
},
|
|
483
610
|
(error) => {
|
|
484
611
|
entry.mocked = true;
|
|
485
612
|
finishEntry(entry, { error: errorMessage(error) });
|
|
486
|
-
respond(this, {
|
|
613
|
+
respond(this, { offline: true }, url);
|
|
487
614
|
}
|
|
488
615
|
);
|
|
489
616
|
};
|
|
@@ -513,6 +640,18 @@ function mockRequests(routes) {
|
|
|
513
640
|
for (const m of added) m.remove();
|
|
514
641
|
};
|
|
515
642
|
}
|
|
643
|
+
function mockApi(baseUrl, routes, options) {
|
|
644
|
+
return mockRequests(apiRoutes(baseUrl, routes, options));
|
|
645
|
+
}
|
|
646
|
+
function strictNetwork(options = {}) {
|
|
647
|
+
installNetwork();
|
|
648
|
+
const state = networkState();
|
|
649
|
+
const rule = strictRule(options);
|
|
650
|
+
state.strictApp.push(rule);
|
|
651
|
+
return () => {
|
|
652
|
+
state.strictApp = state.strictApp.filter((r) => r !== rule);
|
|
653
|
+
};
|
|
654
|
+
}
|
|
516
655
|
var describeMock = (m) => ({
|
|
517
656
|
id: m.id,
|
|
518
657
|
source: m.source,
|
|
@@ -556,13 +695,39 @@ function networkTools(options = {}) {
|
|
|
556
695
|
description: "Remove one agent mock by id, or every agent mock when no id. Returns how many.",
|
|
557
696
|
run: (id) => removeMocks(state, (m) => isAgent(m) && (id === void 0 || m.id === id))
|
|
558
697
|
},
|
|
698
|
+
"net.strict": {
|
|
699
|
+
description: "Strict mode: requests no mock answers fail with a 501 (or like offline) instead of reaching a server. [true | false | { allow?: [substring | { regex }], status?, offline? }] sets it for the agent, [null] follows the app again; no args reads it. Returns { strict, source, allow, blocked }.",
|
|
700
|
+
run: (...args) => {
|
|
701
|
+
if (args.length) {
|
|
702
|
+
const value = args[0];
|
|
703
|
+
if (value === null || value === void 0) state.strictAgent = null;
|
|
704
|
+
else if (value === false) state.strictAgent = false;
|
|
705
|
+
else if (value === true) state.strictAgent = strictRule();
|
|
706
|
+
else if (typeof value === "object") state.strictAgent = strictRule(value);
|
|
707
|
+
else throw new Error("Pass true, false, null or { allow?, status?, offline? }");
|
|
708
|
+
}
|
|
709
|
+
const rules = strictRules(state);
|
|
710
|
+
return {
|
|
711
|
+
strict: rules.length > 0,
|
|
712
|
+
source: state.strictAgent !== null ? "agent" : rules.length ? "app" : null,
|
|
713
|
+
allow: rules.flatMap((r) => r.allowShown),
|
|
714
|
+
blocked: [...state.blocked.values()].slice(-20).reverse()
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
},
|
|
559
718
|
"net.clear": {
|
|
560
|
-
description: "Empty the request log. Returns how many entries it held.",
|
|
561
|
-
run: () =>
|
|
719
|
+
description: "Empty the request log and the strict-mode blocked list. Returns how many log entries it held.",
|
|
720
|
+
run: () => {
|
|
721
|
+
state.blocked.clear();
|
|
722
|
+
return state.log.splice(0).length;
|
|
723
|
+
}
|
|
562
724
|
},
|
|
563
725
|
"net.restore": {
|
|
564
|
-
description: "Undo the agent's network changes: remove agent mocks, keep the app's. Returns how many were removed.",
|
|
565
|
-
run: () =>
|
|
726
|
+
description: "Undo the agent's network changes: remove agent mocks and its net.strict setting, keep the app's. Returns how many mocks were removed.",
|
|
727
|
+
run: () => {
|
|
728
|
+
state.strictAgent = null;
|
|
729
|
+
return removeMocks(state, isAgent);
|
|
730
|
+
}
|
|
566
731
|
}
|
|
567
732
|
};
|
|
568
733
|
}
|
package/dist/network/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { T as Tools } from '../types-
|
|
1
|
+
import { T as Tools } from '../types-6kKQsLC1.cjs';
|
|
2
2
|
|
|
3
3
|
/** Which requests a mock answers. A bare string is a URL substring. */
|
|
4
4
|
type MockMatch = string | {
|
|
@@ -57,6 +57,33 @@ type LogEntry = {
|
|
|
57
57
|
responseBody?: string;
|
|
58
58
|
error?: string;
|
|
59
59
|
mocked?: boolean;
|
|
60
|
+
/** Failed by strict mode: no mock answered it. */
|
|
61
|
+
blocked?: boolean;
|
|
62
|
+
};
|
|
63
|
+
type StrictOptions = {
|
|
64
|
+
/** URLs strict mode lets through to the network: substrings or RegExps. */
|
|
65
|
+
allow?: Array<string | RegExp>;
|
|
66
|
+
/** Status of the error response. Default 501. */
|
|
67
|
+
status?: number;
|
|
68
|
+
/** Fail like a network failure instead of answering with a status. */
|
|
69
|
+
offline?: boolean;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** What a `mockApi` handler sees: the request, plus its path params and query. */
|
|
73
|
+
type ApiRequest = MockRequest & {
|
|
74
|
+
/** `:name` segments of the route, and `*` as "*". */
|
|
75
|
+
params: Record<string, string>;
|
|
76
|
+
query: Record<string, string>;
|
|
77
|
+
};
|
|
78
|
+
type ApiHandler = (request: ApiRequest) => MockResponse | undefined | Promise<MockResponse | undefined>;
|
|
79
|
+
/** "METHOD /path" (or "/path" for any method) → a response or a handler. */
|
|
80
|
+
type ApiRoutes = Record<string, MockResponse | ApiHandler>;
|
|
81
|
+
type MockApiOptions = Omit<MockOptions, 'id'> & {
|
|
82
|
+
/**
|
|
83
|
+
* Registering again with the same id replaces these routes (handy with
|
|
84
|
+
* Fast Refresh). Each route's mock id is `<id> <METHOD /path>`.
|
|
85
|
+
*/
|
|
86
|
+
id?: string;
|
|
60
87
|
};
|
|
61
88
|
|
|
62
89
|
/**
|
|
@@ -75,6 +102,22 @@ declare function mock(match: MockMatch, response: MockResponse | MockHandler, op
|
|
|
75
102
|
};
|
|
76
103
|
/** Registers several app mocks. Returns a function that removes them. */
|
|
77
104
|
declare function mockRequests(routes: MockRoute[]): () => void;
|
|
105
|
+
/**
|
|
106
|
+
* Answers many endpoints under one base URL, e.g. a scenario's fixtures:
|
|
107
|
+
* `mockApi(API, { 'GET /me': { json: user }, 'GET /plants/:id': ({ params }) => ... })`.
|
|
108
|
+
* `:name` matches a path segment and `*` the rest; the query string is ignored.
|
|
109
|
+
* Pass '' as the base URL to match the path on any host. Returns a function
|
|
110
|
+
* that removes the routes.
|
|
111
|
+
*/
|
|
112
|
+
declare function mockApi(baseUrl: string, routes: ApiRoutes, options?: MockApiOptions): () => void;
|
|
113
|
+
/**
|
|
114
|
+
* Strict mode: a fetch or XMLHttpRequest that no mock answers fails with an
|
|
115
|
+
* error response (501 by default, or like offline with `offline: true`) and
|
|
116
|
+
* a console error naming it, instead of
|
|
117
|
+
* reaching a server. Only JS requests: native networking (images, native
|
|
118
|
+
* SDKs, WebSockets) isn't covered. Returns a function that turns it off.
|
|
119
|
+
*/
|
|
120
|
+
declare function strictNetwork(options?: StrictOptions): () => void;
|
|
78
121
|
type NetworkToolsOptions = {
|
|
79
122
|
/** Extra URLs never to log or mock: substrings or RegExps. */
|
|
80
123
|
skip?: Array<string | RegExp>;
|
|
@@ -82,4 +125,4 @@ type NetworkToolsOptions = {
|
|
|
82
125
|
/** Read the request log and mock responses: `net.*`. */
|
|
83
126
|
declare function networkTools(options?: NetworkToolsOptions): Tools;
|
|
84
127
|
|
|
85
|
-
export { type LogEntry, type MockHandler, type MockMatch, type MockOptions, type MockRequest, type MockResponse, type MockRoute, type NetworkToolsOptions, installNetwork, mock, mockRequests, networkTools };
|
|
128
|
+
export { type ApiHandler, type ApiRequest, type ApiRoutes, type LogEntry, type MockApiOptions, type MockHandler, type MockMatch, type MockOptions, type MockRequest, type MockResponse, type MockRoute, type NetworkToolsOptions, type StrictOptions, installNetwork, mock, mockApi, mockRequests, networkTools, strictNetwork };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/noop/ably.ts
|
|
21
|
+
var ably_exports = {};
|
|
22
|
+
__export(ably_exports, {
|
|
23
|
+
ablyTools: () => ablyTools
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(ably_exports);
|
|
26
|
+
var ablyTools = () => ({});
|
package/dist/noop/index.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var noop_exports = {};
|
|
22
22
|
__export(noop_exports, {
|
|
23
23
|
cdpTransport: () => cdpTransport,
|
|
24
|
+
createGate: () => createGate,
|
|
24
25
|
settle: () => settle,
|
|
25
26
|
startAgentBridge: () => startAgentBridge,
|
|
26
27
|
useAgentBridge: () => useAgentBridge
|
|
@@ -34,3 +35,11 @@ var useAgentBridge = () => {
|
|
|
34
35
|
};
|
|
35
36
|
var cdpTransport = inert;
|
|
36
37
|
var settle = () => Promise.resolve({ commits: 0, ms: 0 });
|
|
38
|
+
var createGate = (name) => ({
|
|
39
|
+
name,
|
|
40
|
+
closed: false,
|
|
41
|
+
close: () => () => {
|
|
42
|
+
},
|
|
43
|
+
subscribe: () => () => {
|
|
44
|
+
}
|
|
45
|
+
});
|
package/dist/noop/network.cjs
CHANGED
|
@@ -22,8 +22,10 @@ var network_exports = {};
|
|
|
22
22
|
__export(network_exports, {
|
|
23
23
|
installNetwork: () => installNetwork,
|
|
24
24
|
mock: () => mock,
|
|
25
|
+
mockApi: () => mockApi,
|
|
25
26
|
mockRequests: () => mockRequests,
|
|
26
|
-
networkTools: () => networkTools
|
|
27
|
+
networkTools: () => networkTools,
|
|
28
|
+
strictNetwork: () => strictNetwork
|
|
27
29
|
});
|
|
28
30
|
module.exports = __toCommonJS(network_exports);
|
|
29
31
|
var installNetwork = () => {
|
|
@@ -32,3 +34,7 @@ var mock = () => ({ id: "", remove: () => false });
|
|
|
32
34
|
var mockRequests = () => () => {
|
|
33
35
|
};
|
|
34
36
|
var networkTools = () => ({});
|
|
37
|
+
var mockApi = () => () => {
|
|
38
|
+
};
|
|
39
|
+
var strictNetwork = () => () => {
|
|
40
|
+
};
|