@ansight/capacitor 1.3.0-preview.9 → 1.4.0-preview.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AnsightCapacitor.podspec +1 -1
- package/Package.swift +1 -1
- package/README.md +116 -29
- package/android/build.gradle +2 -2
- package/android/src/main/kotlin/ai/ansight/capacitor/AnsightCapacitorPlugin.kt +74 -22
- package/dist/esm/definitions.d.ts +73 -10
- package/dist/esm/definitions.d.ts.map +1 -1
- package/dist/esm/dom.d.ts +11 -0
- package/dist/esm/dom.d.ts.map +1 -1
- package/dist/esm/dom.js +73 -23
- package/dist/esm/dom.js.map +1 -1
- package/dist/esm/index.d.ts +10 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +113 -5
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/network.d.ts +6 -0
- package/dist/esm/network.d.ts.map +1 -0
- package/dist/esm/network.js +742 -0
- package/dist/esm/network.js.map +1 -0
- package/dist/esm/options.d.ts +7 -1
- package/dist/esm/options.d.ts.map +1 -1
- package/dist/esm/options.js +44 -0
- package/dist/esm/options.js.map +1 -1
- package/dist/esm/session-properties.d.ts +16 -0
- package/dist/esm/session-properties.d.ts.map +1 -0
- package/dist/esm/session-properties.js +123 -0
- package/dist/esm/session-properties.js.map +1 -0
- package/dist/esm/standalone-options.d.ts +3 -0
- package/dist/esm/standalone-options.d.ts.map +1 -0
- package/dist/esm/standalone-options.js +15 -0
- package/dist/esm/standalone-options.js.map +1 -0
- package/dist/esm/standalone.d.ts.map +1 -1
- package/dist/esm/standalone.js +2 -11
- package/dist/esm/standalone.js.map +1 -1
- package/dist/plugin.cjs.js +1096 -28
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +1096 -28
- package/dist/plugin.js.map +1 -1
- package/dist/standalone.js +1099 -31
- package/dist/standalone.js.map +1 -1
- package/ios/Sources/AnsightCapacitorPlugin/AnsightCapacitorPlugin.swift +75 -21
- 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
|