@logbrew/sdk 0.1.38 → 0.1.39
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/automatic-delivery.cjs +271 -0
- package/batch-sender.cjs +164 -0
- package/bounded-number.cjs +34 -0
- package/browser-breadcrumbs.cjs +346 -136
- package/capture-support.cjs +100 -0
- package/client-options.cjs +292 -0
- package/client-report.cjs +232 -0
- package/client.cjs +447 -0
- package/console-arguments.cjs +151 -0
- package/console-capture.cjs +194 -0
- package/core.cjs +30 -2810
- package/delivery-health.cjs +133 -0
- package/error-causes.cjs +130 -0
- package/error-details.cjs +124 -0
- package/error-issue.cjs +187 -0
- package/event-clone.cjs +55 -0
- package/event-metadata.cjs +40 -0
- package/event-pipeline.cjs +325 -0
- package/event-queue.cjs +306 -0
- package/event-store.cjs +237 -0
- package/event-validation.cjs +159 -0
- package/examples/agent-timeline.cjs +57 -46
- package/examples/agent-timeline.mjs +41 -35
- package/examples/index.mjs +122 -94
- package/examples/readme-example.cjs +36 -27
- package/examples/readme-example.mjs +21 -17
- package/examples/real-user-smoke.cjs +41 -32
- package/examples/real-user-smoke.mjs +26 -22
- package/exception-chain.cjs +205 -0
- package/index.d.ts +144 -1076
- package/index.js +1 -1
- package/issue-exception.cjs +13 -8
- package/metric-validation.cjs +95 -0
- package/network-milestone.cjs +155 -0
- package/null-value.cjs +4 -0
- package/opentelemetry-bridge.cjs +330 -0
- package/opentelemetry-context.cjs +245 -0
- package/opentelemetry-exceptions.cjs +69 -0
- package/opentelemetry-keys.cjs +183 -0
- package/opentelemetry-span.cjs +286 -0
- package/opentelemetry-summary.cjs +213 -0
- package/opentelemetry-time.cjs +131 -0
- package/opentelemetry.cjs +99 -1068
- package/package.json +55 -1
- package/pino-destination.cjs +108 -0
- package/pino-record.cjs +237 -0
- package/product-action.cjs +163 -0
- package/provided-breadcrumbs.cjs +117 -0
- package/random-hex.cjs +29 -0
- package/react-native.js +1 -1
- package/recording-transport.cjs +68 -0
- package/release-artifacts-build.cjs +263 -84
- package/release-artifacts-common.js +347 -66
- package/release-artifacts-debug-id.js +178 -0
- package/release-artifacts-inspect.js +213 -0
- package/release-artifacts-issue-event.js +99 -0
- package/release-artifacts-manifest-artifact.js +107 -0
- package/release-artifacts-manifest.js +77 -0
- package/release-artifacts-mappings.js +265 -0
- package/release-artifacts-prepare.js +297 -0
- package/release-artifacts-source-context.js +210 -0
- package/release-artifacts-symbolication.js +272 -421
- package/release-artifacts-upload-files.js +122 -0
- package/release-artifacts-upload-request.js +246 -0
- package/release-artifacts-upload.js +155 -300
- package/release-artifacts-urls.js +49 -0
- package/release-artifacts.js +189 -662
- package/retry-delay.cjs +32 -0
- package/span-validation.cjs +191 -0
- package/stored-events.cjs +243 -0
- package/types/client.d.ts +67 -0
- package/types/delivery.d.ts +125 -0
- package/types/issues.d.ts +249 -0
- package/types/logging.d.ts +175 -0
- package/types/opentelemetry.d.ts +205 -0
- package/types/product.d.ts +128 -0
- package/types/recording-transport.d.ts +15 -0
- package/types/sdk-error.d.ts +9 -0
- package/types/telemetry.d.ts +171 -0
- package/types/trace-context.d.ts +110 -0
- package/types/transport-error.d.ts +10 -0
- package/utf8.cjs +37 -0
- package/validation.cjs +49 -1
- package/vite-release-artifacts-config.cjs +120 -0
- package/vite-release-artifacts.cjs +199 -99
- package/vite-release-artifacts.d.cts +65 -40
- package/vite-release-artifacts.d.ts +65 -40
- package/vite-release-artifacts.js +2 -2
- package/winston-info.cjs +205 -0
- package/winston.cjs +189 -289
package/browser-breadcrumbs.cjs
CHANGED
|
@@ -1,196 +1,406 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @import { IssueBreadcrumbLevel } from "./index.cjs"
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Readonly<{
|
|
9
|
+
* category: string;
|
|
10
|
+
* data?: Readonly<Record<string, number | string>>;
|
|
11
|
+
* level?: IssueBreadcrumbLevel;
|
|
12
|
+
* message?: string;
|
|
13
|
+
* type: string;
|
|
14
|
+
* }>} BrowserBreadcrumb
|
|
15
|
+
* @typedef {(breadcrumb: BrowserBreadcrumb) => boolean} AddBreadcrumb
|
|
16
|
+
* @typedef {() => void} Restore
|
|
17
|
+
* @typedef {(this: unknown, ...args: readonly unknown[]) => unknown} ObservedMethod
|
|
18
|
+
* @typedef {Readonly<{ method: string; url: string | undefined }>} HttpRequest
|
|
19
|
+
* @typedef {Readonly<{ method?: unknown; url?: unknown }> | undefined} FetchInput
|
|
20
|
+
* @typedef {Readonly<{ method?: unknown }> | undefined} FetchInit
|
|
21
|
+
* @typedef {Readonly<{ status?: unknown }> | undefined} FetchResponse
|
|
22
|
+
* @typedef {(this: unknown, input: FetchInput, init: FetchInit) => Promise<FetchResponse>} FetchMethod
|
|
23
|
+
* @typedef {Readonly<{
|
|
24
|
+
* addEventListener?: (type: string, listener: () => void) => unknown;
|
|
25
|
+
* status?: unknown;
|
|
26
|
+
* }>} XhrInstance
|
|
27
|
+
* @typedef {(this: XhrInstance, method: unknown, url: unknown, ...rest: readonly unknown[]) => unknown} XhrOpen
|
|
28
|
+
* @typedef {(this: XhrInstance, ...args: readonly unknown[]) => unknown} XhrSend
|
|
29
|
+
* @typedef {Readonly<{ open?: XhrOpen; send?: XhrSend }>} XhrPrototype
|
|
30
|
+
* @typedef {"debug" | "error" | "info" | "log" | "warn"} ConsoleMethod
|
|
31
|
+
* @typedef {Readonly<Partial<Record<ConsoleMethod, ObservedMethod>>>} BrowserConsole
|
|
32
|
+
* @typedef {Readonly<{ pushState?: ObservedMethod; replaceState?: ObservedMethod }>} BrowserHistory
|
|
33
|
+
* @typedef {Readonly<{
|
|
34
|
+
* addEventListener?: (type: string, listener: () => void) => unknown;
|
|
35
|
+
* console?: BrowserConsole;
|
|
36
|
+
* fetch?: FetchMethod;
|
|
37
|
+
* history?: BrowserHistory;
|
|
38
|
+
* location?: Readonly<{ href?: unknown; pathname?: unknown }>;
|
|
39
|
+
* removeEventListener?: (type: string, listener: () => void) => unknown;
|
|
40
|
+
* XMLHttpRequest?: Readonly<{ prototype?: XhrPrototype }>;
|
|
41
|
+
* }>} BrowserWindow
|
|
42
|
+
* @typedef {Readonly<{ addBreadcrumb: (breadcrumb: BrowserBreadcrumb) => unknown }>} BreadcrumbClient
|
|
43
|
+
* @typedef {Readonly<{
|
|
44
|
+
* console?: boolean;
|
|
45
|
+
* ignoredUrls?: readonly unknown[];
|
|
46
|
+
* navigation?: boolean;
|
|
47
|
+
* network?: boolean;
|
|
48
|
+
* }>} BrowserBreadcrumbOptions
|
|
49
|
+
*/
|
|
50
|
+
|
|
3
51
|
const MAX_MESSAGE_LENGTH = 512;
|
|
4
52
|
const MAX_URL_LENGTH = 256;
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
53
|
+
const MAX_METHOD_LENGTH = 16;
|
|
54
|
+
const TEXT_START = 0;
|
|
55
|
+
const FIRST_RESTORE = 0;
|
|
56
|
+
const NO_STATUS = 0;
|
|
57
|
+
const CLIENT_ERROR_STATUS = 400;
|
|
58
|
+
const SERVER_ERROR_STATUS = 500;
|
|
59
|
+
const LAST_C0_CONTROL = 31;
|
|
60
|
+
const FIRST_C1_CONTROL = 127;
|
|
61
|
+
const LAST_C1_CONTROL = 159;
|
|
62
|
+
const WHITESPACE_PATTERN = /\s+/gu;
|
|
63
|
+
/** @type {ReadonlyMap<ConsoleMethod, IssueBreadcrumbLevel>} */
|
|
64
|
+
const CONSOLE_LEVELS = new Map([
|
|
65
|
+
["debug", "debug"],
|
|
66
|
+
["error", "error"],
|
|
67
|
+
["info", "info"],
|
|
68
|
+
["log", "info"],
|
|
69
|
+
["warn", "warning"]
|
|
70
|
+
]);
|
|
71
|
+
/** @type {readonly (keyof BrowserHistory)[]} */
|
|
72
|
+
const NAVIGATION_METHODS = ["pushState", "replaceState"];
|
|
73
|
+
|
|
74
|
+
/** @type {(value: unknown) => string} */
|
|
75
|
+
const text = String;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {unknown} value
|
|
79
|
+
* @param {number} limit
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
function boundedText(value, limit) {
|
|
83
|
+
const characters = Array.from(text(value), (character) => {
|
|
84
|
+
const code = character.codePointAt(TEXT_START);
|
|
85
|
+
return code !== undefined && (code <= LAST_C0_CONTROL || (code >= FIRST_C1_CONTROL && code <= LAST_C1_CONTROL))
|
|
86
|
+
? " "
|
|
87
|
+
: character;
|
|
88
|
+
});
|
|
89
|
+
return [...characters.join("").replaceAll(WHITESPACE_PATTERN, " ").trim()].slice(TEXT_START, limit).join("");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {unknown} value
|
|
94
|
+
* @param {BrowserWindow} browserWindow
|
|
95
|
+
* @returns {string | undefined}
|
|
96
|
+
*/
|
|
97
|
+
function sanitizeUrl(value, browserWindow) {
|
|
98
|
+
if (value === undefined || value === null) {
|
|
99
|
+
return undefined;
|
|
33
100
|
}
|
|
34
|
-
|
|
35
|
-
|
|
101
|
+
try {
|
|
102
|
+
const url = new URL(text(value), text(browserWindow.location?.href ?? "http://localhost/"));
|
|
103
|
+
const sanitized = boundedText(`${url.protocol}//${url.host}${url.pathname}`, MAX_URL_LENGTH);
|
|
104
|
+
return sanitized === "" ? undefined : sanitized;
|
|
105
|
+
} catch {
|
|
106
|
+
return undefined;
|
|
36
107
|
}
|
|
37
|
-
|
|
38
|
-
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @param {BrowserWindow} browserWindow
|
|
112
|
+
* @returns {string | undefined}
|
|
113
|
+
*/
|
|
114
|
+
function currentPath(browserWindow) {
|
|
115
|
+
const pathname = browserWindow.location?.pathname;
|
|
116
|
+
return typeof pathname === "string" ? boundedText(pathname, MAX_URL_LENGTH) : undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {Readonly<Error>} error
|
|
121
|
+
* @returns {string}
|
|
122
|
+
*/
|
|
123
|
+
function errorName(error) {
|
|
124
|
+
if (error.name) {
|
|
125
|
+
return error.name;
|
|
39
126
|
}
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
|
|
127
|
+
return "Error";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @param {readonly unknown[]} args
|
|
132
|
+
* @returns {string}
|
|
133
|
+
*/
|
|
134
|
+
function consoleTemplate(args) {
|
|
135
|
+
const [first] = args;
|
|
136
|
+
if (typeof first === "string") {
|
|
137
|
+
return first;
|
|
138
|
+
}
|
|
139
|
+
return first instanceof Error ? errorName(first) : "";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @param {number | undefined} statusCode
|
|
144
|
+
* @returns {IssueBreadcrumbLevel}
|
|
145
|
+
*/
|
|
146
|
+
function httpLevel(statusCode) {
|
|
147
|
+
if (statusCode === undefined || statusCode < CLIENT_ERROR_STATUS) {
|
|
148
|
+
return "info";
|
|
149
|
+
}
|
|
150
|
+
return statusCode >= SERVER_ERROR_STATUS ? "error" : "warning";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @param {string} category
|
|
155
|
+
* @param {HttpRequest} request
|
|
156
|
+
* @param {unknown} [status]
|
|
157
|
+
* @returns {BrowserBreadcrumb}
|
|
158
|
+
*/
|
|
159
|
+
function httpBreadcrumb(category, request, status) {
|
|
160
|
+
const statusCode = typeof status === "number" && Number.isInteger(status) && status > NO_STATUS ? status : undefined;
|
|
161
|
+
const method = request.method.slice(TEXT_START, MAX_METHOD_LENGTH);
|
|
162
|
+
return {
|
|
163
|
+
type: "http",
|
|
164
|
+
|
|
165
|
+
category,
|
|
166
|
+
level: httpLevel(statusCode),
|
|
167
|
+
|
|
168
|
+
data: {
|
|
169
|
+
method: method === "" ? "GET" : method,
|
|
170
|
+
...(request.url === undefined ? {} : { url: request.url }),
|
|
171
|
+
...(statusCode === undefined ? {} : { status_code: statusCode })
|
|
43
172
|
}
|
|
44
173
|
};
|
|
45
174
|
}
|
|
46
175
|
|
|
176
|
+
/**
|
|
177
|
+
* @template {object} Target
|
|
178
|
+
* @template {keyof Target & string} Name
|
|
179
|
+
* @param {Target | undefined} target
|
|
180
|
+
* @param {Name} name
|
|
181
|
+
* @param {(original: NonNullable<Target[Name]>) => NonNullable<Target[Name]>} wrap
|
|
182
|
+
* @returns {Restore[]}
|
|
183
|
+
*/
|
|
184
|
+
function replaceMethod(target, name, wrap) {
|
|
185
|
+
const original = target?.[name];
|
|
186
|
+
if (target === undefined || typeof original !== "function") {
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
const replacement = wrap(original);
|
|
190
|
+
Object.assign(target, { [name]: replacement });
|
|
191
|
+
return [
|
|
192
|
+
() => {
|
|
193
|
+
if (target[name] === replacement) {
|
|
194
|
+
Object.assign(target, { [name]: original });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @param {BrowserConsole | undefined} target
|
|
202
|
+
* @param {AddBreadcrumb} add
|
|
203
|
+
* @returns {Restore[]}
|
|
204
|
+
*/
|
|
47
205
|
function patchConsole(target, add) {
|
|
48
206
|
if (!target) {
|
|
49
207
|
return [];
|
|
50
208
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
209
|
+
/** @type {Restore[]} */
|
|
210
|
+
const restores = [];
|
|
211
|
+
for (const [method, level] of CONSOLE_LEVELS) {
|
|
212
|
+
restores.push(...replaceMethod(target, method, (original) => (
|
|
213
|
+
/**
|
|
214
|
+
* @this {unknown}
|
|
215
|
+
* @param {readonly unknown[]} args
|
|
216
|
+
* @returns {unknown}
|
|
217
|
+
*/
|
|
218
|
+
function logBrewConsoleBreadcrumb(...args) {
|
|
219
|
+
const message = boundedText(consoleTemplate(args), MAX_MESSAGE_LENGTH);
|
|
220
|
+
if (message) {
|
|
221
|
+
add({
|
|
222
|
+
type: "debug",
|
|
223
|
+
|
|
224
|
+
category: "console",
|
|
225
|
+
level,
|
|
226
|
+
message
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
return Reflect.apply(original, this, args);
|
|
57
230
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
231
|
+
)));
|
|
232
|
+
}
|
|
233
|
+
return restores;
|
|
61
234
|
}
|
|
62
235
|
|
|
236
|
+
/**
|
|
237
|
+
* @param {BrowserWindow} browserWindow
|
|
238
|
+
* @param {AddBreadcrumb} add
|
|
239
|
+
* @param {Readonly<ReadonlySet<string | undefined>>} ignored
|
|
240
|
+
* @returns {Restore[]}
|
|
241
|
+
*/
|
|
63
242
|
function patchFetch(browserWindow, add, ignored) {
|
|
64
|
-
return replaceMethod(browserWindow, "fetch", (original) =>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
243
|
+
return replaceMethod(browserWindow, "fetch", (original) => (
|
|
244
|
+
/**
|
|
245
|
+
* @this {unknown}
|
|
246
|
+
* @param {FetchInput} input
|
|
247
|
+
* @param {FetchInit} init
|
|
248
|
+
* @returns {Promise<FetchResponse>}
|
|
249
|
+
*/
|
|
250
|
+
async function logBrewFetchBreadcrumb(input, init) {
|
|
251
|
+
const request = {
|
|
252
|
+
method: text(init?.method ?? input?.method ?? "GET").toUpperCase(),
|
|
253
|
+
url: sanitizeUrl(typeof input === "string" || input instanceof URL ? input : input?.url, browserWindow)
|
|
254
|
+
};
|
|
255
|
+
try {
|
|
256
|
+
const response = await original.call(this, input, init);
|
|
257
|
+
if (!ignored.has(request.url)) {
|
|
258
|
+
add(httpBreadcrumb("fetch", request, response?.status));
|
|
259
|
+
}
|
|
260
|
+
return response;
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if (!ignored.has(request.url)) {
|
|
263
|
+
add({ ...httpBreadcrumb("fetch", request), level: "error" });
|
|
264
|
+
}
|
|
265
|
+
throw error;
|
|
71
266
|
}
|
|
72
|
-
return response;
|
|
73
|
-
} catch (error) {
|
|
74
|
-
if (!ignored.has(url)) {
|
|
75
|
-
add({ ...httpBreadcrumb("fetch", method, url), level: "error" });
|
|
76
|
-
}
|
|
77
|
-
throw error;
|
|
78
267
|
}
|
|
79
|
-
|
|
268
|
+
));
|
|
80
269
|
}
|
|
81
270
|
|
|
271
|
+
/**
|
|
272
|
+
* @param {BrowserWindow} browserWindow
|
|
273
|
+
* @param {AddBreadcrumb} add
|
|
274
|
+
* @param {Readonly<ReadonlySet<string | undefined>>} ignored
|
|
275
|
+
* @returns {Restore[]}
|
|
276
|
+
*/
|
|
82
277
|
function patchXhr(browserWindow, add, ignored) {
|
|
83
278
|
const prototype = browserWindow.XMLHttpRequest?.prototype;
|
|
84
279
|
if (!prototype) {
|
|
85
280
|
return [];
|
|
86
281
|
}
|
|
282
|
+
/** @type {WeakMap<XhrInstance, HttpRequest>} */
|
|
87
283
|
const state = new WeakMap();
|
|
88
284
|
return [
|
|
89
|
-
...replaceMethod(prototype, "open", (original) =>
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
});
|
|
285
|
+
...replaceMethod(prototype, "open", (original) => (
|
|
286
|
+
/**
|
|
287
|
+
* @this {XhrInstance}
|
|
288
|
+
* @param {unknown} method
|
|
289
|
+
* @param {unknown} url
|
|
290
|
+
* @param {readonly unknown[]} rest
|
|
291
|
+
* @returns {unknown}
|
|
292
|
+
*/
|
|
293
|
+
function logBrewXhrOpen(method, url, ...rest) {
|
|
294
|
+
state.set(this, { method: text(method ?? "GET").toUpperCase(), url: sanitizeUrl(url, browserWindow) });
|
|
295
|
+
return original.call(this, method, url, ...rest);
|
|
99
296
|
}
|
|
100
|
-
|
|
101
|
-
|
|
297
|
+
)),
|
|
298
|
+
...replaceMethod(prototype, "send", (original) => (
|
|
299
|
+
/**
|
|
300
|
+
* @this {XhrInstance}
|
|
301
|
+
* @param {readonly unknown[]} args
|
|
302
|
+
* @returns {unknown}
|
|
303
|
+
*/
|
|
304
|
+
function logBrewXhrSend(...args) {
|
|
305
|
+
const request = state.get(this);
|
|
306
|
+
if (request && !ignored.has(request.url) && typeof this.addEventListener === "function") {
|
|
307
|
+
this.addEventListener("loadend", () => {
|
|
308
|
+
add(httpBreadcrumb("xhr", request, this.status));
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
return Reflect.apply(original, this, args);
|
|
312
|
+
}
|
|
313
|
+
))
|
|
102
314
|
];
|
|
103
315
|
}
|
|
104
316
|
|
|
317
|
+
/**
|
|
318
|
+
* @param {BrowserWindow} browserWindow
|
|
319
|
+
* @param {AddBreadcrumb} add
|
|
320
|
+
* @returns {Restore[]}
|
|
321
|
+
*/
|
|
105
322
|
function patchNavigation(browserWindow, add) {
|
|
106
|
-
const history = browserWindow
|
|
323
|
+
const { history } = browserWindow;
|
|
107
324
|
if (!history) {
|
|
108
325
|
return [];
|
|
109
326
|
}
|
|
110
327
|
let from = currentPath(browserWindow);
|
|
111
|
-
|
|
328
|
+
/** @returns {void} */
|
|
329
|
+
function record() {
|
|
112
330
|
const to = currentPath(browserWindow);
|
|
113
|
-
if (to && to !== from) {
|
|
114
|
-
add({
|
|
331
|
+
if (to !== undefined && to !== "" && to !== from) {
|
|
332
|
+
add({
|
|
333
|
+
type: "navigation",
|
|
334
|
+
|
|
335
|
+
category: "navigation",
|
|
336
|
+
data: { ...(from === undefined || from === "" ? {} : { from }), to }
|
|
337
|
+
});
|
|
115
338
|
from = to;
|
|
116
339
|
}
|
|
117
|
-
}
|
|
118
|
-
const restores =
|
|
119
|
-
/**
|
|
340
|
+
}
|
|
341
|
+
const restores = NAVIGATION_METHODS.flatMap((method) => replaceMethod(history, method, (original) => (
|
|
342
|
+
/**
|
|
343
|
+
* @this {unknown}
|
|
344
|
+
* @param {readonly unknown[]} args
|
|
345
|
+
* @returns {unknown}
|
|
346
|
+
*/
|
|
120
347
|
function logBrewNavigationBreadcrumb(...args) {
|
|
121
|
-
const result =
|
|
348
|
+
const result = Reflect.apply(original, this, args);
|
|
122
349
|
record();
|
|
123
350
|
return result;
|
|
124
351
|
}
|
|
125
352
|
)));
|
|
126
353
|
if (typeof browserWindow.addEventListener === "function") {
|
|
127
354
|
browserWindow.addEventListener("popstate", record);
|
|
128
|
-
restores.push(() =>
|
|
355
|
+
restores.push(() => {
|
|
356
|
+
browserWindow.removeEventListener?.("popstate", record);
|
|
357
|
+
});
|
|
129
358
|
}
|
|
130
359
|
return restores;
|
|
131
360
|
}
|
|
132
361
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
...(url ? { url } : {}),
|
|
156
|
-
...(statusCode === undefined ? {} : { status_code: statusCode })
|
|
362
|
+
/**
|
|
363
|
+
* @param {BrowserWindow} browserWindow
|
|
364
|
+
* @param {BreadcrumbClient} client
|
|
365
|
+
* @param {BrowserBreadcrumbOptions} [options]
|
|
366
|
+
* @returns {Restore}
|
|
367
|
+
*/
|
|
368
|
+
function installBrowserBreadcrumbs(browserWindow, client, {
|
|
369
|
+
console: captureConsole = true,
|
|
370
|
+
ignoredUrls = [],
|
|
371
|
+
navigation = true,
|
|
372
|
+
network = true
|
|
373
|
+
} = {}) {
|
|
374
|
+
const ignored = new Set(ignoredUrls.map((url) => sanitizeUrl(url, browserWindow)).filter(Boolean));
|
|
375
|
+
/**
|
|
376
|
+
* @param {BrowserBreadcrumb} breadcrumb
|
|
377
|
+
* @returns {boolean}
|
|
378
|
+
*/
|
|
379
|
+
function add(breadcrumb) {
|
|
380
|
+
try {
|
|
381
|
+
client.addBreadcrumb(breadcrumb);
|
|
382
|
+
} catch {
|
|
383
|
+
return false;
|
|
157
384
|
}
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Records the message template, never the interpolated arguments.
|
|
162
|
-
function consoleTemplate(args) {
|
|
163
|
-
const [first] = args;
|
|
164
|
-
if (typeof first === "string") {
|
|
165
|
-
return first;
|
|
385
|
+
return true;
|
|
166
386
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const pathname = browserWindow.location?.pathname;
|
|
172
|
-
return typeof pathname === "string" ? boundedText(pathname, MAX_URL_LENGTH) : undefined;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Keeps scheme, host, and path; drops user info, query, and fragment.
|
|
176
|
-
function sanitizeUrl(value, browserWindow) {
|
|
177
|
-
if (value === undefined || value === null) {
|
|
178
|
-
return undefined;
|
|
387
|
+
/** @type {Restore[]} */
|
|
388
|
+
const restores = [];
|
|
389
|
+
if (captureConsole) {
|
|
390
|
+
restores.push(...patchConsole(browserWindow.console ?? globalThis.console, add));
|
|
179
391
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return boundedText(`${url.protocol}//${url.host}${url.pathname}`, MAX_URL_LENGTH) || undefined;
|
|
183
|
-
} catch {
|
|
184
|
-
return undefined;
|
|
392
|
+
if (network) {
|
|
393
|
+
restores.push(...patchFetch(browserWindow, add, ignored), ...patchXhr(browserWindow, add, ignored));
|
|
185
394
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
395
|
+
if (navigation) {
|
|
396
|
+
restores.push(...patchNavigation(browserWindow, add));
|
|
397
|
+
}
|
|
398
|
+
return () => {
|
|
399
|
+
const pending = restores.splice(FIRST_RESTORE);
|
|
400
|
+
for (let restore = pending.pop(); restore !== undefined; restore = pending.pop()) {
|
|
401
|
+
restore();
|
|
402
|
+
}
|
|
403
|
+
};
|
|
194
404
|
}
|
|
195
405
|
|
|
196
406
|
module.exports = { installBrowserBreadcrumbs };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const { compactMetadata } = require("./log-context.cjs");
|
|
2
|
+
const { NULL_VALUE } = require("./null-value.cjs");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @import { Metadata } from "./index.cjs"
|
|
6
|
+
* @typedef {(reason: unknown) => void} ErrorHandler
|
|
7
|
+
* @typedef {Readonly<{
|
|
8
|
+
* log: (id: string, timestamp: unknown, attributes: Readonly<Record<string, unknown>>) => void,
|
|
9
|
+
* flush: (transport: unknown) => Promise<unknown>,
|
|
10
|
+
* pendingEvents: () => number
|
|
11
|
+
* }>} CaptureClient
|
|
12
|
+
* @typedef {{
|
|
13
|
+
* transport: unknown,
|
|
14
|
+
* flushOnEvent: boolean,
|
|
15
|
+
* includeErrorStack: boolean,
|
|
16
|
+
* logger: unknown,
|
|
17
|
+
* metadata: Metadata,
|
|
18
|
+
* timestamp: () => unknown,
|
|
19
|
+
* eventIdPrefix: unknown,
|
|
20
|
+
* onError: ErrorHandler
|
|
21
|
+
* }} CaptureSettings
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {unknown} flushing
|
|
26
|
+
* @param {ErrorHandler} onError
|
|
27
|
+
* @returns {Promise<unknown>}
|
|
28
|
+
*/
|
|
29
|
+
async function settledFlush(flushing, onError) {
|
|
30
|
+
try {
|
|
31
|
+
return await flushing;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
onError(error);
|
|
34
|
+
return NULL_VALUE;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {unknown} source
|
|
40
|
+
* @returns {(() => unknown) | undefined}
|
|
41
|
+
*/
|
|
42
|
+
function callableOrUndefined(source) {
|
|
43
|
+
if (typeof source !== "function") {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
return () => {
|
|
47
|
+
/** @type {unknown} */
|
|
48
|
+
const value = Reflect.apply(source, undefined, []);
|
|
49
|
+
return value;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {unknown} handler
|
|
55
|
+
* @returns {ErrorHandler}
|
|
56
|
+
*/
|
|
57
|
+
function errorHandler(handler) {
|
|
58
|
+
if (typeof handler !== "function") {
|
|
59
|
+
return () => undefined;
|
|
60
|
+
}
|
|
61
|
+
return (reason) => {
|
|
62
|
+
Reflect.apply(handler, undefined, [reason]);
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {unknown} timestamp
|
|
68
|
+
* @returns {() => unknown}
|
|
69
|
+
*/
|
|
70
|
+
function timestampSource(timestamp) {
|
|
71
|
+
return callableOrUndefined(timestamp) ?? (() => new Date().toISOString());
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {Readonly<Record<string, unknown>>} config
|
|
76
|
+
* @param {string} flushKey
|
|
77
|
+
* @param {string} defaultName
|
|
78
|
+
* @returns {CaptureSettings}
|
|
79
|
+
*/
|
|
80
|
+
function captureSettings(config, flushKey, defaultName) {
|
|
81
|
+
return {
|
|
82
|
+
transport: config["transport"],
|
|
83
|
+
|
|
84
|
+
flushOnEvent: config[flushKey] === true,
|
|
85
|
+
|
|
86
|
+
includeErrorStack: config["includeErrorStack"] === true,
|
|
87
|
+
|
|
88
|
+
logger: config["logger"] ?? defaultName,
|
|
89
|
+
|
|
90
|
+
metadata: compactMetadata(config["metadata"]),
|
|
91
|
+
|
|
92
|
+
timestamp: timestampSource(config["timestamp"]),
|
|
93
|
+
|
|
94
|
+
eventIdPrefix: config["eventIdPrefix"] ?? defaultName,
|
|
95
|
+
|
|
96
|
+
onError: errorHandler(config["onError"])
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = { callableOrUndefined, captureSettings, errorHandler, settledFlush };
|