@avasapp/agent-bridge 0.3.0 → 0.4.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 +149 -11
- package/dist/adapters/ably.cjs +22 -4
- package/dist/adapters/ably.d.cts +8 -1
- package/dist/adapters/expo-router.cjs +22 -1
- package/dist/adapters/expo-router.d.cts +5 -1
- package/dist/adapters/react-native-mmkv.cjs +1 -0
- package/dist/adapters/react-native-mmkv.d.cts +1 -1
- package/dist/adapters/react-native-webview.cjs +672 -0
- package/dist/adapters/react-native-webview.d.cts +79 -0
- package/dist/adapters/socket-io.cjs +3 -1
- package/dist/adapters/socket-io.d.cts +1 -1
- package/dist/adapters/tanstack-query.cjs +256 -17
- package/dist/adapters/tanstack-query.d.cts +1 -1
- package/dist/adapters/zustand.cjs +152 -12
- package/dist/adapters/zustand.d.cts +19 -3
- package/dist/{chunk-S2FYN2R5.js → chunk-AJD3XTNT.js} +223 -57
- package/dist/cli.js +817 -56
- package/dist/client/index.cjs +218 -56
- package/dist/client/index.d.ts +20 -2
- package/dist/client/index.js +1 -1
- package/dist/expo/index.d.cts +1 -1
- package/dist/network/index.cjs +254 -51
- package/dist/network/index.d.cts +9 -2
- package/dist/noop/index.cjs +3 -0
- package/dist/noop/react-native-webview.cjs +31 -0
- package/dist/realtime/index.cjs +2 -1
- package/dist/realtime/index.d.cts +3 -1
- package/dist/runtime/index.cjs +860 -100
- package/dist/runtime/index.d.cts +31 -5
- package/dist/{types-6kKQsLC1.d.cts → types-BmpykP1P.d.cts} +23 -1
- package/entries/react-native-webview.cjs +9 -0
- package/package.json +17 -6
- package/skills/agent-bridge/SKILL.md +196 -5
package/dist/network/index.cjs
CHANGED
|
@@ -29,6 +29,22 @@ __export(network_exports, {
|
|
|
29
29
|
});
|
|
30
30
|
module.exports = __toCommonJS(network_exports);
|
|
31
31
|
|
|
32
|
+
// src/shared/mock-signal.ts
|
|
33
|
+
var KEY = /* @__PURE__ */ Symbol.for("@avasapp/agent-bridge/mock-signal");
|
|
34
|
+
var listeners = () => {
|
|
35
|
+
const g = globalThis;
|
|
36
|
+
return g[KEY] ?? (g[KEY] = /* @__PURE__ */ new Set());
|
|
37
|
+
};
|
|
38
|
+
var signalRequestStarted = (requestId) => {
|
|
39
|
+
for (const l of [...listeners()]) l.started?.(requestId);
|
|
40
|
+
};
|
|
41
|
+
var signalMockAnswered = (mockId, requestId) => {
|
|
42
|
+
for (const l of [...listeners()]) l.answered?.(mockId, requestId);
|
|
43
|
+
};
|
|
44
|
+
var signalMocksRemoved = (ids) => {
|
|
45
|
+
for (const l of [...listeners()]) l.removed?.(ids);
|
|
46
|
+
};
|
|
47
|
+
|
|
32
48
|
// src/network/api.ts
|
|
33
49
|
var escape = (s) => s.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
34
50
|
var ROUTE = /^(?:([A-Za-z]+)\s+)?(\/\S*)$/;
|
|
@@ -96,14 +112,18 @@ function apiRoutes(baseUrl, routes, options = {}) {
|
|
|
96
112
|
|
|
97
113
|
// src/network/body.ts
|
|
98
114
|
var MAX_BODY = 2048;
|
|
99
|
-
|
|
115
|
+
var MAX_FULL_BODY = 1e6;
|
|
116
|
+
var FULL_BUDGET = 4e6;
|
|
117
|
+
function compactBody(text) {
|
|
100
118
|
if (text === void 0 || text === "") return void 0;
|
|
101
|
-
let out = text;
|
|
102
119
|
try {
|
|
103
|
-
|
|
120
|
+
return JSON.stringify(JSON.parse(text));
|
|
104
121
|
} catch {
|
|
122
|
+
return text;
|
|
105
123
|
}
|
|
106
|
-
|
|
124
|
+
}
|
|
125
|
+
function truncateBody(text) {
|
|
126
|
+
return text.length > MAX_BODY ? `${text.slice(0, MAX_BODY)}\u2026 (+${text.length - MAX_BODY} chars)` : text;
|
|
107
127
|
}
|
|
108
128
|
function bodyText(body) {
|
|
109
129
|
if (body === void 0 || body === null) return void 0;
|
|
@@ -129,10 +149,10 @@ function errorMessage(error) {
|
|
|
129
149
|
|
|
130
150
|
// src/network/state.ts
|
|
131
151
|
var LOG_SIZE = 100;
|
|
132
|
-
var
|
|
152
|
+
var KEY2 = /* @__PURE__ */ Symbol.for("@avasapp/agent-bridge/network");
|
|
133
153
|
function networkState() {
|
|
134
154
|
const g = globalThis;
|
|
135
|
-
g[
|
|
155
|
+
g[KEY2] ?? (g[KEY2] = {
|
|
136
156
|
installed: false,
|
|
137
157
|
log: [],
|
|
138
158
|
nextLogId: 1,
|
|
@@ -146,7 +166,7 @@ function networkState() {
|
|
|
146
166
|
strictAgent: null,
|
|
147
167
|
blocked: /* @__PURE__ */ new Map()
|
|
148
168
|
});
|
|
149
|
-
return g[
|
|
169
|
+
return g[KEY2];
|
|
150
170
|
}
|
|
151
171
|
var URL_PARTS = /^(?:[a-z][a-z0-9+.-]*:)?(?:\/\/([^/?#]*))?([^?#]*)/i;
|
|
152
172
|
var hostOf = (url) => URL_PARTS.exec(url)?.[1] ?? "";
|
|
@@ -170,29 +190,96 @@ function deriveDevHost() {
|
|
|
170
190
|
return null;
|
|
171
191
|
}
|
|
172
192
|
}
|
|
193
|
+
var FULL = /* @__PURE__ */ new WeakMap();
|
|
194
|
+
var fullOf = (entry) => {
|
|
195
|
+
let full = FULL.get(entry);
|
|
196
|
+
if (!full) FULL.set(entry, full = {});
|
|
197
|
+
return full;
|
|
198
|
+
};
|
|
199
|
+
function enforceBudget() {
|
|
200
|
+
const { log } = networkState();
|
|
201
|
+
const size = (f) => (f?.requestBody?.length ?? 0) + (f?.responseBody?.length ?? 0);
|
|
202
|
+
let total = log.reduce((sum, e) => sum + size(FULL.get(e)), 0);
|
|
203
|
+
for (const entry of log) {
|
|
204
|
+
if (total <= FULL_BUDGET) return;
|
|
205
|
+
const full = FULL.get(entry);
|
|
206
|
+
if (!full || !size(full)) continue;
|
|
207
|
+
total -= size(full);
|
|
208
|
+
delete full.requestBody;
|
|
209
|
+
if (full.responseBody !== void 0) {
|
|
210
|
+
delete full.responseBody;
|
|
211
|
+
full.response = "evicted";
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function keepRequestBody(entry, text) {
|
|
216
|
+
const body = compactBody(text);
|
|
217
|
+
if (body === void 0) return;
|
|
218
|
+
entry.requestBody = truncateBody(body);
|
|
219
|
+
if (text.length <= MAX_FULL_BODY) {
|
|
220
|
+
fullOf(entry).requestBody = text;
|
|
221
|
+
enforceBudget();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function setResponseBody(entry, text, contentType) {
|
|
225
|
+
const full = fullOf(entry);
|
|
226
|
+
if (contentType !== void 0) full.contentType = contentType;
|
|
227
|
+
if (text === void 0) {
|
|
228
|
+
full.response = "none";
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const body = compactBody(text);
|
|
232
|
+
if (body !== void 0) entry.responseBody = truncateBody(body);
|
|
233
|
+
if (text.length > MAX_FULL_BODY) {
|
|
234
|
+
full.response = "cut";
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
full.responseBody = text;
|
|
238
|
+
full.response = "kept";
|
|
239
|
+
enforceBudget();
|
|
240
|
+
}
|
|
241
|
+
function markResponse(entry, response) {
|
|
242
|
+
fullOf(entry).response = response;
|
|
243
|
+
}
|
|
173
244
|
function startEntry(state, fields) {
|
|
174
245
|
const entry = {
|
|
175
246
|
id: state.nextLogId++,
|
|
176
|
-
|
|
247
|
+
method: fields.method,
|
|
248
|
+
url: fields.url,
|
|
249
|
+
startedAt: Date.now(),
|
|
177
250
|
ms: 0,
|
|
178
|
-
pending: true
|
|
179
|
-
started: Date.now()
|
|
251
|
+
pending: true
|
|
180
252
|
};
|
|
181
|
-
|
|
253
|
+
keepRequestBody(entry, fields.requestBody);
|
|
182
254
|
state.log.push(entry);
|
|
255
|
+
signalRequestStarted(entry.id);
|
|
183
256
|
if (state.log.length > LOG_SIZE)
|
|
184
257
|
state.log.splice(0, state.log.length - LOG_SIZE);
|
|
185
258
|
return entry;
|
|
186
259
|
}
|
|
187
260
|
function finishEntry(entry, fields) {
|
|
188
|
-
entry.ms = Date.now() - entry.
|
|
261
|
+
entry.ms = Date.now() - entry.startedAt;
|
|
189
262
|
delete entry.pending;
|
|
190
|
-
|
|
263
|
+
const { responseBody, contentType, ...rest } = fields;
|
|
264
|
+
for (const [key, value] of Object.entries(rest))
|
|
191
265
|
if (value !== void 0) entry[key] = value;
|
|
266
|
+
if (responseBody !== void 0) setResponseBody(entry, responseBody, contentType);
|
|
267
|
+
}
|
|
268
|
+
function publicEntry(entry, full = false) {
|
|
269
|
+
const out = entry.pending ? { ...entry, ms: Date.now() - entry.startedAt } : { ...entry };
|
|
270
|
+
if (full) {
|
|
271
|
+
const { requestBody, responseBody } = FULL.get(entry) ?? {};
|
|
272
|
+
if (requestBody !== void 0) out.requestBody = requestBody;
|
|
273
|
+
if (responseBody !== void 0) out.responseBody = responseBody;
|
|
274
|
+
const cut = entry.responseBody !== void 0 && responseBody === void 0;
|
|
275
|
+
const cutRequest = entry.requestBody !== void 0 && requestBody === void 0;
|
|
276
|
+
if (cut || cutRequest) out.truncated = true;
|
|
277
|
+
}
|
|
278
|
+
return out;
|
|
192
279
|
}
|
|
193
|
-
function
|
|
194
|
-
const
|
|
195
|
-
return
|
|
280
|
+
function fullResponse(entry) {
|
|
281
|
+
const full = FULL.get(entry);
|
|
282
|
+
return { state: full?.response ?? "none", body: full?.responseBody, contentType: full?.contentType };
|
|
196
283
|
}
|
|
197
284
|
function urlTest(url) {
|
|
198
285
|
if (typeof url === "string") return (u) => u.includes(url);
|
|
@@ -213,6 +300,8 @@ function compileMatch(match) {
|
|
|
213
300
|
function addMock(state, source, match, response, options = {}) {
|
|
214
301
|
if (!response || typeof response !== "object" && typeof response !== "function")
|
|
215
302
|
throw new Error("A mock needs a response: { status?, json?, body?, headers? }, { offline: true } or a handler");
|
|
303
|
+
if (options.priority !== void 0 && !Number.isFinite(options.priority))
|
|
304
|
+
throw new Error("priority must be a finite number");
|
|
216
305
|
const seq = state.nextSeq++;
|
|
217
306
|
const id = options.id ?? `${source}-${seq}`;
|
|
218
307
|
removeMocks(state, (m) => m.id === id);
|
|
@@ -223,6 +312,7 @@ function addMock(state, source, match, response, options = {}) {
|
|
|
223
312
|
response,
|
|
224
313
|
times: options.times,
|
|
225
314
|
delayMs: options.delayMs ?? 0,
|
|
315
|
+
priority: options.priority ?? 0,
|
|
226
316
|
hits: 0,
|
|
227
317
|
seq,
|
|
228
318
|
test: compileMatch(match)
|
|
@@ -235,7 +325,21 @@ function removeMocks(state, which) {
|
|
|
235
325
|
state.mocks = state.mocks.filter((m) => !which(m));
|
|
236
326
|
return before - state.mocks.length;
|
|
237
327
|
}
|
|
238
|
-
var mockOrder = (a, b) => Number(b.source === "agent") - Number(a.source === "agent") || b.seq - a.seq;
|
|
328
|
+
var mockOrder = (a, b) => Number(b.source === "agent") - Number(a.source === "agent") || b.priority - a.priority || b.seq - a.seq;
|
|
329
|
+
var substringMatch = (match) => {
|
|
330
|
+
const spec = typeof match === "string" ? { url: match } : match;
|
|
331
|
+
return typeof spec.url === "string" ? { url: spec.url, method: spec.method?.toUpperCase() } : null;
|
|
332
|
+
};
|
|
333
|
+
function shadowedMocks(state, added) {
|
|
334
|
+
const next = substringMatch(added.match);
|
|
335
|
+
if (!next || typeof added.response === "function") return [];
|
|
336
|
+
return state.mocks.filter((old) => {
|
|
337
|
+
if (old === added || old.source !== "agent" || mockOrder(added, old) >= 0)
|
|
338
|
+
return false;
|
|
339
|
+
const prev = substringMatch(old.match);
|
|
340
|
+
return !!prev && prev.url.includes(next.url) && (!next.method || next.method === prev.method);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
239
343
|
function matchingMocks(state, method, url) {
|
|
240
344
|
return state.mocks.filter((m) => m.test(method, url)).sort(mockOrder);
|
|
241
345
|
}
|
|
@@ -266,6 +370,7 @@ function mockRequest(method, url, headers, body) {
|
|
|
266
370
|
}
|
|
267
371
|
};
|
|
268
372
|
}
|
|
373
|
+
var contentTypeOf = (headers) => Object.entries(headers).find(([k]) => k.toLowerCase() === "content-type")?.[1];
|
|
269
374
|
function responseParts(response) {
|
|
270
375
|
const r = response;
|
|
271
376
|
const status = r.status ?? 200;
|
|
@@ -378,7 +483,7 @@ function patchFetch(state) {
|
|
|
378
483
|
const entry = startEntry(state, {
|
|
379
484
|
method,
|
|
380
485
|
url,
|
|
381
|
-
requestBody
|
|
486
|
+
requestBody
|
|
382
487
|
});
|
|
383
488
|
const real = () => {
|
|
384
489
|
let promise;
|
|
@@ -391,14 +496,11 @@ function patchFetch(state) {
|
|
|
391
496
|
return promise.then(
|
|
392
497
|
(res) => {
|
|
393
498
|
finishEntry(entry, { status: res.status });
|
|
394
|
-
const type = res.headers?.get?.("content-type");
|
|
499
|
+
const type = res.headers?.get?.("content-type") ?? void 0;
|
|
395
500
|
if (isTextual(type) && typeof res.clone === "function") {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}).catch(() => {
|
|
400
|
-
});
|
|
401
|
-
}
|
|
501
|
+
markResponse(entry, "pending");
|
|
502
|
+
res.clone().text().then((text) => setResponseBody(entry, text, type)).catch(() => markResponse(entry, "none"));
|
|
503
|
+
} else markResponse(entry, "none");
|
|
402
504
|
return res;
|
|
403
505
|
},
|
|
404
506
|
(error) => {
|
|
@@ -415,8 +517,8 @@ function patchFetch(state) {
|
|
|
415
517
|
finishEntry(entry, { error: `${OFFLINE_MESSAGE} (strict network)` });
|
|
416
518
|
return Promise.reject(new TypeError(OFFLINE_MESSAGE));
|
|
417
519
|
}
|
|
418
|
-
const { status, text } = responseParts(blocked);
|
|
419
|
-
finishEntry(entry, { status, responseBody:
|
|
520
|
+
const { status, text, headers } = responseParts(blocked);
|
|
521
|
+
finishEntry(entry, { status, responseBody: text, contentType: contentTypeOf(headers) });
|
|
420
522
|
return Promise.resolve(mockedResponse(blocked));
|
|
421
523
|
};
|
|
422
524
|
const candidates = matchingMocks(state, method, url);
|
|
@@ -441,6 +543,7 @@ function patchFetch(state) {
|
|
|
441
543
|
return answered.then(async (hit) => {
|
|
442
544
|
if (!hit) return passOn();
|
|
443
545
|
entry.mocked = true;
|
|
546
|
+
if (hit.mock.source === "agent") signalMockAnswered(hit.mock.id, entry.id);
|
|
444
547
|
await sleep(hit.mock.delayMs);
|
|
445
548
|
if (init?.signal?.aborted) {
|
|
446
549
|
finishEntry(entry, { error: "aborted" });
|
|
@@ -450,8 +553,8 @@ function patchFetch(state) {
|
|
|
450
553
|
finishEntry(entry, { error: `${OFFLINE_MESSAGE} (mocked offline)` });
|
|
451
554
|
throw new TypeError(OFFLINE_MESSAGE);
|
|
452
555
|
}
|
|
453
|
-
const { status, text } = responseParts(hit.response);
|
|
454
|
-
finishEntry(entry, { status, responseBody:
|
|
556
|
+
const { status, text, headers } = responseParts(hit.response);
|
|
557
|
+
finishEntry(entry, { status, responseBody: text, contentType: contentTypeOf(headers) });
|
|
455
558
|
return mockedResponse(hit.response);
|
|
456
559
|
});
|
|
457
560
|
};
|
|
@@ -478,11 +581,14 @@ function track(xhr, entry) {
|
|
|
478
581
|
xhr.addEventListener("timeout", () => error = "timed out");
|
|
479
582
|
xhr.addEventListener("abort", () => error = "aborted");
|
|
480
583
|
xhr.addEventListener("loadend", () => {
|
|
584
|
+
const text = error ? void 0 : responseTextOf(xhr);
|
|
481
585
|
finishEntry(entry, {
|
|
482
586
|
status: error ? void 0 : xhr.status,
|
|
483
587
|
error,
|
|
484
|
-
responseBody:
|
|
588
|
+
responseBody: text,
|
|
589
|
+
contentType: xhr.getResponseHeader?.("content-type") ?? void 0
|
|
485
590
|
});
|
|
591
|
+
if (!error && text === void 0) markResponse(entry, "none");
|
|
486
592
|
});
|
|
487
593
|
}
|
|
488
594
|
var fakeRequestId = 0;
|
|
@@ -569,7 +675,7 @@ function patchXhr(state) {
|
|
|
569
675
|
const entry = startEntry(state, {
|
|
570
676
|
method,
|
|
571
677
|
url,
|
|
572
|
-
requestBody:
|
|
678
|
+
requestBody: text
|
|
573
679
|
});
|
|
574
680
|
const passOn = () => {
|
|
575
681
|
const blocked = blockedResponse(state, method, url);
|
|
@@ -577,11 +683,11 @@ function patchXhr(state) {
|
|
|
577
683
|
track(this, entry);
|
|
578
684
|
return send.call(this, body);
|
|
579
685
|
}
|
|
580
|
-
const { status, text: out } = responseParts(blocked);
|
|
686
|
+
const { status, text: out, headers } = responseParts(blocked);
|
|
581
687
|
entry.blocked = true;
|
|
582
688
|
finishEntry(
|
|
583
689
|
entry,
|
|
584
|
-
isOffline(blocked) ? { error: `${OFFLINE_MESSAGE} (strict network)` } : { status, responseBody:
|
|
690
|
+
isOffline(blocked) ? { error: `${OFFLINE_MESSAGE} (strict network)` } : { status, responseBody: out, contentType: contentTypeOf(headers) }
|
|
585
691
|
);
|
|
586
692
|
void Promise.resolve().then(() => {
|
|
587
693
|
if (!meta.aborted) respond(this, blocked, url);
|
|
@@ -594,16 +700,17 @@ function patchXhr(state) {
|
|
|
594
700
|
async (hit) => {
|
|
595
701
|
if (!hit) return passOn();
|
|
596
702
|
entry.mocked = true;
|
|
703
|
+
if (hit.mock.source === "agent") signalMockAnswered(hit.mock.id, entry.id);
|
|
597
704
|
await sleep(hit.mock.delayMs);
|
|
598
705
|
if (meta.aborted) {
|
|
599
706
|
finishEntry(entry, { error: "aborted" });
|
|
600
707
|
return;
|
|
601
708
|
}
|
|
602
709
|
const offline = isOffline(hit.response);
|
|
603
|
-
const { status, text: out } = responseParts(hit.response);
|
|
710
|
+
const { status, text: out, headers } = responseParts(hit.response);
|
|
604
711
|
finishEntry(
|
|
605
712
|
entry,
|
|
606
|
-
offline ? { error: `${OFFLINE_MESSAGE} (mocked offline)` } : { status, responseBody:
|
|
713
|
+
offline ? { error: `${OFFLINE_MESSAGE} (mocked offline)` } : { status, responseBody: out, contentType: contentTypeOf(headers) }
|
|
607
714
|
);
|
|
608
715
|
respond(this, hit.response, url);
|
|
609
716
|
},
|
|
@@ -652,48 +759,141 @@ function strictNetwork(options = {}) {
|
|
|
652
759
|
state.strictApp = state.strictApp.filter((r) => r !== rule);
|
|
653
760
|
};
|
|
654
761
|
}
|
|
655
|
-
var
|
|
762
|
+
var escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
763
|
+
var isPlain = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
764
|
+
function deepMerge(base, patch) {
|
|
765
|
+
if (!isPlain(base) || !isPlain(patch)) return patch;
|
|
766
|
+
const out = { ...base };
|
|
767
|
+
for (const [key, value] of Object.entries(patch)) out[key] = deepMerge(base[key], value);
|
|
768
|
+
return out;
|
|
769
|
+
}
|
|
770
|
+
var describeMatch = (match) => {
|
|
771
|
+
if (match instanceof RegExp) return String(match);
|
|
772
|
+
if (typeof match === "object" && match.url instanceof RegExp)
|
|
773
|
+
return { ...match, url: String(match.url) };
|
|
774
|
+
return match;
|
|
775
|
+
};
|
|
776
|
+
function describeResponse(response, full) {
|
|
777
|
+
if (typeof response === "function") return { response: "[handler]" };
|
|
778
|
+
if (full || "offline" in response) return { response };
|
|
779
|
+
const out = { ...response };
|
|
780
|
+
let truncated = false;
|
|
781
|
+
if (response.json !== void 0) {
|
|
782
|
+
const text = JSON.stringify(response.json) ?? "";
|
|
783
|
+
const cut = truncateBody(text);
|
|
784
|
+
if (cut !== text) {
|
|
785
|
+
out.json = cut;
|
|
786
|
+
truncated = true;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
if (typeof response.body === "string") {
|
|
790
|
+
const cut = truncateBody(response.body);
|
|
791
|
+
if (cut !== response.body) {
|
|
792
|
+
out.body = cut;
|
|
793
|
+
truncated = true;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return truncated ? { response: out, truncated } : { response };
|
|
797
|
+
}
|
|
798
|
+
var describeMock = (m, full) => ({
|
|
656
799
|
id: m.id,
|
|
657
800
|
source: m.source,
|
|
658
|
-
match:
|
|
659
|
-
|
|
801
|
+
match: describeMatch(m.match),
|
|
802
|
+
...describeResponse(m.response, full),
|
|
660
803
|
times: m.times,
|
|
661
804
|
hits: m.hits,
|
|
662
|
-
delayMs: m.delayMs || void 0
|
|
805
|
+
delayMs: m.delayMs || void 0,
|
|
806
|
+
priority: m.priority || void 0
|
|
663
807
|
});
|
|
664
808
|
function networkTools(options = {}) {
|
|
665
809
|
installNetwork();
|
|
666
810
|
const state = networkState();
|
|
667
811
|
if (options.skip) state.skip = options.skip;
|
|
668
812
|
const isAgent = (m) => m.source === "agent";
|
|
813
|
+
const logged = (id) => {
|
|
814
|
+
const entry = state.log.find((e) => e.id === id);
|
|
815
|
+
if (!entry) throw new Error(`No logged request with id ${id}; it may have left the log`);
|
|
816
|
+
return entry;
|
|
817
|
+
};
|
|
818
|
+
const addAgentMock = (match, response, { times, delayMs, priority }) => {
|
|
819
|
+
const added = addMock(state, "agent", match, response, { times, delayMs, priority });
|
|
820
|
+
const shadows = shadowedMocks(state, added).map((m) => m.id);
|
|
821
|
+
return shadows.length ? { id: added.id, shadows } : { id: added.id };
|
|
822
|
+
};
|
|
669
823
|
return {
|
|
670
824
|
"net.log": {
|
|
671
|
-
description:
|
|
825
|
+
description: 'Recent requests, newest first: method, url, startedAt (epoch ms), status, ms, bodies (~2 KB, cut with "\u2026 (+N chars)"), error, mocked. Filter by url substring, method, since (epoch ms: startedAt >= since) or sinceId (id > sinceId); full: true returns whole bodies (truncated: true when one is too large or gone); clear: true empties the log after reading. net.entry gets one request whole.',
|
|
672
826
|
run: (filter = {}) => {
|
|
673
827
|
const method = filter.method?.toUpperCase();
|
|
828
|
+
if (filter.since !== void 0 && !(filter.since >= 1e11))
|
|
829
|
+
throw new Error("since is an epoch time in ms (e.g. Date.now()); use sinceId for log ids");
|
|
674
830
|
const entries = state.log.filter(
|
|
675
|
-
(e) => (!filter.url || e.url.includes(filter.url)) && (!method || e.method === method)
|
|
676
|
-
).reverse().slice(0, filter.limit ?? 20).map(publicEntry);
|
|
831
|
+
(e) => (!filter.url || e.url.includes(filter.url)) && (!method || e.method === method) && (filter.since === void 0 || e.startedAt >= filter.since) && (filter.sinceId === void 0 || e.id > filter.sinceId)
|
|
832
|
+
).reverse().slice(0, filter.limit ?? 20).map((e) => publicEntry(e, filter.full));
|
|
677
833
|
if (filter.clear) state.log.length = 0;
|
|
678
834
|
return entries;
|
|
679
835
|
}
|
|
680
836
|
},
|
|
837
|
+
"net.entry": {
|
|
838
|
+
description: "One logged request by id, with its request and response bodies whole (not cut at ~2 KB). Ids come from net.log.",
|
|
839
|
+
run: (id) => publicEntry(logged(id), true)
|
|
840
|
+
},
|
|
681
841
|
"net.mock": {
|
|
682
|
-
description: 'Answer matching requests with a canned response until unmocked. match: "/path" or { url: string | { regex }, method? }. response: { status?, json?, body?, headers? } or { offline: true }. options: { times?, delayMs? }.',
|
|
842
|
+
description: 'Answer matching requests with a canned response until unmocked. match: "/path" or { url: string | { regex }, method? }. response: { status?, json?, body?, headers? } or { offline: true }. options: { times?, delayMs?, priority? }. The newest mock is tried first, so a later broad mock shadows an earlier specific one; give the specific one a higher priority (default 0, ties newest first; negative makes a fallback); agent mocks still come before the app mocks. The result lists `shadows` when the new mock will be tried before an agent mock it also matches.',
|
|
683
843
|
run: (match, response, mockOptions = {}) => {
|
|
684
844
|
if (typeof response !== "object" || response === null)
|
|
685
845
|
throw new Error("response must be an object, e.g. { status: 500 }");
|
|
686
|
-
|
|
687
|
-
|
|
846
|
+
return addAgentMock(match, response, mockOptions);
|
|
847
|
+
}
|
|
848
|
+
},
|
|
849
|
+
"net.mockFromLog": {
|
|
850
|
+
description: "Mock the same method and URL as a logged request (id from net.log) with its status and whole response body, optionally changed by patch: JSON objects merge deeply, other values replace. [id, patch?, options?] options as net.mock. Returns { id, match, response }.",
|
|
851
|
+
run: (id, patch, mockOptions = {}) => {
|
|
852
|
+
const entry = logged(id);
|
|
853
|
+
if (entry.status === void 0)
|
|
854
|
+
throw new Error(`Request ${id} has no response to copy (${entry.error ?? "still pending"})`);
|
|
855
|
+
const { state: kept, body, contentType } = fullResponse(entry);
|
|
856
|
+
if (kept !== "kept" || body === void 0)
|
|
857
|
+
throw new Error(
|
|
858
|
+
`Request ${id}'s response body can't be copied: ${{
|
|
859
|
+
pending: "it is still being read, try again",
|
|
860
|
+
none: "it was not captured (binary or unreadable)",
|
|
861
|
+
cut: "it is too large",
|
|
862
|
+
evicted: "it was dropped to save memory",
|
|
863
|
+
kept: "it is missing"
|
|
864
|
+
}[kept]}`
|
|
865
|
+
);
|
|
866
|
+
const response = { status: entry.status };
|
|
867
|
+
if (patch !== void 0) {
|
|
868
|
+
let json;
|
|
869
|
+
try {
|
|
870
|
+
json = JSON.parse(body);
|
|
871
|
+
} catch {
|
|
872
|
+
throw new Error(`Request ${id}'s response is not JSON, so it can't take a patch`);
|
|
873
|
+
}
|
|
874
|
+
response.json = deepMerge(json, patch);
|
|
875
|
+
} else {
|
|
876
|
+
response.body = body;
|
|
877
|
+
if (contentType) response.headers = { "content-type": contentType };
|
|
878
|
+
}
|
|
879
|
+
const match = { url: { regex: `^${escapeRegex(entry.url)}$` }, method: entry.method };
|
|
880
|
+
return { ...addAgentMock(match, response, mockOptions), match: { url: entry.url, method: entry.method }, response };
|
|
688
881
|
}
|
|
689
882
|
},
|
|
690
883
|
"net.mocks": {
|
|
691
|
-
description:
|
|
692
|
-
run: () => [...state.mocks].sort(mockOrder).map(describeMock)
|
|
884
|
+
description: 'Active mocks, agent and app, in the order they are tried. Response json and body over ~2 KB are cut like net.log (json shown as its cut JSON text, "\u2026 (+N chars)"), with truncated: true; { full: true } returns them whole.',
|
|
885
|
+
run: (options2 = {}) => [...state.mocks].sort(mockOrder).map((m) => describeMock(m, options2.full))
|
|
693
886
|
},
|
|
694
887
|
"net.unmock": {
|
|
695
|
-
description: "Remove one agent mock by id, or every agent mock when no id. Returns how many.",
|
|
696
|
-
run: (id) =>
|
|
888
|
+
description: "Remove one agent mock by id, or every agent mock when no id. Returns how many. With the query adapter, queries that fetched while the mock answered are reset, so its fake data can't outlive it.",
|
|
889
|
+
run: (id) => {
|
|
890
|
+
const removed = state.mocks.filter(
|
|
891
|
+
(m) => isAgent(m) && (id === void 0 || m.id === id)
|
|
892
|
+
);
|
|
893
|
+
const count = removeMocks(state, (m) => removed.includes(m));
|
|
894
|
+
signalMocksRemoved(id === void 0 ? void 0 : [id]);
|
|
895
|
+
return count;
|
|
896
|
+
}
|
|
697
897
|
},
|
|
698
898
|
"net.strict": {
|
|
699
899
|
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 }.",
|
|
@@ -723,10 +923,13 @@ function networkTools(options = {}) {
|
|
|
723
923
|
}
|
|
724
924
|
},
|
|
725
925
|
"net.restore": {
|
|
726
|
-
|
|
926
|
+
pending: () => state.strictAgent !== null || state.mocks.some(isAgent),
|
|
927
|
+
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. With the query adapter, queries that fetched while an agent mock answered are reset (query.restore reports how many).",
|
|
727
928
|
run: () => {
|
|
728
929
|
state.strictAgent = null;
|
|
729
|
-
|
|
930
|
+
const count = removeMocks(state, isAgent);
|
|
931
|
+
signalMocksRemoved();
|
|
932
|
+
return count;
|
|
730
933
|
}
|
|
731
934
|
}
|
|
732
935
|
};
|
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-BmpykP1P.cjs';
|
|
2
2
|
|
|
3
3
|
/** Which requests a mock answers. A bare string is a URL substring. */
|
|
4
4
|
type MockMatch = string | {
|
|
@@ -38,6 +38,8 @@ type MockOptions = {
|
|
|
38
38
|
times?: number;
|
|
39
39
|
/** Wait before answering. */
|
|
40
40
|
delayMs?: number;
|
|
41
|
+
/** Higher is tried first among mocks from the same source (agent mocks still come before the app's). Default 0; ties go to the newest. Negative makes a fallback. */
|
|
42
|
+
priority?: number;
|
|
41
43
|
/** Registering again with the same id replaces the mock (handy with Fast Refresh). */
|
|
42
44
|
id?: string;
|
|
43
45
|
};
|
|
@@ -50,11 +52,15 @@ type LogEntry = {
|
|
|
50
52
|
id: number;
|
|
51
53
|
method: string;
|
|
52
54
|
url: string;
|
|
55
|
+
/** When the request started, epoch ms (the app's clock). */
|
|
56
|
+
startedAt: number;
|
|
53
57
|
status?: number;
|
|
54
58
|
ms: number;
|
|
55
59
|
pending?: boolean;
|
|
56
60
|
requestBody?: string;
|
|
57
61
|
responseBody?: string;
|
|
62
|
+
/** From net.entry: a body shown is only a preview because the whole one was too large or is gone. */
|
|
63
|
+
truncated?: boolean;
|
|
58
64
|
error?: string;
|
|
59
65
|
mocked?: boolean;
|
|
60
66
|
/** Failed by strict mode: no mock answered it. */
|
|
@@ -94,7 +100,8 @@ type MockApiOptions = Omit<MockOptions, 'id'> & {
|
|
|
94
100
|
declare function installNetwork(): void;
|
|
95
101
|
/**
|
|
96
102
|
* Answers matching requests from the app's own code, e.g. a fake backend.
|
|
97
|
-
* Agent mocks (from `net.mock`)
|
|
103
|
+
* Agent mocks (from `net.mock`) are tried first, whatever their priority;
|
|
104
|
+
* `net.restore` keeps these.
|
|
98
105
|
*/
|
|
99
106
|
declare function mock(match: MockMatch, response: MockResponse | MockHandler, options?: MockOptions): {
|
|
100
107
|
id: string;
|
package/dist/noop/index.cjs
CHANGED
|
@@ -22,6 +22,7 @@ var noop_exports = {};
|
|
|
22
22
|
__export(noop_exports, {
|
|
23
23
|
cdpTransport: () => cdpTransport,
|
|
24
24
|
createGate: () => createGate,
|
|
25
|
+
onRestore: () => onRestore,
|
|
25
26
|
settle: () => settle,
|
|
26
27
|
startAgentBridge: () => startAgentBridge,
|
|
27
28
|
useAgentBridge: () => useAgentBridge
|
|
@@ -35,6 +36,8 @@ var useAgentBridge = () => {
|
|
|
35
36
|
};
|
|
36
37
|
var cdpTransport = inert;
|
|
37
38
|
var settle = () => Promise.resolve({ commits: 0, ms: 0 });
|
|
39
|
+
var onRestore = () => {
|
|
40
|
+
};
|
|
38
41
|
var createGate = (name) => ({
|
|
39
42
|
name,
|
|
40
43
|
closed: false,
|
|
@@ -0,0 +1,31 @@
|
|
|
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/react-native-webview.ts
|
|
21
|
+
var react_native_webview_exports = {};
|
|
22
|
+
__export(react_native_webview_exports, {
|
|
23
|
+
useWebViewTools: () => useWebViewTools,
|
|
24
|
+
webviewTools: () => webviewTools
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(react_native_webview_exports);
|
|
27
|
+
var useWebViewTools = () => ({
|
|
28
|
+
props: {},
|
|
29
|
+
wrap: (onMessage) => onMessage
|
|
30
|
+
});
|
|
31
|
+
var webviewTools = () => ({});
|
package/dist/realtime/index.cjs
CHANGED
|
@@ -221,7 +221,7 @@ function createRealtimeTap(options = {}) {
|
|
|
221
221
|
}
|
|
222
222
|
},
|
|
223
223
|
[`${ns}.emit`]: {
|
|
224
|
-
description: "Deliver a fake message to every listener on a channel: [channel, message]. Runs the app's own handlers. Returns { id, delivered }.",
|
|
224
|
+
description: "Deliver a fake message to every listener on a channel: [channel, message]. Runs the app's own handlers. Returns { id, delivered }." + (options.emitUsage ? ` ${options.emitUsage}` : ""),
|
|
225
225
|
run: (channel, ...values) => {
|
|
226
226
|
const entries = channels.get(channel);
|
|
227
227
|
if (!entries?.size)
|
|
@@ -257,6 +257,7 @@ function createRealtimeTap(options = {}) {
|
|
|
257
257
|
run: (channel) => setMute(channel, null)
|
|
258
258
|
},
|
|
259
259
|
[`${ns}.restore`]: {
|
|
260
|
+
pending: () => mutes.size > 0 || faked !== null,
|
|
260
261
|
description: "Undo the agent: unmute every channel and go back to the real connection state.",
|
|
261
262
|
run: () => {
|
|
262
263
|
const unmuted = [...mutes.keys()];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { T as Tools } from '../types-
|
|
1
|
+
import { T as Tools } from '../types-BmpykP1P.cjs';
|
|
2
2
|
|
|
3
3
|
/** How a message shows up in `realtime.log`. */
|
|
4
4
|
type MessageSummary = {
|
|
@@ -34,6 +34,8 @@ type RealtimeTapOptions<M> = {
|
|
|
34
34
|
channel: string;
|
|
35
35
|
id: string;
|
|
36
36
|
}) => M;
|
|
37
|
+
/** What to pass to `realtime.emit` for this library, appended to its description. */
|
|
38
|
+
emitUsage?: string;
|
|
37
39
|
/** Extra fields for `realtime.channels`, e.g. the library's channel state. */
|
|
38
40
|
channelInfo?: (channel: string) => Record<string, unknown>;
|
|
39
41
|
/** Adds `realtime.connection`, to fake connection states. */
|