@swmansion/argent 0.29.1-next.20 → 0.29.1-next.21

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.
@@ -11384,11 +11384,11 @@ var init_nl = __esm({
11384
11384
  case "too_small": {
11385
11385
  const adj = issue2.inclusive ? ">=" : ">";
11386
11386
  const sizing = getSizing(issue2.origin);
11387
- const shortName = issue2.origin === "date" ? "vroeg" : issue2.origin === "string" ? "kort" : "klein";
11387
+ const shortName2 = issue2.origin === "date" ? "vroeg" : issue2.origin === "string" ? "kort" : "klein";
11388
11388
  if (sizing) {
11389
- return `Te ${shortName}: verwacht dat ${issue2.origin} ${adj}${issue2.minimum.toString()} ${sizing.unit} ${sizing.verb}`;
11389
+ return `Te ${shortName2}: verwacht dat ${issue2.origin} ${adj}${issue2.minimum.toString()} ${sizing.unit} ${sizing.verb}`;
11390
11390
  }
11391
- return `Te ${shortName}: verwacht dat ${issue2.origin} ${adj}${issue2.minimum.toString()} is`;
11391
+ return `Te ${shortName2}: verwacht dat ${issue2.origin} ${adj}${issue2.minimum.toString()} is`;
11392
11392
  }
11393
11393
  case "invalid_format": {
11394
11394
  const _issue = issue2;
@@ -100666,7 +100666,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
100666
100666
  var SESSION_ID = (0, import_node_crypto3.randomUUID)();
100667
100667
  function readCliVersion() {
100668
100668
  if (true) {
100669
- return "0.29.1-next.20";
100669
+ return "0.29.1-next.21";
100670
100670
  }
100671
100671
  return "0.0.0";
100672
100672
  }
@@ -111742,10 +111742,10 @@ var PublishError = class _PublishError {
111742
111742
  requestId;
111743
111743
  errorCode;
111744
111744
  reasonPhrase;
111745
- constructor({ requestId, errorCode, reasonPhrase }) {
111745
+ constructor({ requestId, errorCode, reasonPhrase: reasonPhrase2 }) {
111746
111746
  this.requestId = requestId;
111747
111747
  this.errorCode = errorCode;
111748
- this.reasonPhrase = reasonPhrase;
111748
+ this.reasonPhrase = reasonPhrase2;
111749
111749
  }
111750
111750
  async #encode(w) {
111751
111751
  await w.u62(this.requestId);
@@ -111761,8 +111761,8 @@ var PublishError = class _PublishError {
111761
111761
  static async #decode(r) {
111762
111762
  const requestId = await r.u62();
111763
111763
  const errorCode = Number(await r.u62());
111764
- const reasonPhrase = await r.string();
111765
- return new _PublishError({ requestId, errorCode, reasonPhrase });
111764
+ const reasonPhrase2 = await r.string();
111765
+ return new _PublishError({ requestId, errorCode, reasonPhrase: reasonPhrase2 });
111766
111766
  }
111767
111767
  };
111768
111768
  var PublishDone = class _PublishDone {
@@ -111770,10 +111770,10 @@ var PublishDone = class _PublishDone {
111770
111770
  requestId;
111771
111771
  statusCode;
111772
111772
  reasonPhrase;
111773
- constructor({ requestId, statusCode, reasonPhrase }) {
111773
+ constructor({ requestId, statusCode, reasonPhrase: reasonPhrase2 }) {
111774
111774
  this.requestId = requestId;
111775
111775
  this.statusCode = statusCode;
111776
- this.reasonPhrase = reasonPhrase;
111776
+ this.reasonPhrase = reasonPhrase2;
111777
111777
  }
111778
111778
  async #encode(w, version3) {
111779
111779
  if (version3 === Version.DRAFT_14 || version3 === Version.DRAFT_15 || version3 === Version.DRAFT_16) {
@@ -111795,8 +111795,8 @@ var PublishDone = class _PublishDone {
111795
111795
  const requestId = version3 === Version.DRAFT_14 || version3 === Version.DRAFT_15 || version3 === Version.DRAFT_16 ? await r.u62() : void 0;
111796
111796
  const statusCode = Number(await r.u62());
111797
111797
  await r.u62();
111798
- const reasonPhrase = await r.string();
111799
- return new _PublishDone({ requestId, statusCode, reasonPhrase });
111798
+ const reasonPhrase2 = await r.string();
111799
+ return new _PublishDone({ requestId, statusCode, reasonPhrase: reasonPhrase2 });
111800
111800
  }
111801
111801
  };
111802
111802
 
@@ -111876,10 +111876,10 @@ var PublishNamespaceError = class _PublishNamespaceError {
111876
111876
  requestId;
111877
111877
  errorCode;
111878
111878
  reasonPhrase;
111879
- constructor({ requestId, errorCode, reasonPhrase }) {
111879
+ constructor({ requestId, errorCode, reasonPhrase: reasonPhrase2 }) {
111880
111880
  this.requestId = requestId;
111881
111881
  this.errorCode = errorCode;
111882
- this.reasonPhrase = reasonPhrase;
111882
+ this.reasonPhrase = reasonPhrase2;
111883
111883
  }
111884
111884
  async #encode(w) {
111885
111885
  await w.u62(this.requestId);
@@ -111895,8 +111895,8 @@ var PublishNamespaceError = class _PublishNamespaceError {
111895
111895
  static async #decode(r) {
111896
111896
  const requestId = await r.u62();
111897
111897
  const errorCode = Number(await r.u62());
111898
- const reasonPhrase = await r.string();
111899
- return new _PublishNamespaceError({ requestId, errorCode, reasonPhrase });
111898
+ const reasonPhrase2 = await r.string();
111899
+ return new _PublishNamespaceError({ requestId, errorCode, reasonPhrase: reasonPhrase2 });
111900
111900
  }
111901
111901
  };
111902
111902
  var PublishNamespaceDone = class _PublishNamespaceDone {
@@ -112249,10 +112249,10 @@ var RequestError = class _RequestError {
112249
112249
  errorCode;
112250
112250
  reasonPhrase;
112251
112251
  retryInterval;
112252
- constructor({ requestId, errorCode, reasonPhrase, retryInterval = 0n }) {
112252
+ constructor({ requestId, errorCode, reasonPhrase: reasonPhrase2, retryInterval = 0n }) {
112253
112253
  this.requestId = requestId;
112254
112254
  this.errorCode = errorCode;
112255
- this.reasonPhrase = reasonPhrase;
112255
+ this.reasonPhrase = reasonPhrase2;
112256
112256
  this.retryInterval = retryInterval;
112257
112257
  }
112258
112258
  async #encode(w, version3) {
@@ -112274,8 +112274,8 @@ var RequestError = class _RequestError {
112274
112274
  const requestId = version3 === Version.DRAFT_14 || version3 === Version.DRAFT_15 || version3 === Version.DRAFT_16 ? await r.u62() : void 0;
112275
112275
  const errorCode = Number(await r.u62());
112276
112276
  const retryInterval = version3 === Version.DRAFT_14 || version3 === Version.DRAFT_15 ? 0n : await r.u62();
112277
- const reasonPhrase = await r.string();
112278
- return new _RequestError({ requestId, errorCode, reasonPhrase, retryInterval });
112277
+ const reasonPhrase2 = await r.string();
112278
+ return new _RequestError({ requestId, errorCode, reasonPhrase: reasonPhrase2, retryInterval });
112279
112279
  }
112280
112280
  static async decode(r, version3) {
112281
112281
  return decode7(r, (rd) => _RequestError.#decode(rd, version3));
@@ -112488,10 +112488,10 @@ var SubscribeError = class _SubscribeError {
112488
112488
  requestId;
112489
112489
  errorCode;
112490
112490
  reasonPhrase;
112491
- constructor({ requestId, errorCode, reasonPhrase }) {
112491
+ constructor({ requestId, errorCode, reasonPhrase: reasonPhrase2 }) {
112492
112492
  this.requestId = requestId;
112493
112493
  this.errorCode = errorCode;
112494
- this.reasonPhrase = reasonPhrase;
112494
+ this.reasonPhrase = reasonPhrase2;
112495
112495
  }
112496
112496
  async #encode(w) {
112497
112497
  await w.u62(this.requestId);
@@ -112507,8 +112507,8 @@ var SubscribeError = class _SubscribeError {
112507
112507
  static async #decode(r) {
112508
112508
  const requestId = await r.u62();
112509
112509
  const errorCode = Number(await r.u62());
112510
- const reasonPhrase = await r.string();
112511
- return new _SubscribeError({ requestId, errorCode, reasonPhrase });
112510
+ const reasonPhrase2 = await r.string();
112511
+ return new _SubscribeError({ requestId, errorCode, reasonPhrase: reasonPhrase2 });
112512
112512
  }
112513
112513
  };
112514
112514
  var SubscribeUpdate = class _SubscribeUpdate {
@@ -114177,16 +114177,16 @@ var Subscriber2 = class {
114177
114177
  console.debug(`subscribe written: id=${requestId} broadcast=${broadcast} track=${request3.name}`);
114178
114178
  const respTypeId = await state3.stream.reader.u53();
114179
114179
  if (respTypeId !== SubscribeOk.id) {
114180
- let reasonPhrase = "unknown error";
114180
+ let reasonPhrase2 = "unknown error";
114181
114181
  try {
114182
114182
  if (respTypeId === RequestError.id) {
114183
114183
  const err = version3 === Version.DRAFT_14 ? await SubscribeError.decode(state3.stream.reader, version3) : await RequestError.decode(state3.stream.reader, version3);
114184
- reasonPhrase = `code=${err.errorCode} reason=${err.reasonPhrase}`;
114184
+ reasonPhrase2 = `code=${err.errorCode} reason=${err.reasonPhrase}`;
114185
114185
  }
114186
114186
  } catch {
114187
114187
  }
114188
114188
  state3.rejected = true;
114189
- throw new Error(`SUBSCRIBE error: ${reasonPhrase}`);
114189
+ throw new Error(`SUBSCRIBE error: ${reasonPhrase2}`);
114190
114190
  }
114191
114191
  const ok = await SubscribeOk.decode(state3.stream.reader, version3);
114192
114192
  try {
@@ -133157,8 +133157,8 @@ var nativeNetworkLogsTool = {
133157
133157
  },
133158
133158
  capability: { apple: { simulator: true }, appleRemote: { simulator: true } },
133159
133159
  description: `Retrieve network requests captured at the native NSURLProtocol level.
133160
- Unlike the JS-level network inspector (view-network-logs), this captures ALL network traffic from the app including native modules, Swift/Objective-C networking, and background transfers that bypass JS fetch.
133161
- Use when you need to inspect native-level HTTP traffic that is invisible to JS fetch interception.
133160
+ Unlike the JS-level network inspector (view-network-logs), this captures ALL network traffic from the app including native modules, Swift/Objective-C networking, and background transfers that never pass through JS XMLHttpRequest or fetch.
133161
+ Use when you need to inspect native-level HTTP traffic that is invisible to the JS-level capture.
133162
133162
  Returns { status, count, events } where each event contains URL, method, status code, headers, and timing.
133163
133163
  If status is restart_required: follow the message (usually restart-app), then retry. If status is service_stale: the app is already injected, so restarting it cannot help. Restart the tool-server (\`argent server stop && argent server start --detach\`) and retry. If the same status comes back after that restart, stop restarting: follow the message, which names the terminal fallback. If status is connect_pending: the app is injected and still connecting. Do not restart it, wait a few seconds and retry. If status is init_failed: the simulator's native-devtools environment could not be initialised. Follow the message (re-boot the simulator) rather than retrying this tool.
133164
133164
  A not-running app comes back as one of those statuses rather than a failure. An app that is not connected does too, except on a device whose devtools agent argent attached to rather than armed itself: there it fails with \`NATIVE_DEVTOOLS_NOT_CONNECTED\`, since no restart of ours can complete a handshake we do not own. Failures are separate: an Apple system app is rejected outright (terminal: never retry it), while a missing host dependency or a udid that is not an Apple device is not.`,
@@ -134237,116 +134237,1038 @@ init_src2();
134237
134237
 
134238
134238
  // ../tool-server/src/utils/debugger/scripts/network-interceptor.ts
134239
134239
  var NETWORK_INTERCEPTOR_SCRIPT = `(function() {
134240
- if (globalThis.__argent_network_installed) return JSON.stringify({ installed: false, reason: 'already installed' });
134241
- globalThis.__argent_network_installed = true;
134240
+ var g = globalThis;
134241
+ if (g.__argent_network_v2) return JSON.stringify({ installed: false, reason: 'already installed' });
134242
+ g.__argent_network_v2 = true;
134243
+ // The previous fetch-only script, injected by an older tool-server before an upgrade, stays
134244
+ // installed until the app reloads. Its fetch wrapper keeps the buffers below in a closure, so
134245
+ // empty them and stop them from growing: it then still reads each response, but keeps nothing.
134246
+ if (g.__argent_network_installed && Array.isArray(g.__argent_network_log)) {
134247
+ var oldLog = g.__argent_network_log;
134248
+ oldLog.length = 0;
134249
+ oldLog.push = function() { return 0; };
134250
+ var oldById = g.__argent_network_by_id;
134251
+ if (oldById && typeof oldById === 'object') {
134252
+ for (var oldKey in oldById) delete oldById[oldKey];
134253
+ Object.freeze(oldById);
134254
+ }
134255
+ }
134256
+ // The previous fetch-only script checks this flag; setting it stops an older tool-server
134257
+ // from replacing the buffer below with its own.
134258
+ g.__argent_network_installed = true;
134259
+
134260
+ var MAX_ENTRIES = 2000;
134261
+ var BODY_CAP = 1048576;
134262
+ var BUFFER_CAP = 52428800;
134263
+ var hasOwn = Object.prototype.hasOwnProperty;
134242
134264
 
134243
134265
  var log = [];
134244
- globalThis.__argent_network_log = log;
134245
134266
  var byId = {};
134246
- globalThis.__argent_network_by_id = byId;
134247
- var nextReqId = 1;
134248
- var MAX_ENTRIES = 2000;
134249
- function genId() { return 'rn-net-' + (nextReqId++); }
134250
- function ts() { return Date.now() / 1000; }
134267
+ g.__argent_network_log = log;
134268
+ g.__argent_network_by_id = byId;
134269
+ var nextId = 1;
134270
+ var bufferedChars = 0;
134271
+ // Above zero while an XHR is sent: the request React Native's network module gets then is the
134272
+ // XHR's, which has its record.
134273
+ var xhrSending = 0;
134251
134274
 
134252
- function getOrCreate(reqId) {
134253
- if (byId[reqId]) return byId[reqId];
134254
- var entry = {
134255
- id: log.length,
134256
- requestId: reqId,
134257
- state: 'pending'
134275
+ function bodyChars(entry) {
134276
+ return (entry.responseBody ? entry.responseBody.length : 0) +
134277
+ (entry.request.postData ? entry.request.postData.length : 0) +
134278
+ (entry.bodyBytes ? entry.bodyBytes.byteLength : 0) + (entry.postBytes ? entry.postBytes.byteLength : 0) +
134279
+ (entry.bodyBase64 ? entry.bodyBase64.length : 0);
134280
+ }
134281
+
134282
+ function evict() {
134283
+ while (log.length > MAX_ENTRIES || (bufferedChars > BUFFER_CAP && log.length > 0)) {
134284
+ var removed = log.shift();
134285
+ bufferedChars -= bodyChars(removed);
134286
+ delete byId[removed.requestId];
134287
+ }
134288
+ }
134289
+
134290
+ // Runs on the app's JS thread for every text response. The loop costs about 200 ms per MiB in
134291
+ // Hermes with a debugger attached, so the native checks go first.
134292
+ function utf8Length(text) {
134293
+ try {
134294
+ if (/^[\\x00-\\x7f]*$/.test(text)) return text.length;
134295
+ if (typeof TextEncoder === 'function') return new TextEncoder().encode(text).length;
134296
+ } catch (e) {}
134297
+ var n = 0;
134298
+ for (var i = 0; i < text.length; i++) {
134299
+ var c = text.charCodeAt(i);
134300
+ if (c < 0x80) n += 1;
134301
+ else if (c < 0x800) n += 2;
134302
+ else if (c >= 0xd800 && c <= 0xdbff && i + 1 < text.length &&
134303
+ text.charCodeAt(i + 1) >= 0xdc00 && text.charCodeAt(i + 1) <= 0xdfff) { n += 4; i++; }
134304
+ else n += 3;
134305
+ }
134306
+ return n;
134307
+ }
134308
+
134309
+ // The UTF-8 size of a text body the interceptor keeps whole. A longer body is cut, and its size
134310
+ // is recorded in characters (bodyLength) instead: counting its bytes would take the app's JS
134311
+ // thread a time that grows with the body.
134312
+ function textSize(text) {
134313
+ return text.length > BODY_CAP ? undefined : utf8Length(text);
134314
+ }
134315
+
134316
+ // A body of one of these types is not text: its size is recorded and it is not read as text.
134317
+ var BINARY_TYPES = /^(image\\/(?!svg)|audio\\/|video\\/|font\\/|application\\/(octet-stream|pdf|zip|gzip|wasm|x-protobuf)$)/;
134318
+ function isBinaryType(mime) {
134319
+ return typeof mime === 'string' && BINARY_TYPES.test(mime.toLowerCase());
134320
+ }
134321
+
134322
+ function mimeTypeOf(headers) {
134323
+ return hasOwn.call(headers, 'content-type') ? String(headers['content-type']).split(';')[0].trim() : '';
134324
+ }
134325
+
134326
+ // The headers of every HeadersInit form: [name, value] pairs (an array or another iterable), a
134327
+ // Headers or Map, or a plain object. A name given twice keeps both values.
134328
+ function headersObject(source) {
134329
+ var out = {};
134330
+ if (!source || typeof source !== 'object') return out;
134331
+ var add = function(name, value) {
134332
+ name = String(name);
134333
+ out[name] = hasOwn.call(out, name) ? out[name] + ', ' + String(value) : String(value);
134258
134334
  };
134335
+ try {
134336
+ // A Headers and a Map iterate as pairs too.
134337
+ if (Array.isArray(source) || (typeof Symbol === 'function' && typeof source[Symbol.iterator] === 'function')) {
134338
+ var pairs = Array.from(source);
134339
+ for (var i = 0; i < pairs.length; i++) {
134340
+ if (pairs[i] && typeof pairs[i] === 'object' && pairs[i].length === 2) add(pairs[i][0], pairs[i][1]);
134341
+ }
134342
+ } else if (typeof source.forEach === 'function') {
134343
+ source.forEach(function(value, name) { add(name, value); });
134344
+ } else {
134345
+ for (var k in source) if (hasOwn.call(source, k)) add(k, source[k]);
134346
+ }
134347
+ } catch (e) {}
134348
+ return out;
134349
+ }
134350
+
134351
+ function parseHeaders(raw) {
134352
+ var out = {};
134353
+ if (typeof raw !== 'string') return out;
134354
+ var lines = raw.split(/\\r?\\n/);
134355
+ for (var i = 0; i < lines.length; i++) {
134356
+ var at = lines[i].indexOf(':');
134357
+ if (at <= 0) continue;
134358
+ var name = lines[i].slice(0, at).trim().toLowerCase();
134359
+ var value = lines[i].slice(at + 1).trim();
134360
+ out[name] = hasOwn.call(out, name) ? out[name] + ', ' + value : value;
134361
+ }
134362
+ return out;
134363
+ }
134364
+
134365
+ function describeFormData(form) {
134366
+ var parts = [];
134367
+ function file(value) {
134368
+ return '<file' + (value && value.name ? ' ' + value.name : '') + (value && value.type ? ' ' + value.type : '') + '>';
134369
+ }
134370
+ // React Native's FormData lists its parts through getParts().
134371
+ var list = form.getParts();
134372
+ for (var i = 0; i < list.length; i++) {
134373
+ parts.push(list[i].fieldName + '=' + (typeof list[i].string === 'string' ? list[i].string : file(list[i])));
134374
+ }
134375
+ return '[FormData] ' + parts.join('; ');
134376
+ }
134377
+
134378
+ // The text of UTF-8 bytes, cut at BODY_CAP on a character boundary; undefined when they are not
134379
+ // UTF-8 or no decoder exists (React Native has none; Expo installs one).
134380
+ function utf8Text(bytes) {
134381
+ if (typeof TextDecoder !== 'function') return undefined;
134382
+ var end = Math.min(bytes.byteLength, BODY_CAP);
134383
+ if (end < bytes.byteLength) while (end > 0 && (bytes[end] & 0xc0) === 0x80) end--;
134384
+ try { return new TextDecoder('utf-8', { fatal: true }).decode(bytes.subarray(0, end)); } catch (e) { return undefined; }
134385
+ }
134386
+
134387
+ // Bodies of bytes are kept as bytes, 3 past the cap to let utf8Text step back to a character
134388
+ // boundary, and decoded only when the agent reads the record: Expo SDK 54 and 55 install a
134389
+ // TextDecoder written in JS, which would stop the app's JS thread for each request.
134390
+ // A copy even of a subclass whose slice() returns a view (the Buffer of the buffer package).
134391
+ function keepBytes(bytes) {
134392
+ return Uint8Array.prototype.slice.call(bytes, 0, BODY_CAP + 3);
134393
+ }
134394
+
134395
+ // The bytes of base64 text, which a response of React Native's network module can come as.
134396
+ var base64Values = null;
134397
+ function base64Bytes(text) {
134398
+ if (!base64Values) {
134399
+ base64Values = {};
134400
+ var digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
134401
+ for (var d = 0; d < 64; d++) base64Values[digits.charAt(d)] = d;
134402
+ }
134403
+ var out = new Uint8Array(Math.floor(text.length * 3 / 4));
134404
+ var n = 0, bits = 0, acc = 0;
134405
+ for (var i = 0; i < text.length; i++) {
134406
+ var v = base64Values[text.charAt(i)];
134407
+ if (v === undefined) continue;
134408
+ acc = (acc << 6) | v;
134409
+ bits += 6;
134410
+ if (bits >= 8) {
134411
+ bits -= 8;
134412
+ out[n++] = (acc >> bits) & 0xff;
134413
+ acc &= (1 << bits) - 1;
134414
+ }
134415
+ }
134416
+ return out.subarray(0, n);
134417
+ }
134418
+
134419
+ function decodeBodies(entry) {
134420
+ if (!entry.postBytes && !entry.bodyBytes && entry.bodyBase64 === undefined) return;
134421
+ var before = bodyChars(entry);
134422
+ if (entry.bodyBase64 !== undefined) {
134423
+ entry.bodyBytes = base64Bytes(entry.bodyBase64);
134424
+ delete entry.bodyBase64;
134425
+ }
134426
+ if (entry.postBytes) {
134427
+ // Expo hands its native request the body as bytes: they show as text when they are UTF-8.
134428
+ var post = utf8Text(entry.postBytes);
134429
+ entry.request.postData = post === undefined ? '[binary ' + entry.postSize + ' bytes]' : post;
134430
+ if (post !== undefined && entry.postSize > BODY_CAP) entry.request.postDataTruncated = true;
134431
+ delete entry.postBytes;
134432
+ delete entry.postSize;
134433
+ }
134434
+ if (entry.bodyBytes) {
134435
+ var body = utf8Text(entry.bodyBytes);
134436
+ if (body !== undefined) {
134437
+ entry.responseBody = body;
134438
+ if (entry.encodedDataLength > BODY_CAP) entry.bodyTruncated = true;
134439
+ } else if (typeof TextDecoder === 'function') {
134440
+ // Bytes that are not UTF-8.
134441
+ entry.bodyOmitted = true;
134442
+ }
134443
+ delete entry.bodyBytes;
134444
+ }
134445
+ bufferedChars += bodyChars(entry) - before;
134446
+ evict();
134447
+ }
134448
+ g.__argent_network_decode = decodeBodies;
134449
+
134450
+ // What a request body shows, and whether that is only its start.
134451
+ function describeBody(body) {
134452
+ var text;
134453
+ try {
134454
+ if (body == null) return undefined;
134455
+ if (typeof body === 'string') text = body;
134456
+ else if (typeof FormData === 'function' && body instanceof FormData) text = describeFormData(body);
134457
+ else if (typeof Blob === 'function' && body instanceof Blob) text = '[Blob ' + body.size + ' bytes]';
134458
+ else if (typeof Uint8Array === 'function' && body instanceof Uint8Array) return { bytes: keepBytes(body), size: body.byteLength };
134459
+ else if (typeof ArrayBuffer === 'function' && (body instanceof ArrayBuffer || ArrayBuffer.isView(body))) {
134460
+ text = '[binary ' + body.byteLength + ' bytes]';
134461
+ }
134462
+ } catch (e) {}
134463
+ if (text === undefined) return undefined;
134464
+ return text.length > BODY_CAP ? { text: text.slice(0, BODY_CAP), cut: true } : { text: text, cut: false };
134465
+ }
134466
+
134467
+ function createRecord(resourceType, method, url, headers, body, startedAt) {
134468
+ var id = nextId++;
134469
+ var entry = {
134470
+ id: id,
134471
+ requestId: 'rn-net-' + id,
134472
+ state: 'pending',
134473
+ resourceType: resourceType,
134474
+ request: { url: url, method: method, headers: headers },
134475
+ timestamp: startedAt / 1000
134476
+ };
134477
+ var postData = describeBody(body);
134478
+ if (postData && postData.bytes) {
134479
+ entry.postBytes = postData.bytes;
134480
+ entry.postSize = postData.size;
134481
+ } else if (postData) {
134482
+ entry.request.postData = postData.text;
134483
+ if (postData.cut) entry.request.postDataTruncated = true;
134484
+ }
134259
134485
  log.push(entry);
134260
- byId[reqId] = entry;
134261
- if (log.length > MAX_ENTRIES) {
134262
- var removed = log.splice(0, log.length - MAX_ENTRIES);
134263
- for (var i = 0; i < removed.length; i++) {
134264
- delete byId[removed[i].requestId];
134265
- }
134266
- }
134267
- return entry;
134268
- }
134269
-
134270
- // \u2500\u2500 Patch fetch \u2500\u2500
134271
- var origFetch = globalThis.fetch;
134272
- if (origFetch) {
134273
- globalThis.fetch = function(input, init) {
134274
- var reqId = genId();
134275
- var method = (init && init.method) ? init.method.toUpperCase() : 'GET';
134276
- var url = typeof input === 'string' ? input : (input && input.url ? input.url : String(input));
134277
- var headers = {};
134278
- if (init && init.headers) {
134279
- if (typeof init.headers.forEach === 'function') {
134280
- init.headers.forEach(function(v, k) { headers[k] = v; });
134281
- } else if (typeof init.headers === 'object') {
134282
- var ks = Object.keys(init.headers);
134283
- for (var i = 0; i < ks.length; i++) { headers[ks[i]] = String(init.headers[ks[i]]); }
134284
- }
134285
- }
134286
- var postData = (init && init.body && typeof init.body === 'string') ? init.body : undefined;
134287
- var t = ts();
134288
-
134289
- var entry = getOrCreate(reqId);
134290
- entry.request = { url: url, method: method, headers: headers, postData: postData };
134291
- entry.timestamp = t;
134292
- entry.wallTime = t;
134293
- entry.resourceType = 'Fetch';
134294
-
134295
- return origFetch.apply(globalThis, arguments).then(function(response) {
134296
- var respHeaders = {};
134297
- if (response.headers && typeof response.headers.forEach === 'function') {
134298
- response.headers.forEach(function(v, k) { respHeaders[k] = v; });
134299
- }
134300
- var contentType = respHeaders['content-type'] || '';
134301
- var mimeType = contentType.split(';')[0].trim();
134302
-
134303
- entry.response = {
134304
- url: response.url || url,
134305
- status: response.status,
134306
- statusText: response.statusText || '',
134307
- headers: respHeaders,
134308
- mimeType: mimeType
134486
+ byId[entry.requestId] = entry;
134487
+ bufferedChars += bodyChars(entry);
134488
+ evict();
134489
+ return { entry: entry, startedAt: startedAt };
134490
+ }
134491
+
134492
+ function setResponse(rec, url, status, statusText, headers) {
134493
+ var e = rec.entry;
134494
+ e.response = { url: url || e.request.url, status: status, statusText: statusText || '', headers: headers, mimeType: mimeTypeOf(headers) };
134495
+ }
134496
+
134497
+ // A response header, by its name in lower case.
134498
+ function headerValue(response, wanted) {
134499
+ var headers = response && response.headers;
134500
+ if (!headers) return undefined;
134501
+ for (var name in headers) {
134502
+ if (hasOwn.call(headers, name) && name.toLowerCase() === wanted) return String(headers[name]);
134503
+ }
134504
+ return undefined;
134505
+ }
134506
+
134507
+ // The Content-Length of a response, when it gives the size of the body the app gets. With a
134508
+ // Content-Encoding (gzip, br) it is the compressed size, which iOS keeps on the decoded response.
134509
+ function contentLength(response) {
134510
+ var encoding = headerValue(response, 'content-encoding');
134511
+ if (encoding !== undefined && encoding.trim().toLowerCase() !== 'identity' && encoding.trim() !== '') return undefined;
134512
+ var value = headerValue(response, 'content-length');
134513
+ var n = Number(value);
134514
+ return value !== undefined && value !== '' && n >= 0 && Math.floor(n) === n ? n : undefined;
134515
+ }
134516
+
134517
+ function isBinaryResponse(rec) {
134518
+ return !!rec.entry.response && isBinaryType(rec.entry.response.mimeType);
134519
+ }
134520
+
134521
+ // The size of a text body, unless the body is binary: its text is then not stored (see storeBody).
134522
+ function textSizeOf(rec, text) {
134523
+ return isBinaryResponse(rec) ? undefined : textSize(text);
134524
+ }
134525
+
134526
+ // Records a body that is not kept, once: the details show its size instead.
134527
+ function omitBody(rec, byteLength) {
134528
+ var e = rec.entry;
134529
+ if (e.responseBody !== undefined || e.encodedDataLength !== undefined || e.bodyOmitted) return;
134530
+ e.bodyOmitted = true;
134531
+ if (typeof byteLength === 'number') e.encodedDataLength = byteLength;
134532
+ }
134533
+
134534
+ // Stores the body of a response, once: text, or bytes that keepBytes cut.
134535
+ function storeBody(rec, body, byteLength, truncated) {
134536
+ var e = rec.entry;
134537
+ if (e.responseBody !== undefined || e.encodedDataLength !== undefined || e.bodyOmitted) return;
134538
+ if (typeof body === 'string' && isBinaryResponse(rec)) {
134539
+ // A binary body read as text (an XHR's default responseType, Expo's text()) is not that text.
134540
+ // Its size is the one the server gave, else the length of the text, close to it: a byte that is
134541
+ // not UTF-8 reads as one character.
134542
+ var declared = e.state === 'failed' ? undefined : contentLength(e.response);
134543
+ omitBody(rec, declared);
134544
+ if (declared === undefined) e.bodyLength = body.length;
134545
+ return;
134546
+ }
134547
+ if (body instanceof Uint8Array && isBinaryResponse(rec)) return omitBody(rec, byteLength);
134548
+ if (typeof byteLength === 'number') e.encodedDataLength = byteLength;
134549
+ if (body instanceof Uint8Array) {
134550
+ e.bodyBytes = body;
134551
+ if (byId[e.requestId] === e) { bufferedChars += body.byteLength; evict(); }
134552
+ } else if (typeof body === 'string') {
134553
+ if (body.length > BODY_CAP) {
134554
+ // A cut body is not counted (see textSize): its size is the one the server gave, if any.
134555
+ e.bodyLength = body.length;
134556
+ if (e.encodedDataLength === undefined) e.encodedDataLength = contentLength(e.response);
134557
+ body = body.slice(0, BODY_CAP);
134558
+ truncated = true;
134559
+ }
134560
+ e.responseBody = body;
134561
+ if (truncated) e.bodyTruncated = true;
134562
+ if (byId[e.requestId] === e) { bufferedChars += body.length; evict(); }
134563
+ }
134564
+ }
134565
+
134566
+ // Ends a request that got a response and stores its body. A request that failed meanwhile (the
134567
+ // app aborted it while its body was read) stays failed.
134568
+ function complete(rec, body, byteLength, truncated) {
134569
+ if (rec.entry.state === 'failed') return;
134570
+ storeBody(rec, body, byteLength, truncated);
134571
+ rec.entry.state = 'finished';
134572
+ }
134573
+
134574
+ function fail(rec, errorText) {
134575
+ var e = rec.entry;
134576
+ e.state = 'failed';
134577
+ e.errorText = errorText;
134578
+ e.durationMs = Date.now() - rec.startedAt;
134579
+ }
134580
+
134581
+ // \u2500\u2500 XMLHttpRequest \u2500\u2500
134582
+ var XHR = g.XMLHttpRequest;
134583
+ if (typeof XHR === 'function') {
134584
+ var slots = new WeakMap();
134585
+ // The record and the end handler of each XHR's live request.
134586
+ var live = new WeakMap();
134587
+ var proto = XHR.prototype;
134588
+ var origOpen = proto.open;
134589
+ var origSetRequestHeader = proto.setRequestHeader;
134590
+ var origSend = proto.send;
134591
+ var origAbort = proto.abort;
134592
+
134593
+ var onHeaders = function(xhr, rec) {
134594
+ var raw;
134595
+ try { raw = xhr.getAllResponseHeaders(); } catch (e) {}
134596
+ setResponse(rec, xhr.responseURL, xhr.status, xhr.statusText, parseHeaders(raw));
134597
+ };
134598
+
134599
+ // React Native's fetch is whatwg-fetch over XHR. Each call builds a Request of whatwg-fetch, whose
134600
+ // constructor ends with _initBody, and then, in the same synchronous run, opens its XHR with that
134601
+ // Request's method and URL. Its Request class is the global one, so wrapping _initBody marks the
134602
+ // Request of each call, also of a fetch the app captured before the install, and the next open
134603
+ // that matches it, before the run ends, is the call's XHR. Any other XHR is the app's own, even
134604
+ // one that reads its response as a blob.
134605
+ var fetchRequest = null;
134606
+ var knowsFetch = false;
134607
+ try {
134608
+ var requestProto = typeof g.Request === 'function' && g.Request.prototype;
134609
+ if (requestProto && hasOwn.call(requestProto, '_initBody') && typeof requestProto._initBody === 'function' &&
134610
+ typeof Promise === 'function') {
134611
+ var origInitBody = requestProto._initBody;
134612
+ var markFetch = function() {
134613
+ fetchRequest = this;
134614
+ var marked = this;
134615
+ Promise.resolve().then(function() { if (fetchRequest === marked) fetchRequest = null; });
134616
+ return origInitBody.apply(this, arguments);
134309
134617
  };
134618
+ requestProto._initBody = markFetch;
134619
+ knowsFetch = requestProto._initBody === markFetch;
134620
+ }
134621
+ } catch (e) {}
134622
+ // Without that Request class, an XHR shaped as React Native's fetch sends it: it reads every
134623
+ // response as a blob (as an arraybuffer where Blob is unavailable) and sets these handlers.
134624
+ // axios sets onloadend, not onload.
134625
+ var isFetchXhr = function(xhr, slot) {
134626
+ if (knowsFetch) return slot.fetch;
134627
+ return (xhr.responseType === 'blob' || xhr.responseType === 'arraybuffer') && typeof xhr.onload === 'function' &&
134628
+ typeof xhr.onabort === 'function' && typeof xhr.ontimeout === 'function';
134629
+ };
134630
+
134631
+ var isBinaryBlob = function(rec, blob) {
134632
+ var mime = rec.entry.response && rec.entry.response.mimeType;
134633
+ try { if (!mime) mime = blob.type; } catch (e) {}
134634
+ return isBinaryType(mime);
134635
+ };
134636
+
134637
+ // iOS resolves readAsText with null when the cut splits a UTF-8 character, so step back a
134638
+ // byte at a time, at most 3 times.
134639
+ // The size is read up front: RN's Blob throws on every access once the app closes it.
134640
+ // cutOf(end), when given, makes the start of the blob that is read instead of a slice.
134641
+ var readBlob = function(rec, blob, size, end, cutOf) {
134642
+ if (end === Math.min(size, BODY_CAP) && isBinaryBlob(rec, blob)) {
134643
+ omitBody(rec, size);
134644
+ return complete(rec, undefined, size, false);
134645
+ }
134646
+ var part = end < size ? (cutOf ? cutOf(end) : blob.slice(0, end)) : blob;
134647
+ // A slice holds its own reference to the native blob: release it, so the app's close() frees it.
134648
+ var release = function() { if (part !== blob && !cutOf) part.close(); };
134649
+ var reader = new FileReader();
134650
+ reader.onload = function() {
134651
+ release();
134652
+ if (typeof reader.result === 'string' || part === blob || end <= BODY_CAP - 3) {
134653
+ return complete(rec, reader.result, size, part !== blob);
134654
+ }
134655
+ // The app may have closed the blob since the first read.
134656
+ try { readBlob(rec, blob, size, end - 1, cutOf); } catch (e) { complete(rec, undefined, size, false); }
134657
+ };
134658
+ reader.onerror = function() {
134659
+ release();
134660
+ complete(rec, undefined, size, false);
134661
+ };
134662
+ reader.readAsText(part);
134663
+ };
134664
+
134665
+ var readBody = function(rec, xhr) {
134666
+ var type = xhr.responseType || '';
134667
+ if (type === '' || type === 'text') {
134668
+ var text = xhr.responseText;
134669
+ if (typeof text === 'string') return complete(rec, text, textSizeOf(rec, text), false);
134670
+ } else if (type === 'json') {
134671
+ // React Native keeps the text it received, which spares a serialization of the whole object.
134672
+ var raw = xhr._response;
134673
+ if (typeof raw === 'string') return complete(rec, raw, textSize(raw), false);
134674
+ var json = xhr.response == null ? undefined : JSON.stringify(xhr.response);
134675
+ // Re-serialized, so its length is not the size received: record no size.
134676
+ if (typeof json === 'string') return complete(rec, json, undefined, false);
134677
+ } else if (type === 'arraybuffer') {
134678
+ // Kept as bytes like Expo's bodies, and decoded when the agent reads the record.
134679
+ var buffer = xhr.response;
134680
+ if (buffer && typeof buffer.byteLength === 'number') return complete(rec, keepBytes(new Uint8Array(buffer)), buffer.byteLength);
134681
+ } else if (type === 'blob') {
134682
+ var blob = xhr.response;
134683
+ if (blob) return readBlob(rec, blob, blob.size, Math.min(blob.size, BODY_CAP));
134684
+ }
134685
+ complete(rec, undefined, undefined, false);
134686
+ };
134687
+
134688
+ // Listeners belong to one request and are removed when it ends.
134689
+ var listen = function(xhr, rec) {
134690
+ var handlers = {
134691
+ readystatechange: function() { if (xhr.readyState === 2) onHeaders(xhr, rec); },
134692
+ load: function() { end('load'); },
134693
+ error: function() { end('error'); },
134694
+ timeout: function() { end('timeout'); },
134695
+ abort: function() { end('abort'); }
134696
+ };
134697
+ var end = function(outcome, force) {
134698
+ // An end event that finds the XHR no longer DONE belongs to a request the app has already
134699
+ // replaced, and the abort patch below recorded how that request ended.
134700
+ if (!force && xhr.readyState !== 4) return;
134701
+ for (var type in handlers) xhr.removeEventListener(type, handlers[type]);
134702
+ if (live.has(xhr) && live.get(xhr).end === end) live.delete(xhr);
134703
+ if (outcome === 'abort') return fail(rec, 'aborted');
134704
+ if (outcome === 'timeout') return fail(rec, 'timeout');
134705
+ if (outcome === 'error') {
134706
+ var text = '';
134707
+ try { if (xhr.responseType === '' || xhr.responseType === 'text') text = xhr.responseText; } catch (e) {}
134708
+ return fail(rec, text ? String(text) : 'Network error');
134709
+ }
134710
+ // RN sets responseURL only after it announces HEADERS_RECEIVED: this is the final URL.
134711
+ if (rec.entry.response && xhr.responseURL) rec.entry.response.url = xhr.responseURL;
134712
+ rec.entry.durationMs = Date.now() - rec.startedAt;
134713
+ try { readBody(rec, xhr); } catch (e) { complete(rec, undefined, undefined, false); }
134714
+ };
134715
+ live.set(xhr, { rec: rec, end: end });
134716
+ // RN switches an XHR to incremental response updates once it has a readystatechange
134717
+ // listener. Restore the flag so these listeners do not change how the app gets data.
134718
+ var incremental = xhr._incrementalEvents;
134719
+ for (var type in handlers) xhr.addEventListener(type, handlers[type]);
134720
+ if (typeof incremental === 'boolean') xhr._incrementalEvents = incremental;
134721
+ };
134722
+
134723
+ proto.open = function(method, url) {
134724
+ var orphan = live.get(this);
134725
+ var previous = slots.get(this);
134726
+ var request = fetchRequest;
134727
+ fetchRequest = null;
134728
+ var slot = { method: String(method || 'GET').toUpperCase(), url: String(url), headers: {}, fetch: false };
134729
+ // whatwg-fetch opens '' as the page's URL, which React Native does not have.
134730
+ slot.fetch = !!request && String(request.method).toUpperCase() === slot.method && (String(request.url) === slot.url || request.url === '');
134731
+ // Set before the original open, which dispatches readystatechange: a handler may send from there.
134732
+ slots.set(this, slot);
134733
+ var result;
134734
+ try {
134735
+ result = origOpen.apply(this, arguments);
134736
+ } catch (e) {
134737
+ if (previous) slots.set(this, previous);
134738
+ else slots.delete(this);
134739
+ throw e;
134740
+ }
134741
+ // Open succeeds only on a reset XHR, so a request still listening here can never end: RN reset
134742
+ // it without an end event reaching these listeners (a re-send from inside an abort's own
134743
+ // handlers, or an app listener that stopped the event). Drop it before it takes a response.
134744
+ if (orphan) orphan.end('abort', true);
134745
+ return result;
134746
+ };
134310
134747
 
134311
- // Read the cloned response as a Blob to preserve the decoded entity's
134312
- // byte length across text charsets and binary responses. Content-Length
134313
- // cannot be used here: HEAD/304 responses may advertise a body that was
134314
- // not sent, and compression headers describe a different measurement.
134315
- var sizeClone = response.clone();
134316
- var bodyClone = response.clone();
134317
- var sizePromise = typeof sizeClone.blob === 'function'
134318
- ? sizeClone.blob().then(function(blob) {
134319
- return blob && typeof blob.size === 'number' ? blob.size : undefined;
134320
- }).catch(function() { return undefined; })
134321
- : Promise.resolve(undefined);
134322
- var bodyPromise = bodyClone.text().catch(function() { return undefined; });
134323
-
134324
- Promise.all([sizePromise, bodyPromise]).then(function(values) {
134325
- var byteLength = values[0];
134326
- var body = values[1];
134327
- entry.state = 'finished';
134328
- if (typeof byteLength === 'number') entry.encodedDataLength = byteLength;
134329
- entry.durationMs = Math.round((ts() - t) * 1000);
134330
- if (typeof body === 'string') entry.responseBody = body;
134331
- }).catch(function() {
134332
- entry.state = 'finished';
134333
- entry.durationMs = Math.round((ts() - t) * 1000);
134334
- });
134748
+ proto.setRequestHeader = function(name, value) {
134749
+ var result = origSetRequestHeader.apply(this, arguments);
134750
+ var slot = slots.get(this);
134751
+ // React Native sends one value per lowercased name, the last one set. RN still accepts a
134752
+ // header after send, but it never reaches the wire, so it is not recorded.
134753
+ if (slot && slot.headers) {
134754
+ name = String(name).toLowerCase();
134755
+ value = String(value);
134756
+ // whatwg-fetch sets headers given as [name, value] pairs as if they were an object: each pair
134757
+ // as '0': 'name,value' and so on, then 'length', then each pair again under its own name. The
134758
+ // pair is recorded under its own name, so a redacted header stays redacted.
134759
+ var comma = value.indexOf(',');
134760
+ if (/^\\d+$/.test(name) && comma > 0) {
134761
+ slot.pairs = true;
134762
+ name = value.slice(0, comma).trim().toLowerCase();
134763
+ value = value.slice(comma + 1);
134764
+ } else if (name === 'length' && slot.pairs) {
134765
+ return result;
134766
+ }
134767
+ slot.headers[name] = value;
134768
+ }
134769
+ return result;
134770
+ };
134335
134771
 
134336
- return response;
134337
- }).catch(function(err) {
134338
- entry.state = 'failed';
134339
- entry.errorText = err ? (err.message || String(err)) : 'Network error';
134340
- entry.durationMs = Math.round((ts() - t) * 1000);
134341
- throw err;
134772
+ proto.send = function(body) {
134773
+ var slot = slots.get(this);
134774
+ var startedAt = Date.now();
134775
+ var result;
134776
+ xhrSending++;
134777
+ try {
134778
+ result = origSend.apply(this, arguments);
134779
+ } finally {
134780
+ xhrSending--;
134781
+ }
134782
+ if (!slot || !slot.headers) return result;
134783
+ var rec = createRecord(isFetchXhr(this, slot) ? 'Fetch' : 'XHR', slot.method, slot.url, slot.headers, body, startedAt);
134784
+ slot.headers = null;
134785
+ listen(this, rec);
134786
+ return result;
134787
+ };
134788
+
134789
+ // To reuse an XHR from its own handler, the app aborts it first, and RN runs that handler
134790
+ // before these listeners. Record a request that has already ended before abort resets the XHR.
134791
+ // RN keeps _aborted set until the next open, so an abort called from a handler of an abort
134792
+ // still counts as one. An app can also abort from its own handler of HEADERS_RECEIVED, which
134793
+ // runs before the listener that records the headers: keep them.
134794
+ // RN clears the response before it announces the abort, so the text an XHR has received while it
134795
+ // streams (react-native-sse aborts to close) is taken first. The request stays failed: the body
134796
+ // is only the part that arrived.
134797
+ proto.abort = function() {
134798
+ var request = live.get(this);
134799
+ if (request && this.readyState >= 2 && !request.rec.entry.response) onHeaders(this, request.rec);
134800
+ if (request && this.readyState === 4) {
134801
+ request.end(this._aborted ? 'abort' : this._hasError ? (this._timedOut ? 'timeout' : 'error') : 'load');
134802
+ }
134803
+ var partial;
134804
+ if (request && this.readyState === 3) {
134805
+ try { if (this.responseType === '' || this.responseType === 'text') partial = this.responseText; } catch (e) {}
134806
+ }
134807
+ var result = origAbort.apply(this, arguments);
134808
+ if (typeof partial === 'string' && partial && request.rec.entry.state === 'failed') {
134809
+ storeBody(request.rec, partial, textSizeOf(request.rec, partial), false);
134810
+ }
134811
+ return result;
134812
+ };
134813
+ }
134814
+
134815
+ // \u2500\u2500 Expo's native fetch \u2500\u2500
134816
+ // expo/fetch builds each request as new NativeRequest(response) and sends it with start(url, init,
134817
+ // body), whether the app calls it as the global fetch or imports it. The app gets that response
134818
+ // once start resolves.
134819
+ var expoFetch;
134820
+ try { expoFetch = g.expo && g.expo.modules && g.expo.modules.ExpoFetchModule; } catch (e) {}
134821
+ // Takes the whole body of a response from native, for the app to get at its first read. It stays
134822
+ // null when Expo's module cannot hand a body back (see below).
134823
+ var takeBody = null;
134824
+
134825
+ // Hands each result of an object's method to tap as well. track(1) runs when a call starts and
134826
+ // track(-1) once its promise settles. The caller gets a promise that settles like the original, so
134827
+ // a rejection it never handles is still reported as unhandled.
134828
+ function tapMethod(target, name, tap, track) {
134829
+ var method = target[name];
134830
+ if (typeof method !== 'function') return;
134831
+ target[name] = function() {
134832
+ var result = method.apply(this, arguments);
134833
+ if (!result || typeof result.then !== 'function') return result;
134834
+ track(1);
134835
+ return result.then(function(value) {
134836
+ try { tap(value); } catch (e) {}
134837
+ track(-1);
134838
+ return value;
134839
+ }, function(error) {
134840
+ track(-1);
134841
+ throw error;
134342
134842
  });
134343
134843
  };
134344
134844
  }
134345
134845
 
134346
- // Note: XHR is intentionally NOT patched. In React Native, fetch() is built
134347
- // on top of XMLHttpRequest internally, so patching both would double-count
134348
- // every request. Since virtually all RN code uses fetch(), patching only
134349
- // fetch captures all meaningful traffic without duplicates.
134846
+ // Native Expo hands a body out once, so reading it here while the request runs, or through a clone,
134847
+ // would take it from the app or make it wait. The body is taken as the app reads it: its text() or
134848
+ // arrayBuffer() (json(), blob() and bytes() go through them), or the chunks of its body stream. A
134849
+ // 2xx body the app never reads is not recorded. The native side announces the end of every request with
134850
+ // readyForJSFinalization, a failed one too, and that event can come before start rejects: the
134851
+ // record ends only once start resolved. The body can be complete before JS gets that event: a
134852
+ // whole body the app reads, or didComplete after the chunks it streams, ends the record too. So an
134853
+ // app that aborts once it has the body (urql, graphql-sse) does not make the request fail. A read
134854
+ // of the app still in flight holds the record: on iOS, when the connection drops in the middle of
134855
+ // the body, the event can say nothing of the drop, and the read then never settles. The record
134856
+ // stays pending, as the app's read does.
134857
+ // Apps often do not read the body of an error response (if (!response.ok) throw ...), and that
134858
+ // body is what the agent needs. Once the request has ended, and the app has not started to read
134859
+ // the body in the turn after, such a body is taken from native. The app gets it at its first read.
134860
+ function observeExpoResponse(rec, response, started) {
134861
+ var chunks = [];
134862
+ var kept = 0;
134863
+ var streamed = 0;
134864
+ var endedAt = 0;
134865
+ var responded = false;
134866
+ var reading = 0;
134867
+ var read = false;
134868
+ var finalized = false;
134869
+ var stalled = false;
134870
+ var completed = false;
134871
+ // Keeps as many bytes of the chunks as keepBytes keeps of a whole body.
134872
+ var take = function(data) {
134873
+ if (!data || typeof data.byteLength !== 'number') return;
134874
+ var view = ArrayBuffer.isView(data) ? data : new Uint8Array(data);
134875
+ streamed += view.byteLength;
134876
+ if (kept >= BODY_CAP + 3) return;
134877
+ chunks.push(new Uint8Array(view.buffer.slice(view.byteOffset, view.byteOffset + Math.min(view.byteLength, BODY_CAP + 3 - kept))));
134878
+ kept += chunks[chunks.length - 1].byteLength;
134879
+ };
134880
+ // A whole body, which can come after the request ended.
134881
+ var takeWhole = function(data) {
134882
+ if (!data || typeof data.byteLength !== 'number' || rec.entry.state === 'failed') return;
134883
+ var bytes = ArrayBuffer.isView(data) ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : new Uint8Array(data);
134884
+ storeBody(rec, keepBytes(bytes), bytes.byteLength);
134885
+ };
134886
+ var onData = function(data) { take(data); };
134887
+ var onFail = function(error) { if (rec.entry.state === 'pending') fail(rec, String(error)); };
134888
+ // Returns whether it holds the record for a take of the body.
134889
+ var tried = false;
134890
+ var takeUnread = function() {
134891
+ if (tried) return false;
134892
+ tried = true;
134893
+ var status = rec.entry.response.status;
134894
+ if (rec.entry.request.method === 'HEAD' || status === 204 || status === 205 || status === 304 ||
134895
+ rec.entry.response.headers['content-length'] === '0') {
134896
+ storeBody(rec, '', 0, false);
134897
+ return false;
134898
+ }
134899
+ if ((status >= 200 && status < 300) || !takeBody) return false;
134900
+ var release = function() {
134901
+ reading--;
134902
+ finish();
134903
+ };
134904
+ // The app gets a turn to start its own read: on iOS, SDK 55 resolves fetch only once the request
134905
+ // ended.
134906
+ reading++;
134907
+ setTimeout(function() {
134908
+ var body = read ? undefined : takeBody(response);
134909
+ if (!body) return release();
134910
+ body.then(function(data) {
134911
+ if (data != null) takeWhole(data);
134912
+ release();
134913
+ }, release);
134914
+ }, 0);
134915
+ return true;
134916
+ };
134917
+ var finish = function() {
134918
+ if (!endedAt || !responded || reading || stalled || rec.entry.state !== 'pending') return;
134919
+ if (!read && !streamed && takeUnread()) return;
134920
+ rec.entry.durationMs = endedAt - rec.startedAt;
134921
+ if (!streamed) return complete(rec, undefined, undefined, false);
134922
+ var bytes = new Uint8Array(kept);
134923
+ for (var i = 0, at = 0; i < chunks.length; at += chunks[i].byteLength, i++) bytes.set(chunks[i], at);
134924
+ complete(rec, bytes, streamed);
134925
+ };
134926
+ var bodyEnded = function() {
134927
+ if (!endedAt) endedAt = Date.now();
134928
+ };
134929
+ var ended = function() {
134930
+ bodyEnded();
134931
+ finish();
134932
+ };
134933
+ var track = function(step) {
134934
+ read = true;
134935
+ // A read that starts once the record ended without a body can hang too, and holds it again.
134936
+ if (step > 0 && rec.entry.state === 'finished' && rec.entry.encodedDataLength === undefined) rec.entry.state = 'pending';
134937
+ reading += step;
134938
+ finish();
134939
+ };
134940
+ var onEnd = function() {
134941
+ try { response.removeListener('readyForJSFinalization', onEnd); } catch (e) {}
134942
+ finalized = true;
134943
+ ended();
134944
+ };
134945
+ // Expo drops the stream listeners itself when the request ends.
134946
+ response.addListener('didReceiveResponseData', onData);
134947
+ response.addListener('didComplete', function() {
134948
+ completed = true;
134949
+ ended();
134950
+ });
134951
+ response.addListener('didFailWithError', onFail);
134952
+ response.addListener('readyForJSFinalization', onEnd);
134953
+ // startStreaming returns the whole body when it completed before the app opened its stream (on
134954
+ // iOS, SDK 55 resolves fetch only then), else null and the chunks follow as events.
134955
+ tapMethod(response, 'startStreaming', function(data) {
134956
+ // Once the request ended, native answers null only when the body did not complete: the app's
134957
+ // stream then never ends, and the record stays pending. On Android, SDK 55 can deliver the null
134958
+ // of a stream it started after the stream's didComplete and the end: that stream is whole.
134959
+ if (data == null) {
134960
+ if (finalized && !completed) stalled = true;
134961
+ return;
134962
+ }
134963
+ takeWhole(data);
134964
+ bodyEnded();
134965
+ }, track);
134966
+ tapMethod(response, 'arrayBuffer', function(data) {
134967
+ takeWhole(data);
134968
+ bodyEnded();
134969
+ }, track);
134970
+ tapMethod(response, 'text', function(text) {
134971
+ if (typeof text === 'string' && rec.entry.state !== 'failed') storeBody(rec, text, textSizeOf(rec, text), false);
134972
+ bodyEnded();
134973
+ }, track);
134974
+ started.then(function() {
134975
+ if (rec.entry.state === 'failed') return;
134976
+ setResponse(rec, response.url, response.status, response.statusText, headersObject(response.headers));
134977
+ responded = true;
134978
+ finish();
134979
+ }, function(err) {
134980
+ if (rec.entry.state === 'pending') fail(rec, err ? String(err.message || err) : 'Network error');
134981
+ });
134982
+ }
134983
+
134984
+ if (expoFetch && typeof expoFetch.NativeRequest === 'function' && typeof expoFetch.NativeRequest.prototype.start === 'function' &&
134985
+ typeof Proxy === 'function' && typeof Reflect === 'object') {
134986
+ var NativeRequest = expoFetch.NativeRequest;
134987
+ var nativeProto = NativeRequest.prototype;
134988
+ var origStart = nativeProto.start;
134989
+ var origCancel = nativeProto.cancel;
134990
+ // The response each request fills, and the record of each request sent.
134991
+ var responses = new WeakMap();
134992
+ var nativeRecords = new WeakMap();
134993
+ try {
134994
+ expoFetch.NativeRequest = new Proxy(NativeRequest, {
134995
+ construct: function(target, args, newTarget) {
134996
+ var request = Reflect.construct(target, args, newTarget);
134997
+ if (args[0] && typeof args[0] === 'object') responses.set(request, args[0]);
134998
+ return request;
134999
+ }
135000
+ });
135001
+ } catch (e) {}
135002
+ if (expoFetch.NativeRequest !== NativeRequest) {
135003
+ nativeProto.start = function(url, init, body) {
135004
+ var startedAt = Date.now();
135005
+ var promise = origStart.apply(this, arguments);
135006
+ var response = responses.get(this);
135007
+ if (!response || !promise || typeof promise.then !== 'function') return promise;
135008
+ try {
135009
+ var rec = createRecord('Fetch', String((init && init.method) || 'GET').toUpperCase(), String(url),
135010
+ lowerCaseHeaders(init && init.headers), body, startedAt);
135011
+ nativeRecords.set(this, rec);
135012
+ observeExpoResponse(rec, response, promise);
135013
+ } catch (e) {}
135014
+ return promise;
135015
+ };
135016
+ // expo/fetch cancels the request when the app aborts it, also while the app reads the body.
135017
+ if (typeof origCancel === 'function') {
135018
+ nativeProto.cancel = function() {
135019
+ var rec = nativeRecords.get(this);
135020
+ if (rec && rec.entry.state === 'pending') fail(rec, 'aborted');
135021
+ return origCancel.apply(this, arguments);
135022
+ };
135023
+ }
135024
+ // Native hands a body out once. A body taken for the record goes to the first read of the app
135025
+ // instead, through the native methods that expo/fetch reads with. Expo's own response, its
135026
+ // listeners and its body stream stay as they are.
135027
+ var responseProto = expoFetch.NativeResponse && expoFetch.NativeResponse.prototype;
135028
+ if (responseProto && typeof responseProto.arrayBuffer === 'function' && typeof responseProto.text === 'function' &&
135029
+ typeof responseProto.startStreaming === 'function' && typeof TextDecoder === 'function') {
135030
+ var takenBodies = new WeakMap();
135031
+ var origStartStreaming = responseProto.startStreaming;
135032
+ var handBack = function(name, convert) {
135033
+ var original = responseProto[name];
135034
+ var patched = function() {
135035
+ var body = takenBodies.get(this);
135036
+ if (!body) return original.apply(this, arguments);
135037
+ takenBodies.delete(this);
135038
+ var self = this, args = arguments;
135039
+ // Nothing taken (the body did not complete): native answers as it would have.
135040
+ return body.then(function(data) { return data == null ? original.apply(self, args) : convert(data); });
135041
+ };
135042
+ try { responseProto[name] = patched; } catch (e) {}
135043
+ return responseProto[name] === patched;
135044
+ };
135045
+ var handed = [
135046
+ handBack('arrayBuffer', function(data) {
135047
+ return data.byteOffset === 0 && data.byteLength === data.buffer.byteLength ? data.buffer :
135048
+ data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
135049
+ }),
135050
+ handBack('startStreaming', function(data) { return data; }),
135051
+ // As native text(): a byte order mark stays, and bytes that are not UTF-8 become U+FFFD. This
135052
+ // decode runs on the app's JS thread, only for an error body the app reads a turn or more
135053
+ // after the request ended.
135054
+ handBack('text', function(data) { return new TextDecoder('utf-8', { ignoreBOM: true }).decode(data); })
135055
+ ];
135056
+ if (handed[0] && handed[1] && handed[2]) {
135057
+ // Once the request ended, native startStreaming returns the whole body, or null when the body
135058
+ // did not complete, and does not wait.
135059
+ takeBody = function(response) {
135060
+ var body = origStartStreaming.call(response);
135061
+ if (!body || typeof body.then !== 'function') return undefined;
135062
+ takenBodies.set(response, body);
135063
+ return body;
135064
+ };
135065
+ }
135066
+ }
135067
+ }
135068
+ }
135069
+
135070
+ // \u2500\u2500 React Native's network module \u2500\u2500
135071
+ // A fetch library can send through React Native's network module (RCTNetworking) itself, not
135072
+ // through an XHR: react-native-fetch-api, which react-native-polyfill-globals installs as the global
135073
+ // fetch. Each request such a library sends there is recorded, so it has one record whatever the
135074
+ // app's wrappers do, and whenever the library was installed. The module is not global: it is found
135075
+ // in the module list of Metro's development build (__r.getModules()). A request that an XHR sends
135076
+ // (xhrSending) has its record from the XHR.
135077
+ // The native module answers by request id, with events of the module: the response, its data
135078
+ // (text, base64 text or a native blob, as the sender asked) or the parts of a text it streams, and
135079
+ // the end of the request. An aborted request gets no end event on iOS.
135080
+ var NETWORKING_PATH = /(^|[\\\\/])Libraries[\\\\/]Network[\\\\/]RCTNetworking(\\.[a-z]+)?\\.js$/;
135081
+ function findNetworking() {
135082
+ var modules;
135083
+ try { modules = typeof g.__r === 'function' && typeof g.__r.getModules === 'function' ? g.__r.getModules() : null; } catch (e) {}
135084
+ if (!modules || typeof modules !== 'object') return null;
135085
+ var matches = [];
135086
+ var check = function(mod, id) {
135087
+ if (mod && typeof mod.verboseName === 'string' && NETWORKING_PATH.test(mod.verboseName)) matches.push({ mod: mod, id: Number(id) });
135088
+ };
135089
+ if (typeof modules.forEach === 'function') modules.forEach(check);
135090
+ else for (var key in modules) if (hasOwn.call(modules, key)) check(modules[key], key);
135091
+ // A module the app has not loaded yet is loaded here, as its first request would.
135092
+ matches.sort(function(a, b) { return (b.mod.isInitialized ? 1 : 0) - (a.mod.isInitialized ? 1 : 0); });
135093
+ for (var i = 0; i < matches.length; i++) {
135094
+ var exports;
135095
+ try {
135096
+ exports = matches[i].mod.isInitialized ? matches[i].mod.publicModule && matches[i].mod.publicModule.exports : g.__r(matches[i].id);
135097
+ } catch (e) {}
135098
+ var api = exports && typeof exports === 'object' && exports.default ? exports.default : exports;
135099
+ if (api && typeof api.sendRequest === 'function' && typeof api.abortRequest === 'function' &&
135100
+ typeof api.addListener === 'function') return api;
135101
+ }
135102
+ return null;
135103
+ }
135104
+
135105
+ function lowerCaseHeaders(source) {
135106
+ var headers = headersObject(source);
135107
+ var out = {};
135108
+ for (var name in headers) {
135109
+ if (!hasOwn.call(headers, name)) continue;
135110
+ var lower = name.toLowerCase();
135111
+ out[lower] = hasOwn.call(out, lower) ? out[lower] + ', ' + headers[name] : headers[name];
135112
+ }
135113
+ return out;
135114
+ }
135115
+
135116
+ // A Blob of the native blob a response came as, for FileReader only: it does not count as a
135117
+ // reference to the native blob, so the app's own Blob of it still frees it, and nothing here closes it.
135118
+ function blobOf(data, size) {
135119
+ var copy = {};
135120
+ for (var key in data) if (hasOwn.call(data, key)) copy[key] = data[key];
135121
+ copy.size = size;
135122
+ var blob = Object.create(Blob.prototype);
135123
+ blob.data = copy;
135124
+ return blob;
135125
+ }
135126
+
135127
+ // Records one request sent through the module. Returns the request's watcher, which gets the
135128
+ // request id once the module hands it back.
135129
+ function watchRequest(networking, method, url, headers, data, responseType) {
135130
+ var rec = createRecord('Fetch', String(method || 'GET').toUpperCase(), String(url), lowerCaseHeaders(headers), data, Date.now());
135131
+ var requestId;
135132
+ var subscriptions = [];
135133
+ var received;
135134
+ // The text received so far of a response streamed in parts, up to one character past the cut.
135135
+ var streamed = '';
135136
+ var streamedChars = 0;
135137
+ var streamedBytes = 0;
135138
+ var stop = function() {
135139
+ for (var i = 0; i < subscriptions.length; i++) {
135140
+ try { subscriptions[i].remove(); } catch (e) {}
135141
+ }
135142
+ subscriptions = [];
135143
+ if (requestId !== undefined && watched[requestId] === watcher) delete watched[requestId];
135144
+ };
135145
+ var storeStreamed = function() {
135146
+ if (!streamedChars) return;
135147
+ var cut = streamedChars > BODY_CAP;
135148
+ storeBody(rec, streamed, cut ? undefined : streamedBytes, false);
135149
+ if (cut) rec.entry.bodyLength = streamedChars;
135150
+ };
135151
+ var handlers = {
135152
+ didReceiveNetworkResponse: function(args) {
135153
+ setResponse(rec, args[3], args[1], '', lowerCaseHeaders(args[2]));
135154
+ },
135155
+ didReceiveNetworkData: function(args) { received = args[1]; },
135156
+ didReceiveNetworkIncrementalData: function(args) {
135157
+ var text = args[1];
135158
+ if (typeof text !== 'string' || !text) return;
135159
+ streamedChars += text.length;
135160
+ if (streamedChars <= BODY_CAP) streamedBytes += utf8Length(text);
135161
+ if (streamed.length <= BODY_CAP) streamed += text.slice(0, BODY_CAP + 1 - streamed.length);
135162
+ },
135163
+ didCompleteNetworkResponse: function(args) {
135164
+ stop();
135165
+ if (rec.entry.state !== 'pending') return;
135166
+ if (args[2]) return fail(rec, 'timeout');
135167
+ if (args[1]) {
135168
+ storeStreamed();
135169
+ return fail(rec, String(args[1]));
135170
+ }
135171
+ rec.entry.durationMs = Date.now() - rec.startedAt;
135172
+ if (typeof received === 'string') {
135173
+ if (responseType !== 'base64') return complete(rec, received, textSizeOf(rec, received), false);
135174
+ var n = received.length;
135175
+ var padding = received.charAt(n - 1) === '=' ? (received.charAt(n - 2) === '=' ? 2 : 1) : 0;
135176
+ var size = Math.max(0, Math.floor(n * 3 / 4) - padding);
135177
+ // A binary body keeps no bytes (see storeBody).
135178
+ if (isBinaryResponse(rec)) {
135179
+ omitBody(rec, size);
135180
+ return complete(rec, undefined, size, false);
135181
+ }
135182
+ // Decoded when the agent reads the record, as bytes are.
135183
+ rec.entry.bodyBase64 = received.slice(0, Math.ceil((BODY_CAP + 3) / 3) * 4);
135184
+ if (byId[rec.entry.requestId] === rec.entry) { bufferedChars += rec.entry.bodyBase64.length; evict(); }
135185
+ return complete(rec, undefined, size, false);
135186
+ }
135187
+ if (received && typeof received === 'object' && typeof received.blobId === 'string') {
135188
+ var blobSize = Number(received.size) || 0;
135189
+ if (typeof Blob === 'function' && typeof FileReader === 'function' && typeof readBlob === 'function') {
135190
+ try {
135191
+ return readBlob(rec, blobOf(received, blobSize), blobSize, Math.min(blobSize, BODY_CAP), function(end) { return blobOf(received, end); });
135192
+ } catch (e) {}
135193
+ }
135194
+ if (isBinaryResponse(rec)) omitBody(rec, blobSize);
135195
+ return complete(rec, undefined, blobSize, false);
135196
+ }
135197
+ storeStreamed();
135198
+ complete(rec, undefined, undefined, false);
135199
+ }
135200
+ };
135201
+ var watcher = {
135202
+ sent: function(id) {
135203
+ requestId = id;
135204
+ watched[id] = watcher;
135205
+ },
135206
+ // The app aborted the request: what it streamed so far stays with the failed record.
135207
+ abort: function() {
135208
+ stop();
135209
+ if (rec.entry.state !== 'pending') return;
135210
+ storeStreamed();
135211
+ fail(rec, 'aborted');
135212
+ },
135213
+ // The module did not take the request: its send threw before it handed back an id.
135214
+ drop: function() {
135215
+ if (requestId !== undefined) return;
135216
+ stop();
135217
+ var at = log.lastIndexOf(rec.entry);
135218
+ if (at === -1) return;
135219
+ log.splice(at, 1);
135220
+ delete byId[rec.entry.requestId];
135221
+ bufferedChars -= bodyChars(rec.entry);
135222
+ }
135223
+ };
135224
+ // Each event carries the request id, which the module hands back only after the request starts.
135225
+ try {
135226
+ for (var event in handlers) {
135227
+ (function(handler) {
135228
+ subscriptions.push(networking.addListener(event, function(args) {
135229
+ if (requestId === undefined || !args || args[0] !== requestId) return;
135230
+ try { handler(args); } catch (e) {}
135231
+ }));
135232
+ })(handlers[event]);
135233
+ }
135234
+ } catch (e) {
135235
+ watcher.drop();
135236
+ return null;
135237
+ }
135238
+ return watcher;
135239
+ }
135240
+
135241
+ // The watcher of each request in flight, by request id.
135242
+ var watched = {};
135243
+ var networking = findNetworking();
135244
+ if (networking) {
135245
+ var origSendRequest = networking.sendRequest;
135246
+ var origAbortRequest = networking.abortRequest;
135247
+ networking.sendRequest = function(method, trackingName, url, headers, data, responseType, incrementalUpdates, timeout, callback) {
135248
+ if (xhrSending || typeof callback !== 'function') return origSendRequest.apply(this, arguments);
135249
+ var watcher = null;
135250
+ try { watcher = watchRequest(networking, method, url, headers, data, responseType); } catch (e) {}
135251
+ if (!watcher) return origSendRequest.apply(this, arguments);
135252
+ var args = Array.prototype.slice.call(arguments);
135253
+ args[8] = function(requestId) {
135254
+ try { if (typeof requestId === 'number' || typeof requestId === 'string') watcher.sent(requestId); } catch (e) {}
135255
+ return callback.apply(this, arguments);
135256
+ };
135257
+ try {
135258
+ return origSendRequest.apply(this, args);
135259
+ } catch (e) {
135260
+ watcher.drop();
135261
+ throw e;
135262
+ }
135263
+ };
135264
+ networking.abortRequest = function(requestId) {
135265
+ var watcher = hasOwn.call(watched, requestId) ? watched[requestId] : null;
135266
+ if (watcher) {
135267
+ try { watcher.abort(); } catch (e) {}
135268
+ }
135269
+ return origAbortRequest.apply(this, arguments);
135270
+ };
135271
+ }
134350
135272
 
134351
135273
  return JSON.stringify({ installed: true });
134352
135274
  })()`;
@@ -134382,9 +135304,10 @@ function makeNetworkLogReadScript(start2, limit, metroPort2) {
134382
135304
  requestId: s.requestId,
134383
135305
  state: s.state,
134384
135306
  request: s.request ? { url: s.request.url, method: s.request.method } : undefined,
134385
- response: s.response ? { status: s.response.status, statusText: s.response.statusText, mimeType: s.response.mimeType } : undefined,
135307
+ response: s.response ? { url: s.response.url, status: s.response.status, statusText: s.response.statusText, mimeType: s.response.mimeType } : undefined,
134386
135308
  resourceType: s.resourceType,
134387
135309
  encodedDataLength: s.encodedDataLength,
135310
+ bodyLength: s.bodyLength,
134388
135311
  timestamp: s.timestamp,
134389
135312
  durationMs: s.durationMs,
134390
135313
  errorText: s.errorText
@@ -134400,6 +135323,7 @@ function makeNetworkDetailReadScript(requestId) {
134400
135323
  if (!byId) return JSON.stringify({ error: 'Network interceptor not installed' });
134401
135324
  var entry = byId[${JSON.stringify(requestId)}];
134402
135325
  if (!entry) return JSON.stringify({ error: 'Request not found' });
135326
+ if (typeof globalThis.__argent_network_decode === 'function') globalThis.__argent_network_decode(entry);
134403
135327
 
134404
135328
  return JSON.stringify({
134405
135329
  id: entry.id,
@@ -134410,11 +135334,12 @@ function makeNetworkDetailReadScript(requestId) {
134410
135334
  resourceType: entry.resourceType,
134411
135335
  encodedDataLength: entry.encodedDataLength,
134412
135336
  timestamp: entry.timestamp,
134413
- wallTime: entry.wallTime,
134414
135337
  durationMs: entry.durationMs,
134415
135338
  errorText: entry.errorText,
134416
- initiator: entry.initiator,
134417
- responseBody: entry.responseBody
135339
+ responseBody: entry.responseBody,
135340
+ bodyTruncated: entry.bodyTruncated,
135341
+ bodyLength: entry.bodyLength,
135342
+ bodyOmitted: entry.bodyOmitted
134418
135343
  });
134419
135344
  })()`;
134420
135345
  }
@@ -149700,35 +150625,83 @@ When the debugger cannot be reached, this tool does not fail: it returns { statu
149700
150625
  }
149701
150626
 
149702
150627
  // ../tool-server/src/tools/network/network-logs.ts
150628
+ var import_node_http4 = require("node:http");
149703
150629
  init_zod();
149704
150630
  var import_bytes3 = __toESM(require_bytes());
149705
150631
  init_device_info();
149706
150632
  var ITEMS_PER_PAGE = 50;
150633
+ function notInstalled(installError) {
150634
+ return installError ? `Network interceptor could not be installed in the app's JS runtime: ${installError}` : "Network interceptor not installed: the app's JS runtime reloaded, which clears captured requests. Call view-network-logs again to reinstall it, then repeat the action that sends the requests.";
150635
+ }
149707
150636
  function formatBytes2(bytes) {
149708
150637
  return (0, import_bytes3.default)(bytes, { decimalPlaces: 1, unitSeparator: " " }) ?? `${bytes} B`;
149709
150638
  }
149710
- function formatEntry(entry) {
149711
- const method = entry.request?.method ?? "???";
149712
- const url2 = entry.request?.url ?? "unknown";
149713
- let name;
150639
+ function reasonPhrase(status) {
150640
+ return import_node_http4.STATUS_CODES[status] ?? "";
150641
+ }
150642
+ function comparableUrl(url2) {
150643
+ let text = url2;
149714
150644
  try {
149715
150645
  const parsed = new URL(url2);
149716
- name = parsed.pathname === "/" ? parsed.hostname : parsed.pathname;
150646
+ parsed.hash = "";
150647
+ text = parsed.search ? parsed.href : parsed.href.replace(/\?$/, "");
150648
+ } catch {
150649
+ }
150650
+ text = text.replace(/\+/g, " ");
150651
+ for (let i = 0; i < 4; i++) {
150652
+ let decoded;
150653
+ try {
150654
+ decoded = decodeURI(text);
150655
+ } catch {
150656
+ break;
150657
+ }
150658
+ if (decoded === text) break;
150659
+ text = decoded;
150660
+ }
150661
+ return text.replace(/%[0-9a-f]{2}/gi, (escape2) => escape2.toUpperCase());
150662
+ }
150663
+ function isRedirect(requestUrl, finalUrl) {
150664
+ if (!finalUrl || requestUrl === void 0 || finalUrl === requestUrl) return false;
150665
+ return comparableUrl(finalUrl) !== comparableUrl(requestUrl);
150666
+ }
150667
+ function shortName(url2, from2) {
150668
+ try {
150669
+ const parsed = new URL(url2);
150670
+ const path53 = parsed.pathname === "/" ? "" : parsed.pathname;
150671
+ let sameHost = true;
150672
+ try {
150673
+ sameHost = from2 === void 0 || new URL(from2).host === parsed.host;
150674
+ } catch {
150675
+ sameHost = false;
150676
+ }
150677
+ if (sameHost) return path53 || parsed.hostname;
150678
+ return parsed.host + path53;
149717
150679
  } catch {
149718
- name = url2;
150680
+ return url2;
149719
150681
  }
150682
+ }
150683
+ function formatEntry(entry) {
150684
+ const method = entry.request?.method ?? "???";
150685
+ const url2 = entry.request?.url ?? "unknown";
150686
+ const name = shortName(url2);
150687
+ const finalUrl = entry.response?.url;
150688
+ const redirect = finalUrl && isRedirect(url2, finalUrl) ? `\u2192 ${shortName(finalUrl, url2)}` : "";
149720
150689
  let status;
149721
150690
  if (entry.state === "failed") {
149722
150691
  status = entry.errorText ?? "failed";
149723
150692
  } else if (entry.response) {
149724
- status = `${entry.response.status} ${entry.response.statusText}`;
150693
+ status = [
150694
+ entry.response.status,
150695
+ entry.response.statusText,
150696
+ entry.state === "pending" ? "(pending)" : ""
150697
+ ].filter((part) => part !== "").join(" ");
149725
150698
  } else {
149726
150699
  status = "pending";
149727
150700
  }
149728
150701
  const type = entry.resourceType ?? "";
149729
- const size = entry.encodedDataLength != null ? formatBytes2(entry.encodedDataLength) : "";
150702
+ const size = entry.encodedDataLength != null ? formatBytes2(entry.encodedDataLength) : entry.bodyLength != null ? `~${formatBytes2(entry.bodyLength)}` : "";
149730
150703
  const duration3 = entry.durationMs != null ? `${entry.durationMs} ms` : "";
149731
- return `{id: ${entry.requestId}} "${method} ${name}" ${status} ${type} ${size} ${duration3}`.trim();
150704
+ return [`{id: ${entry.requestId}}`, `"${method} ${name}"`, status, type, size, duration3, redirect].filter(Boolean).join(" ");
149732
150705
  }
149733
150706
  function recordToLogEntry(r, id) {
149734
150707
  return {
@@ -149746,7 +150719,7 @@ function recordToLogEntry(r, id) {
149746
150719
  function renderChromiumPage(records, pageIndexParam) {
149747
150720
  const total = records.length;
149748
150721
  if (total === 0) {
149749
- return "No network traffic captured. Recording is active on the active tab \u2014 navigate or reload the page to populate it (requests made before the device was attached are not captured).";
150722
+ return "No network traffic captured. Recording is active on the active tab. Navigate or reload the page to populate it (requests made before the device was attached are not captured).";
149750
150723
  }
149751
150724
  const pageCount = Math.ceil(total / ITEMS_PER_PAGE);
149752
150725
  const pageIndex = pageIndexParam === "latest" ? pageCount - 1 : pageIndexParam;
@@ -149777,8 +150750,9 @@ var networkLogsTool = {
149777
150750
  },
149778
150751
  description: `Retrieve captured network (HTTP) requests from the running app.
149779
150752
  Returns a paginated list of requests with method, URL, status, resource type, size, and duration.
150753
+ A size with "~" is estimated from the body's length in characters; on React Native, "\u2192 <url>" is the final URL after a redirect.
149780
150754
  Each entry includes a requestId that can be passed to view-network-request-details for full details.
149781
- On React Native (iOS / Android / Vega) interception is injected into the JS runtime: it captures fetch() calls. On Chromium it reads the browser's native CDP Network domain (the active tab; all request types).
150755
+ On React Native (iOS / Android / Vega) interception is injected into the JS runtime: it records each request once: XMLHttpRequest (axios included), React Native's fetch, Expo's fetch, and a fetch library that sends through React Native's network module itself, such as react-native-fetch-api (found in a Metro development build). Capture starts at the first network-tool call; a reload clears it. On Chromium it reads the browser's native CDP Network domain (the active tab; all request types).
149782
150756
  Use when inspecting outbound HTTP traffic or debugging API calls in the running app.
149783
150757
  Fails if the app is not connected (RN) or the device is not reachable (Chromium).`,
149784
150758
  zodSchema: zodSchema38,
@@ -149798,12 +150772,15 @@ Fails if the app is not connected (RN) or the device is not reachable (Chromium)
149798
150772
  return renderChromiumPage(chromium.server.network.requests(), params.pageIndex);
149799
150773
  }
149800
150774
  const api = services.inspector;
149801
- await api.cdp.evaluate(NETWORK_INTERCEPTOR_SCRIPT).catch(() => {
149802
- });
150775
+ const installError = await api.cdp.evaluate(NETWORK_INTERCEPTOR_SCRIPT).then(
150776
+ () => void 0,
150777
+ (err) => err instanceof Error ? err.message : String(err)
150778
+ );
149803
150779
  const countRaw = await api.cdp.evaluate(makeNetworkLogReadScript(0, 0, api.port));
149804
- const { total } = JSON.parse(countRaw);
150780
+ const { total, interceptorInstalled } = JSON.parse(countRaw);
150781
+ if (!interceptorInstalled) return notInstalled(installError);
149805
150782
  if (total === 0) {
149806
- return "No network traffic captured. Make sure the app is running and making HTTP requests. Network interception is active \u2014 it captures fetch() calls.";
150783
+ return "No network traffic captured. Make sure the app is running and making HTTP requests. Network interception is active: it records XMLHttpRequest (axios included), React Native's fetch, Expo's fetch, and a fetch library that sends through React Native's network module, but not requests sent before capture started (at the first network-tool call).";
149807
150784
  }
149808
150785
  const pageCount = Math.ceil(total / ITEMS_PER_PAGE);
149809
150786
  const pageIndex = params.pageIndex === "latest" ? pageCount - 1 : params.pageIndex;
@@ -149814,10 +150791,15 @@ Fails if the app is not connected (RN) or the device is not reachable (Chromium)
149814
150791
  const readScript = makeNetworkLogReadScript(start2, ITEMS_PER_PAGE, api.port);
149815
150792
  const raw = await api.cdp.evaluate(readScript);
149816
150793
  const data = JSON.parse(raw);
149817
- if (!data.interceptorInstalled) {
149818
- return "Network interceptor not installed. Try reconnecting with network-inspector-connect.";
149819
- }
149820
- const lines = data.entries.map(formatEntry);
150794
+ if (!data.interceptorInstalled) return notInstalled(installError);
150795
+ const lines = data.entries.map(
150796
+ (entry) => formatEntry(
150797
+ entry.response ? {
150798
+ ...entry,
150799
+ response: { ...entry.response, statusText: reasonPhrase(entry.response.status) }
150800
+ } : entry
150801
+ )
150802
+ );
149821
150803
  return `=== NETWORK LOGS (page ${pageIndex + 1}/${pageCount}, ${data.total} total) ===
149822
150804
 
149823
150805
  ${lines.join("\n")}`;
@@ -149840,19 +150822,37 @@ var SENSITIVE_HEADER_PATTERNS = [
149840
150822
  "apikey",
149841
150823
  "x-api-key"
149842
150824
  ];
149843
- function isSensitiveHeader(name) {
150825
+ function isSensitiveHeader(name, value) {
149844
150826
  const lower = name.toLowerCase();
149845
- return SENSITIVE_HEADER_PATTERNS.some((p) => lower.includes(p));
150827
+ if (SENSITIVE_HEADER_PATTERNS.some((p) => lower.includes(p))) return true;
150828
+ return /^\d+$/.test(name) && value != null && isSensitiveHeader(String(value).split(",")[0].trim());
149846
150829
  }
149847
150830
  function redactHeaders(headers) {
149848
150831
  if (!headers) return {};
149849
150832
  const result = {};
149850
150833
  for (const [key2, value] of Object.entries(headers)) {
149851
- result[key2] = isSensitiveHeader(key2) ? "[REDACTED]" : value;
150834
+ result[key2] = isSensitiveHeader(key2, value) ? "[REDACTED]" : value;
149852
150835
  }
149853
150836
  return result;
149854
150837
  }
149855
150838
  var MAX_BODY_SIZE = 1e3;
150839
+ function cutMarker(fullSize, mimeType) {
150840
+ const mime = mimeType !== void 0 ? `, MIME: ${mimeType}` : "";
150841
+ return `[TRUNCATED: first ${MAX_BODY_SIZE} chars shown, full size: ${fullSize}${mime}]`;
150842
+ }
150843
+ function utf8Text(bytes) {
150844
+ try {
150845
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
150846
+ } catch {
150847
+ return bytes;
150848
+ }
150849
+ }
150850
+ function truncatePostData(postData, truncated) {
150851
+ if (postData == null || postData.length <= MAX_BODY_SIZE) return postData;
150852
+ const fullSize = truncated ? `more than ${postData.length} chars` : `${postData.length} chars`;
150853
+ return `${cutMarker(fullSize)}
150854
+ ${postData.slice(0, MAX_BODY_SIZE)}...`;
150855
+ }
149856
150856
  var zodSchema39 = external_exports.object({
149857
150857
  port: metroPortField,
149858
150858
  device_id: external_exports.string().describe(
@@ -149869,9 +150869,9 @@ var networkRequestTool = {
149869
150869
  failedMsg: ({ params, failureSignal: failureSignal2 }) => `Failed to read network request ${params.requestId}: ${failureSignal2.error_code}`
149870
150870
  },
149871
150871
  description: `Get full details of a specific network request by its requestId (from view-network-logs).
149872
- Returns request/response headers (sensitive headers redacted), status, timing, and optionally the response body.
149873
- Large response bodies are truncated. Use when you need headers, body, or timing for a specific request after listing logs.
149874
- Returns an error message string if the requestId is not found; use view-network-logs to get valid requestId values.`,
150872
+ Returns request/response headers (sensitive headers redacted), status, timing, the request body, and optionally the response body.
150873
+ Request and response bodies over 1000 chars are cut to 1000 under a "[TRUNCATED: ...]" line that gives the full size; a binary response body is replaced by "[body omitted: binary <mime>, N bytes]". Use when you need headers, body, or timing for a specific request after listing logs.
150874
+ Returns an error message string if the requestId is not found: use view-network-logs to get valid requestId values.`,
149875
150875
  zodSchema: zodSchema39,
149876
150876
  capability: DEBUGGER_TOOL_CAPABILITY,
149877
150877
  services: (params) => {
@@ -149904,7 +150904,7 @@ Returns an error message string if the requestId is not found; use view-network-
149904
150904
  url: rec.url,
149905
150905
  method: rec.method,
149906
150906
  headers: redactHeaders(rec.requestHeaders),
149907
- postData: rec.postData
150907
+ postData: truncatePostData(rec.postData)
149908
150908
  };
149909
150909
  }
149910
150910
  if (rec.status != null) {
@@ -149920,9 +150920,13 @@ Returns an error message string if the requestId is not found; use view-network-
149920
150920
  requestId: rec.requestId
149921
150921
  });
149922
150922
  if (out.body != null) {
149923
- const body = out.base64Encoded ? Buffer.from(out.body, "base64").toString("utf8") : out.body;
149924
- resp.body = body.length > MAX_BODY_SIZE ? `[TRUNCATED \u2014 original size: ${body.length} chars, MIME: ${resp.mimeType}]
150923
+ const body = out.base64Encoded ? utf8Text(Buffer.from(out.body, "base64")) : out.body;
150924
+ if (typeof body !== "string") {
150925
+ resp.body = `[body omitted: binary ${resp.mimeType || "data"}, ${body.length} bytes]`;
150926
+ } else {
150927
+ resp.body = body.length > MAX_BODY_SIZE ? `${cutMarker(`${body.length} chars`, resp.mimeType)}
149925
150928
  ${body.slice(0, MAX_BODY_SIZE)}...` : body;
150929
+ }
149926
150930
  }
149927
150931
  } catch {
149928
150932
  }
@@ -149947,28 +150951,34 @@ ${body.slice(0, MAX_BODY_SIZE)}...` : body;
149947
150951
  resourceType: entry.resourceType,
149948
150952
  durationMs: entry.durationMs,
149949
150953
  encodedDataLength: entry.encodedDataLength,
149950
- errorText: entry.errorText,
149951
- initiator: entry.initiator
150954
+ errorText: entry.errorText
149952
150955
  };
149953
150956
  if (entry.request) {
149954
150957
  details.request = {
149955
150958
  url: entry.request.url,
149956
150959
  method: entry.request.method,
149957
150960
  headers: redactHeaders(entry.request.headers),
149958
- postData: entry.request.postData
150961
+ postData: truncatePostData(entry.request.postData, entry.request.postDataTruncated)
149959
150962
  };
149960
150963
  }
149961
150964
  if (entry.response) {
149962
150965
  const resp = {
149963
150966
  status: entry.response.status,
149964
- statusText: entry.response.statusText,
150967
+ statusText: reasonPhrase(entry.response.status),
149965
150968
  headers: redactHeaders(entry.response.headers),
149966
150969
  mimeType: entry.response.mimeType
149967
150970
  };
149968
- if (params.includeBody && entry.responseBody != null) {
150971
+ if (isRedirect(entry.request?.url, entry.response.url)) {
150972
+ resp.redirectedTo = entry.response.url;
150973
+ }
150974
+ if (params.includeBody && entry.bodyOmitted) {
150975
+ const size = entry.encodedDataLength != null ? `, ${entry.encodedDataLength} bytes` : entry.bodyLength != null ? `, ~${entry.bodyLength} bytes` : "";
150976
+ resp.body = `[body omitted: binary ${entry.response.mimeType || "data"}${size}]`;
150977
+ } else if (params.includeBody && entry.responseBody != null) {
149969
150978
  const body = entry.responseBody;
149970
150979
  if (body.length > MAX_BODY_SIZE) {
149971
- resp.body = `[TRUNCATED \u2014 original size: ${body.length} chars, MIME: ${entry.response.mimeType}]
150980
+ const fullSize = !entry.bodyTruncated ? `${body.length} chars` : entry.encodedDataLength != null ? `${entry.encodedDataLength} bytes` : entry.bodyLength != null ? `${entry.bodyLength} chars` : `more than ${body.length} chars`;
150981
+ resp.body = `${cutMarker(fullSize, entry.response.mimeType)}
149972
150982
  ${body.slice(0, MAX_BODY_SIZE)}...`;
149973
150983
  } else {
149974
150984
  resp.body = body;