@ansight/capacitor 1.3.0-preview.9 → 1.4.0-preview.3

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.
Files changed (42) hide show
  1. package/AnsightCapacitor.podspec +1 -1
  2. package/Package.swift +1 -1
  3. package/README.md +116 -29
  4. package/android/build.gradle +2 -2
  5. package/android/src/main/kotlin/ai/ansight/capacitor/AnsightCapacitorPlugin.kt +75 -22
  6. package/dist/esm/definitions.d.ts +74 -10
  7. package/dist/esm/definitions.d.ts.map +1 -1
  8. package/dist/esm/dom.d.ts +11 -0
  9. package/dist/esm/dom.d.ts.map +1 -1
  10. package/dist/esm/dom.js +73 -23
  11. package/dist/esm/dom.js.map +1 -1
  12. package/dist/esm/index.d.ts +10 -1
  13. package/dist/esm/index.d.ts.map +1 -1
  14. package/dist/esm/index.js +113 -5
  15. package/dist/esm/index.js.map +1 -1
  16. package/dist/esm/network.d.ts +6 -0
  17. package/dist/esm/network.d.ts.map +1 -0
  18. package/dist/esm/network.js +742 -0
  19. package/dist/esm/network.js.map +1 -0
  20. package/dist/esm/options.d.ts +7 -1
  21. package/dist/esm/options.d.ts.map +1 -1
  22. package/dist/esm/options.js +44 -0
  23. package/dist/esm/options.js.map +1 -1
  24. package/dist/esm/session-properties.d.ts +16 -0
  25. package/dist/esm/session-properties.d.ts.map +1 -0
  26. package/dist/esm/session-properties.js +123 -0
  27. package/dist/esm/session-properties.js.map +1 -0
  28. package/dist/esm/standalone-options.d.ts +3 -0
  29. package/dist/esm/standalone-options.d.ts.map +1 -0
  30. package/dist/esm/standalone-options.js +15 -0
  31. package/dist/esm/standalone-options.js.map +1 -0
  32. package/dist/esm/standalone.d.ts.map +1 -1
  33. package/dist/esm/standalone.js +2 -11
  34. package/dist/esm/standalone.js.map +1 -1
  35. package/dist/plugin.cjs.js +1096 -28
  36. package/dist/plugin.cjs.js.map +1 -1
  37. package/dist/plugin.js +1096 -28
  38. package/dist/plugin.js.map +1 -1
  39. package/dist/standalone.js +1099 -31
  40. package/dist/standalone.js.map +1 -1
  41. package/ios/Sources/AnsightCapacitorPlugin/AnsightCapacitorPlugin.swift +77 -22
  42. package/package.json +1 -1
@@ -0,0 +1,742 @@
1
+ export const networkRequestSchema = "ansight.network-request.v1";
2
+ export const redactedNetworkValue = "<redacted>";
3
+ const maximumHeaderCount = 128;
4
+ const maximumHeaderValueLength = 4096;
5
+ const maximumErrorMessageLength = 4096;
6
+ const maximumUrlLength = 16384;
7
+ const defaultMaximumBodyBytes = 64 * 1024;
8
+ const sensitiveHeaderNames = new Set([
9
+ "authorization",
10
+ "cookie",
11
+ "proxy-authorization",
12
+ "set-cookie",
13
+ "x-api-key",
14
+ "x-auth-token",
15
+ ]);
16
+ const sensitiveQueryNames = new Set([
17
+ "access_token",
18
+ "accesskey",
19
+ "access_key",
20
+ "api_key",
21
+ "apikey",
22
+ "auth",
23
+ "authorization",
24
+ "client_secret",
25
+ "code",
26
+ "credential",
27
+ "credentials",
28
+ "id_token",
29
+ "jwt",
30
+ "key",
31
+ "password",
32
+ "passwd",
33
+ "refresh_token",
34
+ "sas",
35
+ "sastoken",
36
+ "secret",
37
+ "secret_key",
38
+ "security_token",
39
+ "session_token",
40
+ "sig",
41
+ "signature",
42
+ "token",
43
+ ]);
44
+ const azureSasFingerprintNames = new Set([
45
+ "se",
46
+ "skoid",
47
+ "sp",
48
+ "sr",
49
+ "srt",
50
+ "ss",
51
+ "sv",
52
+ ]);
53
+ const azureSasQueryNames = new Set([
54
+ "epk",
55
+ "erk",
56
+ "rscc",
57
+ "rscd",
58
+ "rsce",
59
+ "rscl",
60
+ "rsct",
61
+ "saoid",
62
+ "scid",
63
+ "se",
64
+ "sig",
65
+ "si",
66
+ "sip",
67
+ "ske",
68
+ "skoid",
69
+ "sks",
70
+ "skt",
71
+ "sktid",
72
+ "skv",
73
+ "snapshot",
74
+ "sp",
75
+ "spk",
76
+ "spr",
77
+ "sr",
78
+ "srk",
79
+ "srt",
80
+ "ss",
81
+ "st",
82
+ "suoid",
83
+ "tn",
84
+ "versionid",
85
+ "sv",
86
+ ]);
87
+ function truncate(value, maximumLength) {
88
+ const text = String(value);
89
+ return text.length <= maximumLength
90
+ ? text
91
+ : `${text.slice(0, maximumLength)}…`;
92
+ }
93
+ function normalizeRequired(value, fallback, maximumLength) {
94
+ const normalized = value == null ? "" : String(value).trim();
95
+ return truncate(normalized || fallback, maximumLength);
96
+ }
97
+ function normalizeOptional(value, maximumLength) {
98
+ if (value == null)
99
+ return undefined;
100
+ const normalized = String(value).trim();
101
+ return normalized ? truncate(normalized, maximumLength) : undefined;
102
+ }
103
+ function lowercaseSet(values) {
104
+ return new Set((values ?? []).map((value) => value.toLowerCase()));
105
+ }
106
+ function isSensitiveHeader(name, options) {
107
+ const lowered = name.toLowerCase();
108
+ if (sensitiveHeaderNames.has(lowered) ||
109
+ lowercaseSet(options.additionalSensitiveHeaderNames).has(lowered)) {
110
+ return true;
111
+ }
112
+ const compact = lowered.replaceAll("-", "");
113
+ return (compact.includes("token") ||
114
+ compact.includes("secret") ||
115
+ compact.includes("apikey"));
116
+ }
117
+ function headerEntries(headers) {
118
+ if (!headers)
119
+ return [];
120
+ if (Array.isArray(headers)) {
121
+ return headers.flatMap((header) => {
122
+ if (Array.isArray(header))
123
+ return [[header[0], header[1]]];
124
+ if (header && typeof header === "object") {
125
+ const value = header;
126
+ return [[value.name, value.value]];
127
+ }
128
+ return [];
129
+ });
130
+ }
131
+ if (typeof headers.forEach === "function") {
132
+ const entries = [];
133
+ headers.forEach((value, name) => entries.push([name, value]));
134
+ return entries;
135
+ }
136
+ return typeof headers === "object" ? Object.entries(headers) : [];
137
+ }
138
+ function sanitizeHeaders(headers, options) {
139
+ return headerEntries(headers)
140
+ .filter(([name]) => name != null && String(name).trim())
141
+ .slice(0, maximumHeaderCount)
142
+ .map(([rawName, rawValue]) => {
143
+ const name = normalizeRequired(rawName, "Header", 256);
144
+ return {
145
+ name,
146
+ value: isSensitiveHeader(name, options)
147
+ ? redactedNetworkValue
148
+ : normalizeRequired(rawValue, "", maximumHeaderValueLength),
149
+ };
150
+ });
151
+ }
152
+ function sanitizeQuery(query, options) {
153
+ const appSensitive = lowercaseSet(options.additionalSensitiveQueryParameterNames);
154
+ const pairs = query.split("&");
155
+ const decodedNames = new Set(pairs.map((pair) => decodeQueryName(pair).toLowerCase()));
156
+ const hasAzureSas = decodedNames.has("sig") &&
157
+ [...azureSasFingerprintNames].some((name) => decodedNames.has(name));
158
+ const hasAwsSignature = decodedNames.has("x-amz-signature");
159
+ const hasGoogleSignature = decodedNames.has("x-goog-signature");
160
+ const hasCloudFrontSignature = decodedNames.has("signature") &&
161
+ ["key-pair-id", "policy", "expires"].some((name) => decodedNames.has(name));
162
+ const hasLegacyGoogleSignature = decodedNames.has("signature") && decodedNames.has("googleaccessid");
163
+ const hasAlibabaSignature = (decodedNames.has("signature") && decodedNames.has("ossaccesskeyid")) ||
164
+ decodedNames.has("x-oss-signature");
165
+ return pairs
166
+ .map((pair) => {
167
+ const equalsIndex = pair.indexOf("=");
168
+ const encodedName = equalsIndex < 0 ? pair : pair.slice(0, equalsIndex);
169
+ const decodedName = decodeQueryName(pair);
170
+ const lowered = decodedName.toLowerCase();
171
+ const providerSensitive = (hasAzureSas && azureSasQueryNames.has(lowered)) ||
172
+ (hasAwsSignature && lowered.startsWith("x-amz-")) ||
173
+ (hasGoogleSignature && lowered.startsWith("x-goog-")) ||
174
+ (hasCloudFrontSignature &&
175
+ [
176
+ "signature",
177
+ "key-pair-id",
178
+ "policy",
179
+ "expires",
180
+ "hash-algorithm",
181
+ ].includes(lowered)) ||
182
+ (hasLegacyGoogleSignature &&
183
+ ["signature", "googleaccessid", "expires"].includes(lowered)) ||
184
+ (hasAlibabaSignature &&
185
+ (lowered.startsWith("x-oss-") ||
186
+ ["signature", "ossaccesskeyid", "security-token"].includes(lowered)));
187
+ return providerSensitive ||
188
+ sensitiveQueryNames.has(lowered) ||
189
+ appSensitive.has(lowered)
190
+ ? `${encodedName}=${encodeURIComponent(redactedNetworkValue)}`
191
+ : pair;
192
+ })
193
+ .join("&");
194
+ }
195
+ function decodeQueryName(pair) {
196
+ const equalsIndex = pair.indexOf("=");
197
+ const encodedName = equalsIndex < 0 ? pair : pair.slice(0, equalsIndex);
198
+ try {
199
+ return decodeURIComponent(encodedName.replaceAll("+", " "));
200
+ }
201
+ catch {
202
+ return encodedName;
203
+ }
204
+ }
205
+ function sanitizeUrl(value, options) {
206
+ let normalized = normalizeRequired(value, "<unknown>", maximumUrlLength);
207
+ normalized = normalized.replace(/^(https?:\/\/)[^/@]+@/i, `$1${redactedNetworkValue}@`);
208
+ const queryIndex = normalized.indexOf("?");
209
+ if (queryIndex < 0)
210
+ return truncate(normalized, maximumUrlLength);
211
+ const fragmentIndex = normalized.indexOf("#", queryIndex);
212
+ if (options.includeQueryString === false) {
213
+ return truncate(normalized.slice(0, queryIndex) +
214
+ (fragmentIndex < 0 ? "" : normalized.slice(fragmentIndex)), maximumUrlLength);
215
+ }
216
+ const queryEnd = fragmentIndex < 0 ? normalized.length : fragmentIndex;
217
+ return truncate(normalized.slice(0, queryIndex + 1) +
218
+ sanitizeQuery(normalized.slice(queryIndex + 1, queryEnd), options) +
219
+ (fragmentIndex < 0 ? "" : normalized.slice(fragmentIndex)), maximumUrlLength);
220
+ }
221
+ function sanitizeErrorMessage(value, options) {
222
+ const normalized = normalizeOptional(value, maximumErrorMessageLength);
223
+ if (!normalized)
224
+ return undefined;
225
+ return truncate(normalized
226
+ .replace(/(access_token|api_key|apikey|auth|authorization|code|key|password|passwd|secret|signature|token)(\s*=\s*)([^&\s,;]+)/gi, `$1$2${redactedNetworkValue}`)
227
+ .replace(/https?:\/\/[^\s"'<>]+/gi, (url) => sanitizeUrl(url, options)), maximumErrorMessageLength);
228
+ }
229
+ function normalizeTimestamp(value, fallback) {
230
+ const date = new Date(value == null ? fallback : String(value));
231
+ return Number.isFinite(date.valueOf()) ? date.toISOString() : fallback;
232
+ }
233
+ function generateId(globalObject) {
234
+ if (typeof globalObject.crypto?.randomUUID === "function") {
235
+ return globalObject.crypto.randomUUID().replaceAll("-", "");
236
+ }
237
+ return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
238
+ }
239
+ function normalizeSize(value) {
240
+ const number = Number(value);
241
+ return Number.isFinite(number) && number >= 0
242
+ ? Math.round(number)
243
+ : undefined;
244
+ }
245
+ function maximumBodyBytes(options) {
246
+ const configured = Number(options.maximumBodyBytes);
247
+ const value = Number.isFinite(configured)
248
+ ? Math.round(configured)
249
+ : defaultMaximumBodyBytes;
250
+ return Math.max(0, value);
251
+ }
252
+ function sanitizeSensitiveText(value, options) {
253
+ return value
254
+ .replace(/(access_token|accesskey|access_key|api_key|apikey|auth|authorization|client_secret|code|credential|credentials|id_token|jwt|key|password|passwd|refresh_token|sas|sastoken|secret|secret_key|security_token|session_token|sig|signature|token)(["']?\s*[:=]\s*["']?)([^&\s,;}"']+)/gi, `$1$2${redactedNetworkValue}`)
255
+ .replace(/https?:\/\/[^\s"'<>]+/gi, (url) => sanitizeUrl(url, options));
256
+ }
257
+ function truncateUtf8(bytes, maximum) {
258
+ let length = Math.min(bytes.length, maximum);
259
+ const decoder = new TextDecoder("utf-8", { fatal: true });
260
+ while (length > 0) {
261
+ try {
262
+ decoder.decode(bytes.slice(0, length));
263
+ return bytes.slice(0, length);
264
+ }
265
+ catch {
266
+ length -= 1;
267
+ }
268
+ }
269
+ return new Uint8Array();
270
+ }
271
+ function bytesToBase64(bytes) {
272
+ let binary = "";
273
+ for (const byte of bytes)
274
+ binary += String.fromCharCode(byte);
275
+ return btoa(binary);
276
+ }
277
+ function base64ToBytes(value) {
278
+ const binary = atob(value);
279
+ return Uint8Array.from(binary, (character) => character.charCodeAt(0));
280
+ }
281
+ function normalizeBody(body, options) {
282
+ const maximum = maximumBodyBytes(options);
283
+ if (!body || maximum <= 0)
284
+ return undefined;
285
+ const encoding = body.encoding?.toLowerCase();
286
+ let bytes;
287
+ try {
288
+ if (encoding === "utf8") {
289
+ bytes = new TextEncoder().encode(sanitizeSensitiveText(body.data, options));
290
+ }
291
+ else if (encoding === "base64" && options.captureBinaryBodies === true) {
292
+ bytes = base64ToBytes(body.data);
293
+ }
294
+ else {
295
+ return undefined;
296
+ }
297
+ }
298
+ catch {
299
+ return undefined;
300
+ }
301
+ const originalLength = bytes.length;
302
+ const captured = encoding === "utf8"
303
+ ? truncateUtf8(bytes, maximum)
304
+ : bytes.slice(0, maximum);
305
+ const totalBytes = normalizeSize(body.totalBytes);
306
+ return {
307
+ contentType: normalizeOptional(body.contentType, 512),
308
+ encoding,
309
+ data: encoding === "base64"
310
+ ? bytesToBase64(captured)
311
+ : new TextDecoder().decode(captured),
312
+ capturedBytes: captured.length,
313
+ totalBytes,
314
+ truncated: body.truncated ||
315
+ originalLength > captured.length ||
316
+ (totalBytes != null && totalBytes > captured.length),
317
+ };
318
+ }
319
+ function normalizeRecord(input, options, globalObject) {
320
+ const now = new Date().toISOString();
321
+ const startedAtUtc = normalizeTimestamp(input.startedAtUtc, now);
322
+ const completedAtUtc = normalizeTimestamp(input.completedAtUtc, startedAtUtc);
323
+ const duration = Number(input.durationMilliseconds);
324
+ return {
325
+ schema: networkRequestSchema,
326
+ id: normalizeRequired(input.id, generateId(globalObject), 128),
327
+ source: normalizeRequired(input.source, "unknown", 128),
328
+ startedAtUtc,
329
+ completedAtUtc: completedAtUtc < startedAtUtc ? startedAtUtc : completedAtUtc,
330
+ durationMilliseconds: Number.isFinite(duration) && duration >= 0 ? duration : 0,
331
+ method: normalizeRequired(input.method, "GET", 32).toUpperCase(),
332
+ url: sanitizeUrl(input.url, options),
333
+ protocol: normalizeOptional(input.protocol, 64),
334
+ requestHeaders: options.includeRequestHeaders === false
335
+ ? []
336
+ : sanitizeHeaders(input.requestHeaders, options),
337
+ requestBodySizeBytes: options.includeBodySizes === false
338
+ ? undefined
339
+ : normalizeSize(input.requestBodySizeBytes),
340
+ requestBody: options.captureRequestBody !== false
341
+ ? normalizeBody(input.requestBody, options)
342
+ : undefined,
343
+ statusCode: Number.isInteger(Number(input.statusCode)) &&
344
+ Number(input.statusCode) >= 100 &&
345
+ Number(input.statusCode) <= 999
346
+ ? Number(input.statusCode)
347
+ : undefined,
348
+ reasonPhrase: normalizeOptional(input.reasonPhrase, 512),
349
+ responseHeaders: options.includeResponseHeaders === false
350
+ ? []
351
+ : sanitizeHeaders(input.responseHeaders, options),
352
+ responseBodySizeBytes: options.includeBodySizes === false
353
+ ? undefined
354
+ : normalizeSize(input.responseBodySizeBytes),
355
+ responseBody: options.captureResponseBody !== false
356
+ ? normalizeBody(input.responseBody, options)
357
+ : undefined,
358
+ errorType: normalizeOptional(input.errorType, 512),
359
+ errorMessage: sanitizeErrorMessage(input.errorMessage, options),
360
+ };
361
+ }
362
+ export function sanitizeNetworkRequest(input, options = {}, globalObject = globalThis) {
363
+ try {
364
+ let normalized = normalizeRecord(input, options, globalObject);
365
+ if (options.urlSanitizer) {
366
+ normalized = normalizeRecord({ ...normalized, url: options.urlSanitizer(normalized.url) }, options, globalObject);
367
+ }
368
+ if (options.requestSanitizer) {
369
+ const transformed = options.requestSanitizer(normalized);
370
+ if (transformed == null)
371
+ return null;
372
+ normalized = normalizeRecord(transformed, options, globalObject);
373
+ }
374
+ return normalized;
375
+ }
376
+ catch {
377
+ return null;
378
+ }
379
+ }
380
+ function parseContentLength(headers) {
381
+ const entry = headerEntries(headers).find(([name]) => String(name).toLowerCase() === "content-length");
382
+ return entry ? normalizeSize(entry[1]) : undefined;
383
+ }
384
+ function headerValue(headers, wantedName) {
385
+ const entry = headerEntries(headers).find(([name]) => String(name).toLowerCase() === wantedName);
386
+ return entry == null ? undefined : String(entry[1]);
387
+ }
388
+ function isTextContentType(contentType) {
389
+ if (!contentType)
390
+ return true;
391
+ const mediaType = contentType.split(";", 1)[0].trim().toLowerCase();
392
+ return (mediaType.startsWith("text/") ||
393
+ mediaType.endsWith("+json") ||
394
+ mediaType.endsWith("+xml") ||
395
+ [
396
+ "application/json",
397
+ "application/xml",
398
+ "application/graphql",
399
+ "application/javascript",
400
+ "application/x-www-form-urlencoded",
401
+ ].includes(mediaType));
402
+ }
403
+ function bodyFromBytes(bytes, totalBytes, contentType, options) {
404
+ const binary = !isTextContentType(contentType);
405
+ if (binary && options.captureBinaryBodies !== true)
406
+ return undefined;
407
+ const maximum = maximumBodyBytes(options);
408
+ if (maximum <= 0)
409
+ return undefined;
410
+ const captured = binary
411
+ ? bytes.slice(0, maximum)
412
+ : truncateUtf8(bytes, maximum);
413
+ return {
414
+ contentType,
415
+ encoding: binary ? "base64" : "utf8",
416
+ data: binary ? bytesToBase64(captured) : new TextDecoder().decode(captured),
417
+ capturedBytes: captured.length,
418
+ totalBytes,
419
+ truncated: bytes.length > captured.length ||
420
+ (totalBytes != null && totalBytes > captured.length),
421
+ };
422
+ }
423
+ function bodyFromValue(value, headers, options) {
424
+ if (value == null || options.captureRequestBody === false)
425
+ return undefined;
426
+ const contentType = headerValue(headers, "content-type");
427
+ if (typeof value === "string" || value instanceof URLSearchParams) {
428
+ const bytes = new TextEncoder().encode(String(value));
429
+ return bodyFromBytes(bytes, bytes.length, contentType ||
430
+ (value instanceof URLSearchParams
431
+ ? "application/x-www-form-urlencoded"
432
+ : undefined), options);
433
+ }
434
+ if (value instanceof ArrayBuffer) {
435
+ const bytes = new Uint8Array(value);
436
+ return bodyFromBytes(bytes, bytes.length, contentType, options);
437
+ }
438
+ if (ArrayBuffer.isView(value)) {
439
+ const bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
440
+ return bodyFromBytes(bytes, bytes.length, contentType, options);
441
+ }
442
+ return undefined;
443
+ }
444
+ async function bodyFromFetchResponse(response, headers, options, shouldContinue = () => true) {
445
+ if (!shouldContinue() || options.captureResponseBody === false)
446
+ return undefined;
447
+ const contentType = headerValue(headers, "content-type");
448
+ if (!isTextContentType(contentType) && options.captureBinaryBodies !== true) {
449
+ return undefined;
450
+ }
451
+ const totalBytes = parseContentLength(headers);
452
+ const maximum = maximumBodyBytes(options);
453
+ if (maximum <= 0)
454
+ return undefined;
455
+ const clone = response.clone();
456
+ if (clone.body) {
457
+ const reader = clone.body.getReader();
458
+ const chunks = [];
459
+ let capturedLength = 0;
460
+ let observedLength = 0;
461
+ try {
462
+ while (capturedLength <= maximum) {
463
+ if (!shouldContinue()) {
464
+ await reader.cancel().catch(() => undefined);
465
+ return undefined;
466
+ }
467
+ const result = await reader.read();
468
+ if (!shouldContinue()) {
469
+ await reader.cancel().catch(() => undefined);
470
+ return undefined;
471
+ }
472
+ if (result.done)
473
+ break;
474
+ const chunk = result.value;
475
+ observedLength += chunk.length;
476
+ const remaining = maximum - capturedLength;
477
+ if (remaining > 0) {
478
+ const kept = chunk.slice(0, remaining);
479
+ chunks.push(kept);
480
+ capturedLength += kept.length;
481
+ }
482
+ if (observedLength > maximum) {
483
+ await reader.cancel().catch(() => undefined);
484
+ break;
485
+ }
486
+ }
487
+ }
488
+ finally {
489
+ reader.releaseLock();
490
+ }
491
+ const joined = new Uint8Array(capturedLength);
492
+ let offset = 0;
493
+ for (const chunk of chunks) {
494
+ joined.set(chunk, offset);
495
+ offset += chunk.length;
496
+ }
497
+ return bodyFromBytes(joined, totalBytes ?? observedLength, contentType, options);
498
+ }
499
+ if (totalBytes == null || totalBytes > maximum)
500
+ return undefined;
501
+ const bytes = new Uint8Array(await clone.arrayBuffer());
502
+ if (!shouldContinue())
503
+ return undefined;
504
+ return bodyFromBytes(bytes, totalBytes, contentType, options);
505
+ }
506
+ function parseXhrResponseHeaders(value) {
507
+ return value
508
+ .trim()
509
+ .split(/[\r\n]+/)
510
+ .flatMap((line) => {
511
+ const separator = line.indexOf(":");
512
+ return separator < 0
513
+ ? []
514
+ : [
515
+ {
516
+ name: line.slice(0, separator),
517
+ value: line.slice(separator + 1),
518
+ },
519
+ ];
520
+ });
521
+ }
522
+ function monotonicNow(globalObject) {
523
+ return typeof globalObject.performance?.now === "function"
524
+ ? globalObject.performance.now()
525
+ : Date.now();
526
+ }
527
+ export function installBrowserNetworkCapture(capture, options = {}, sourcePrefix = "capacitor", globalObject = globalThis) {
528
+ const cleanups = [];
529
+ let active = true;
530
+ let fetchInvocationDepth = 0;
531
+ if (options.captureFetch !== false &&
532
+ typeof globalObject.fetch === "function") {
533
+ const originalFetch = globalObject.fetch;
534
+ const wrappedFetch = function (input, init) {
535
+ const startedAtUtc = new Date().toISOString();
536
+ const started = monotonicNow(globalObject);
537
+ const inputRequest = typeof input === "object" && "headers" in input && "method" in input
538
+ ? input
539
+ : undefined;
540
+ const requestHeaders = headerEntries(inputRequest?.headers).concat(headerEntries(init?.headers));
541
+ const requestBody = bodyFromValue(init?.body, requestHeaders, options);
542
+ const method = init?.method || inputRequest?.method || "GET";
543
+ const url = typeof input === "string" ? input : inputRequest?.url || String(input);
544
+ let promise;
545
+ fetchInvocationDepth += 1;
546
+ try {
547
+ promise = originalFetch(input, init);
548
+ }
549
+ catch (error) {
550
+ fetchInvocationDepth -= 1;
551
+ const request = sanitizeNetworkRequest({
552
+ source: `${sourcePrefix}.fetch`,
553
+ startedAtUtc,
554
+ completedAtUtc: new Date().toISOString(),
555
+ durationMilliseconds: monotonicNow(globalObject) - started,
556
+ method,
557
+ url,
558
+ requestHeaders: sanitizeHeaders(requestHeaders, {}),
559
+ requestBodySizeBytes: parseContentLength(requestHeaders) ?? requestBody?.totalBytes,
560
+ requestBody,
561
+ errorType: error instanceof Error ? error.name : "Error",
562
+ errorMessage: error instanceof Error ? error.message : String(error),
563
+ }, options, globalObject);
564
+ if (active && request)
565
+ Promise.resolve(capture(request)).catch(() => undefined);
566
+ throw error;
567
+ }
568
+ fetchInvocationDepth -= 1;
569
+ return promise.then((response) => {
570
+ if (!active)
571
+ return response;
572
+ const responseHeaders = headerEntries(response.headers);
573
+ const responseRecord = {
574
+ source: `${sourcePrefix}.fetch`,
575
+ startedAtUtc,
576
+ completedAtUtc: new Date().toISOString(),
577
+ durationMilliseconds: monotonicNow(globalObject) - started,
578
+ method,
579
+ url: response.url || url,
580
+ requestHeaders: sanitizeHeaders(requestHeaders, {}),
581
+ requestBodySizeBytes: parseContentLength(requestHeaders) ?? requestBody?.totalBytes,
582
+ requestBody,
583
+ statusCode: response.status,
584
+ reasonPhrase: response.statusText,
585
+ responseHeaders: sanitizeHeaders(responseHeaders, {}),
586
+ responseBodySizeBytes: parseContentLength(responseHeaders),
587
+ };
588
+ return bodyFromFetchResponse(response, responseHeaders, options, () => active).then((responseBody) => {
589
+ const request = sanitizeNetworkRequest({
590
+ ...responseRecord,
591
+ responseBody,
592
+ responseBodySizeBytes: responseRecord.responseBodySizeBytes ??
593
+ responseBody?.totalBytes,
594
+ }, options, globalObject);
595
+ if (active && request)
596
+ void Promise.resolve(capture(request)).catch(() => undefined);
597
+ return response;
598
+ }, () => {
599
+ const request = sanitizeNetworkRequest(responseRecord, options, globalObject);
600
+ if (active && request)
601
+ void Promise.resolve(capture(request)).catch(() => undefined);
602
+ return response;
603
+ });
604
+ }, (error) => {
605
+ const request = sanitizeNetworkRequest({
606
+ source: `${sourcePrefix}.fetch`,
607
+ startedAtUtc,
608
+ completedAtUtc: new Date().toISOString(),
609
+ durationMilliseconds: monotonicNow(globalObject) - started,
610
+ method,
611
+ url,
612
+ requestHeaders: sanitizeHeaders(requestHeaders, {}),
613
+ requestBodySizeBytes: parseContentLength(requestHeaders) ?? requestBody?.totalBytes,
614
+ requestBody,
615
+ errorType: error instanceof Error ? error.name : "Error",
616
+ errorMessage: error instanceof Error ? error.message : String(error),
617
+ }, options, globalObject);
618
+ if (active && request)
619
+ Promise.resolve(capture(request)).catch(() => undefined);
620
+ throw error;
621
+ });
622
+ };
623
+ globalObject.fetch = wrappedFetch;
624
+ cleanups.push(() => {
625
+ if (globalObject.fetch === wrappedFetch)
626
+ globalObject.fetch = originalFetch;
627
+ });
628
+ }
629
+ const Xhr = globalObject.XMLHttpRequest;
630
+ if (options.captureXmlHttpRequest !== false && Xhr?.prototype) {
631
+ const states = new WeakMap();
632
+ const prototype = Xhr.prototype;
633
+ const originalOpen = prototype.open;
634
+ const originalSend = prototype.send;
635
+ const originalSetRequestHeader = prototype.setRequestHeader;
636
+ const wrappedOpen = function (method, url, ...rest) {
637
+ states.set(this, {
638
+ method,
639
+ url: String(url),
640
+ requestHeaders: [],
641
+ suppressed: fetchInvocationDepth > 0,
642
+ });
643
+ Reflect.apply(originalOpen, this, [method, url, ...rest]);
644
+ };
645
+ const wrappedSetRequestHeader = function (name, value) {
646
+ states.get(this)?.requestHeaders.push({ name, value });
647
+ Reflect.apply(originalSetRequestHeader, this, [name, value]);
648
+ };
649
+ const wrappedSend = function (body) {
650
+ const state = states.get(this);
651
+ if (!state || state.suppressed) {
652
+ Reflect.apply(originalSend, this, [body]);
653
+ return;
654
+ }
655
+ state.startedAtUtc = new Date().toISOString();
656
+ state.started = monotonicNow(globalObject);
657
+ state.requestBody = bodyFromValue(body, state.requestHeaders, options);
658
+ let failure;
659
+ const markFailure = (event) => {
660
+ failure = event.type;
661
+ };
662
+ const complete = () => {
663
+ if (!active)
664
+ return;
665
+ let responseHeaders = [];
666
+ try {
667
+ responseHeaders = parseXhrResponseHeaders(this.getAllResponseHeaders());
668
+ }
669
+ catch {
670
+ // Some WebViews throw before response headers exist.
671
+ }
672
+ let responseBody;
673
+ try {
674
+ const responseType = this.responseType || "text";
675
+ const responseOptions = {
676
+ ...options,
677
+ captureRequestBody: options.captureResponseBody,
678
+ };
679
+ if (responseType === "text") {
680
+ responseBody = bodyFromValue(this.responseText, responseHeaders, responseOptions);
681
+ }
682
+ else if (responseType === "arraybuffer") {
683
+ responseBody = bodyFromValue(this.response, responseHeaders, responseOptions);
684
+ }
685
+ }
686
+ catch {
687
+ // Response data is not readable for every XHR response type.
688
+ }
689
+ const request = sanitizeNetworkRequest({
690
+ source: `${sourcePrefix}.xhr`,
691
+ startedAtUtc: state.startedAtUtc,
692
+ completedAtUtc: new Date().toISOString(),
693
+ durationMilliseconds: monotonicNow(globalObject) - (state.started ?? 0),
694
+ method: state.method,
695
+ url: this.responseURL || state.url,
696
+ requestHeaders: state.requestHeaders,
697
+ requestBodySizeBytes: parseContentLength(state.requestHeaders) ??
698
+ state.requestBody?.totalBytes,
699
+ requestBody: state.requestBody,
700
+ statusCode: this.status || undefined,
701
+ reasonPhrase: this.statusText,
702
+ responseHeaders,
703
+ responseBodySizeBytes: parseContentLength(responseHeaders) ?? responseBody?.totalBytes,
704
+ responseBody,
705
+ errorType: failure,
706
+ errorMessage: failure ? `XMLHttpRequest ${failure}` : undefined,
707
+ }, options, globalObject);
708
+ if (request)
709
+ Promise.resolve(capture(request)).catch(() => undefined);
710
+ };
711
+ this.addEventListener("error", markFailure);
712
+ this.addEventListener("abort", markFailure);
713
+ this.addEventListener("timeout", markFailure);
714
+ this.addEventListener("loadend", complete, { once: true });
715
+ Reflect.apply(originalSend, this, [body]);
716
+ };
717
+ prototype.open = wrappedOpen;
718
+ prototype.setRequestHeader = wrappedSetRequestHeader;
719
+ prototype.send = wrappedSend;
720
+ cleanups.push(() => {
721
+ if (prototype.open === wrappedOpen)
722
+ prototype.open = originalOpen;
723
+ if (prototype.send === wrappedSend)
724
+ prototype.send = originalSend;
725
+ if (prototype.setRequestHeader === wrappedSetRequestHeader) {
726
+ prototype.setRequestHeader = originalSetRequestHeader;
727
+ }
728
+ });
729
+ }
730
+ let removed = false;
731
+ return {
732
+ remove() {
733
+ if (removed)
734
+ return;
735
+ removed = true;
736
+ active = false;
737
+ for (const cleanup of cleanups.reverse())
738
+ cleanup();
739
+ },
740
+ };
741
+ }
742
+ //# sourceMappingURL=network.js.map