@debugbundle/sdk-browser 1.3.1 → 1.4.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/README.md +27 -0
- package/dist/analytics-friction.d.ts +8 -0
- package/dist/analytics-friction.d.ts.map +1 -0
- package/dist/analytics-friction.js +59 -0
- package/dist/analytics-friction.js.map +1 -0
- package/dist/analytics-normalization.d.ts +17 -0
- package/dist/analytics-normalization.d.ts.map +1 -0
- package/dist/analytics-normalization.js +276 -0
- package/dist/analytics-normalization.js.map +1 -0
- package/dist/analytics.d.ts +41 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +410 -0
- package/dist/analytics.js.map +1 -0
- package/dist/capture-helpers.d.ts +11 -0
- package/dist/capture-helpers.d.ts.map +1 -0
- package/dist/capture-helpers.js +156 -0
- package/dist/capture-helpers.js.map +1 -0
- package/dist/event-pipeline.d.ts +13 -0
- package/dist/event-pipeline.d.ts.map +1 -0
- package/dist/event-pipeline.js +96 -0
- package/dist/event-pipeline.js.map +1 -0
- package/dist/event-transport.d.ts +31 -0
- package/dist/event-transport.d.ts.map +1 -0
- package/dist/event-transport.js +217 -0
- package/dist/event-transport.js.map +1 -0
- package/dist/index.d.ts +10 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +147 -672
- package/dist/index.js.map +1 -1
- package/dist/probes.d.ts +39 -0
- package/dist/probes.d.ts.map +1 -0
- package/dist/probes.js +190 -0
- package/dist/probes.js.map +1 -0
- package/dist/runtime.d.ts +4 -2
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +52 -0
- package/dist/runtime.js.map +1 -1
- package/dist/types.d.ts +123 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,164 +1,22 @@
|
|
|
1
1
|
import { redact } from "@debugbundle/redaction";
|
|
2
2
|
import { createEventEnvelope } from "@debugbundle/shared-types";
|
|
3
|
+
import { BrowserAnalyticsController } from "./analytics.js";
|
|
3
4
|
import { applyBrowserBeforeSend } from "./before-send.js";
|
|
4
|
-
import {
|
|
5
|
+
import { isImmediateRequestIncidentStatus, normalizeUnhandledRejectionReason, shouldCaptureBrowserNetworkRequest, shouldCaptureFailedBrowserNetworkRequest, shouldCaptureRequestStatus } from "./capture-helpers.js";
|
|
6
|
+
import { applyBrowserCaptureRules, buildBrowserSuppressionKey } from "./event-pipeline.js";
|
|
5
7
|
import { collectDeviceInfo, installConsoleHook, installNetworkHook } from "./hooks.js";
|
|
6
8
|
import { EventSuppressionTracker } from "./suppression.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
+
import { BrowserEventTransport } from "./event-transport.js";
|
|
10
|
+
import { BrowserProbeController } from "./probes.js";
|
|
11
|
+
import { buildSelector, createFetchTransport, getConsoleSource, getDocumentSource, getFetchSource, getHistorySource, getLocationSource, getWindowSource, createBrowserTraceId, normalizeBrowserErrorEvent, normalizeBoolean, normalizeError, normalizeLogLevel, normalizeNetworkFilter, normalizePositiveNumber, normalizeSampleRate, normalizeTracePropagationTargets, normalizeUnknownRecord, resolveBrowserTransport, } from "./runtime.js";
|
|
9
12
|
import { DEFAULT_BATCH_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_LOG_LEVEL, DEFAULT_MAX_BREADCRUMBS, DEFAULT_MAX_EVENTS_PER_SESSION, DEFAULT_REQUEST_TIMEOUT_MS, DEFAULT_SAMPLE_RATE, DEFAULT_SESSION_SAMPLE_RATE, LOG_LEVEL_ORDER, SDK_NAME, SDK_SCHEMA_VERSION, SDK_VERSION } from "./types.js";
|
|
10
|
-
const DEFAULT_REQUEST_FAILURE_PRESET = "balanced";
|
|
11
|
-
const DEFAULT_REQUEST_CAPTURE_EVENTS = "failures_only";
|
|
12
|
-
const DEFAULT_IMMEDIATE_CLIENT_ERROR_STATUSES = [];
|
|
13
|
-
const MAX_REJECTION_REASON_PREVIEW_LENGTH = 500;
|
|
14
|
-
function createInitialRemoteProbeState() {
|
|
15
|
-
return {
|
|
16
|
-
probesEnabled: false,
|
|
17
|
-
remoteProbesEnabled: false,
|
|
18
|
-
directives: [],
|
|
19
|
-
triggerTokenKey: null,
|
|
20
|
-
requestFailurePreset: DEFAULT_REQUEST_FAILURE_PRESET,
|
|
21
|
-
requestCaptureEvents: DEFAULT_REQUEST_CAPTURE_EVENTS,
|
|
22
|
-
immediateClientErrorStatuses: [...DEFAULT_IMMEDIATE_CLIENT_ERROR_STATUSES],
|
|
23
|
-
immediateClientErrorPathRules: []
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
function truncateRejectionReasonPreview(value) {
|
|
27
|
-
return value.length > MAX_REJECTION_REASON_PREVIEW_LENGTH
|
|
28
|
-
? `${value.slice(0, MAX_REJECTION_REASON_PREVIEW_LENGTH)}[truncated]`
|
|
29
|
-
: value;
|
|
30
|
-
}
|
|
31
|
-
function readReasonStringField(record, key) {
|
|
32
|
-
const value = record[key];
|
|
33
|
-
return typeof value === "string" && value.trim().length > 0
|
|
34
|
-
? truncateRejectionReasonPreview(value.trim())
|
|
35
|
-
: undefined;
|
|
36
|
-
}
|
|
37
|
-
function normalizeUnhandledRejectionReason(reason) {
|
|
38
|
-
if (reason instanceof Error) {
|
|
39
|
-
return {
|
|
40
|
-
error: reason,
|
|
41
|
-
rejectionReason: {
|
|
42
|
-
kind: "error",
|
|
43
|
-
name: reason.name || "Error",
|
|
44
|
-
message: truncateRejectionReasonPreview(reason.message || "Unknown rejection error")
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
if (typeof reason === "string") {
|
|
49
|
-
const preview = truncateRejectionReasonPreview(reason.length > 0 ? reason : "[empty string]");
|
|
50
|
-
return {
|
|
51
|
-
error: new Error(reason.length > 0 ? reason : "Unhandled promise rejection"),
|
|
52
|
-
rejectionReason: { kind: "string", preview }
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
if (reason === null) {
|
|
56
|
-
return {
|
|
57
|
-
error: new Error("Unhandled promise rejection: null"),
|
|
58
|
-
rejectionReason: { kind: "null", preview: "null" }
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
if (reason === undefined) {
|
|
62
|
-
return {
|
|
63
|
-
error: new Error("Unhandled promise rejection: undefined"),
|
|
64
|
-
rejectionReason: { kind: "undefined", preview: "undefined" }
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
const record = normalizeUnknownRecord(reason);
|
|
68
|
-
const name = readReasonStringField(record, "name");
|
|
69
|
-
const message = readReasonStringField(record, "message");
|
|
70
|
-
const constructorName = typeof reason === "object" && reason !== null && "constructor" in reason
|
|
71
|
-
? reason.constructor?.name
|
|
72
|
-
: undefined;
|
|
73
|
-
const preview = typeof constructorName === "string" && constructorName.length > 0 ? constructorName : "object";
|
|
74
|
-
return {
|
|
75
|
-
error: new Error(message ?? "Unhandled promise rejection"),
|
|
76
|
-
rejectionReason: {
|
|
77
|
-
kind: "object",
|
|
78
|
-
...(name === undefined ? {} : { name }),
|
|
79
|
-
...(message === undefined ? {} : { message }),
|
|
80
|
-
preview
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
const BALANCED_IMMEDIATE_REQUEST_STATUSES = new Set([408, 423, 424, 425, 429]);
|
|
85
|
-
const INVESTIGATIVE_IMMEDIATE_REQUEST_STATUSES = new Set([...BALANCED_IMMEDIATE_REQUEST_STATUSES, 409]);
|
|
86
|
-
function isImmediateRequestIncidentStatus(statusCode, preset, immediateClientErrorStatuses = [], requestPath, httpMethod, immediateClientErrorPathRules = []) {
|
|
87
|
-
if (!Number.isFinite(statusCode)) {
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
if (statusCode >= 500) {
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
if (immediateClientErrorStatuses.includes(statusCode)) {
|
|
94
|
-
return true;
|
|
95
|
-
}
|
|
96
|
-
if (matchesImmediateClientErrorPathRule(statusCode, requestPath, httpMethod, immediateClientErrorPathRules)) {
|
|
97
|
-
return true;
|
|
98
|
-
}
|
|
99
|
-
if (preset === "investigative") {
|
|
100
|
-
return INVESTIGATIVE_IMMEDIATE_REQUEST_STATUSES.has(statusCode);
|
|
101
|
-
}
|
|
102
|
-
if (preset === "balanced") {
|
|
103
|
-
return BALANCED_IMMEDIATE_REQUEST_STATUSES.has(statusCode);
|
|
104
|
-
}
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
function matchesImmediateClientErrorPathRule(statusCode, requestPath, httpMethod, rules) {
|
|
108
|
-
if (statusCode < 400 || statusCode > 499 || requestPath === undefined) {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
const normalizedPath = normalizeRequestPath(requestPath);
|
|
112
|
-
const normalizedMethod = typeof httpMethod === "string" ? httpMethod.toUpperCase() : null;
|
|
113
|
-
return rules.some((rule) => {
|
|
114
|
-
if (rule.statusCode !== statusCode) {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
if (rule.methods.length > 0 && (normalizedMethod === null || !rule.methods.includes(normalizedMethod))) {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
if (rule.pathPattern.endsWith("*")) {
|
|
121
|
-
return normalizedPath.startsWith(rule.pathPattern.slice(0, -1));
|
|
122
|
-
}
|
|
123
|
-
return normalizedPath === rule.pathPattern;
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
function normalizeRequestPath(value) {
|
|
127
|
-
try {
|
|
128
|
-
return new URL(value, getLocationSource()?.href ?? "https://debugbundle.local").pathname || "/";
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
const queryIndex = value.indexOf("?");
|
|
132
|
-
const fragmentIndex = value.indexOf("#");
|
|
133
|
-
const end = queryIndex === -1 ? (fragmentIndex === -1 ? value.length : fragmentIndex) : fragmentIndex === -1 ? queryIndex : Math.min(queryIndex, fragmentIndex);
|
|
134
|
-
const path = value.slice(0, end);
|
|
135
|
-
return path.startsWith("/") && path.length > 0 ? path : "/";
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
function shouldCaptureRequestStatus(statusCode, preset, policy, immediateClientErrorStatuses = [], requestPath, httpMethod, immediateClientErrorPathRules = []) {
|
|
139
|
-
if (isImmediateRequestIncidentStatus(statusCode, preset, immediateClientErrorStatuses, requestPath, httpMethod, immediateClientErrorPathRules)) {
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
if (policy === "all") {
|
|
143
|
-
return Number.isFinite(statusCode) && statusCode >= 400;
|
|
144
|
-
}
|
|
145
|
-
if (policy === "failures_only") {
|
|
146
|
-
return statusCode >= 500;
|
|
147
|
-
}
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
13
|
export class BrowserSdk {
|
|
151
14
|
config = null;
|
|
152
|
-
bufferedEvents = [];
|
|
153
15
|
breadcrumbs = [];
|
|
154
16
|
persistentContext = {};
|
|
155
17
|
deviceInfo = null;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
nextRetryAt = null;
|
|
159
|
-
_lastEventAt = null;
|
|
160
|
-
_consecutiveFailures = 0;
|
|
161
|
-
authRejected = false;
|
|
18
|
+
browserSessionId = null;
|
|
19
|
+
analyticsInitialization = null;
|
|
162
20
|
registeredListeners = [];
|
|
163
21
|
originalPushState = null;
|
|
164
22
|
originalReplaceState = null;
|
|
@@ -168,28 +26,36 @@ export class BrowserSdk {
|
|
|
168
26
|
originalConsoleWarn = null;
|
|
169
27
|
sessionSampledIn = true;
|
|
170
28
|
sessionEventCount = 0;
|
|
171
|
-
probeBuffers = new Map();
|
|
172
29
|
suppressionTracker = new EventSuppressionTracker();
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return "disconnected";
|
|
185
|
-
}
|
|
186
|
-
if (this.nextRetryAt !== null) {
|
|
187
|
-
return "degraded";
|
|
30
|
+
probeController = new BrowserProbeController({
|
|
31
|
+
getConfig: () => this.config,
|
|
32
|
+
isDebugRejected: () => this.eventTransport.debugRejected,
|
|
33
|
+
isSessionSampledIn: () => this.sessionSampledIn,
|
|
34
|
+
emitProbeEvent: ({ label, data, directive }) => this.emitProbeEvent(label, data, directive),
|
|
35
|
+
applyRemoteAnalytics: (config) => this.analyticsController.applyRemoteSettings(config)
|
|
36
|
+
});
|
|
37
|
+
eventTransport = new BrowserEventTransport({
|
|
38
|
+
onDebugResponse: (payload) => this.probeController.updateFromIngestionResponse(payload),
|
|
39
|
+
onUnauthorized: (lane, statusCode, endpoint, body) => {
|
|
40
|
+
this.reportUnauthorizedTransportFailure(lane, statusCode, endpoint, body);
|
|
188
41
|
}
|
|
189
|
-
|
|
42
|
+
});
|
|
43
|
+
analyticsController = new BrowserAnalyticsController({
|
|
44
|
+
getConfig: () => this.config,
|
|
45
|
+
getDeviceInfo: () => this.deviceInfo,
|
|
46
|
+
getCurrentRoute: () => this.getCurrentRoute(),
|
|
47
|
+
getSessionId: () => this.browserSessionId ?? createBrowserTraceId(),
|
|
48
|
+
enqueue: (event) => this.enqueueAnalyticsEvent(event)
|
|
49
|
+
});
|
|
50
|
+
analytics = this.analyticsController.api;
|
|
51
|
+
get remoteProbeState() {
|
|
52
|
+
return this.probeController.state;
|
|
53
|
+
}
|
|
54
|
+
get status() {
|
|
55
|
+
return this.eventTransport.status;
|
|
190
56
|
}
|
|
191
57
|
get lastEventAt() {
|
|
192
|
-
return this.
|
|
58
|
+
return this.eventTransport.lastEventAt;
|
|
193
59
|
}
|
|
194
60
|
init(config) {
|
|
195
61
|
this.dispose();
|
|
@@ -227,19 +93,38 @@ export class BrowserSdk {
|
|
|
227
93
|
maxProbeEntriesPerLabel: normalizePositiveNumber(config.maxProbeEntriesPerLabel, 10),
|
|
228
94
|
probeFlushOnError: normalizeBoolean(config.probeFlushOnError, true),
|
|
229
95
|
requestTimeoutMs: normalizePositiveNumber(config.requestTimeoutMs, DEFAULT_REQUEST_TIMEOUT_MS),
|
|
96
|
+
requestsAnalyticsConfig: config.analytics?.enabled === true,
|
|
230
97
|
captureRules: [],
|
|
231
98
|
fetchImpl: getFetchSource(),
|
|
232
99
|
transport: config.transport ?? createFetchTransport(),
|
|
233
100
|
transportMode: resolvedTransport.mode,
|
|
234
101
|
...(config.beforeSend === undefined ? {} : { beforeSend: config.beforeSend })
|
|
235
102
|
};
|
|
236
|
-
this.
|
|
103
|
+
this.eventTransport.configure(this.config);
|
|
237
104
|
this.sessionSampledIn = this.config.sessionSampleRate >= 1 || Math.random() < this.config.sessionSampleRate;
|
|
238
105
|
this.sessionEventCount = 0;
|
|
106
|
+
this.browserSessionId = createBrowserTraceId();
|
|
239
107
|
this.deviceInfo = collectDeviceInfo();
|
|
240
|
-
|
|
241
|
-
|
|
108
|
+
const deferAnalyticsCapture = this.config.requestsAnalyticsConfig &&
|
|
109
|
+
this.config.transportMode === "direct" &&
|
|
110
|
+
this.config.projectToken !== null &&
|
|
111
|
+
this.config.fetchImpl !== null;
|
|
112
|
+
this.analyticsController.configure(config.analytics, { deferCapture: deferAnalyticsCapture });
|
|
113
|
+
const remoteInitialization = this.probeController.initialize();
|
|
242
114
|
this.installBrowserHooks();
|
|
115
|
+
if (deferAnalyticsCapture) {
|
|
116
|
+
const activeConfig = this.config;
|
|
117
|
+
this.analyticsInitialization = remoteInitialization.finally(() => {
|
|
118
|
+
if (this.config !== activeConfig) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
this.analyticsController.markCaptureReady();
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
this.analyticsController.captureSessionStart();
|
|
126
|
+
this.analyticsController.captureInitialPageView();
|
|
127
|
+
}
|
|
243
128
|
}
|
|
244
129
|
captureException(error, context = {}) {
|
|
245
130
|
const config = this.config;
|
|
@@ -251,7 +136,9 @@ export class BrowserSdk {
|
|
|
251
136
|
const device = this.deviceInfo;
|
|
252
137
|
const browser = device?.browser ?? { name: "Unknown", version: "0" };
|
|
253
138
|
const breadcrumbs = this.consumeBreadcrumbs();
|
|
254
|
-
const probeData = config.probeFlushOnError
|
|
139
|
+
const probeData = config.probeFlushOnError
|
|
140
|
+
? this.probeController.consumeBufferedData()
|
|
141
|
+
: { version: 1, items: [] };
|
|
255
142
|
const domContext = typeof context.target?.outerHTML === "string" && context.target.outerHTML.length > 0
|
|
256
143
|
? {
|
|
257
144
|
mode: "lightweight",
|
|
@@ -374,133 +261,26 @@ export class BrowserSdk {
|
|
|
374
261
|
this.persistentContext[key] = redacted[key] ?? null;
|
|
375
262
|
}
|
|
376
263
|
probe(label, data) {
|
|
377
|
-
|
|
378
|
-
const normalizedLabel = label.trim();
|
|
379
|
-
if (config === null || normalizedLabel.length === 0) {
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
try {
|
|
383
|
-
const redacted = redact(this.normalizeProbeInput(data), {
|
|
384
|
-
sensitiveKeys: config.redactFields
|
|
385
|
-
}).redacted;
|
|
386
|
-
const probeData = normalizeUnknownRecord(redacted);
|
|
387
|
-
this.bufferProbe(normalizedLabel, probeData);
|
|
388
|
-
const matchingDirectives = this.getMatchingRemoteProbeDirectives(normalizedLabel, Date.now());
|
|
389
|
-
if (!this.sessionSampledIn || matchingDirectives.length === 0) {
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
for (const directive of matchingDirectives) {
|
|
393
|
-
this.enqueueEvent(this.createSdkEventEnvelope(config, {
|
|
394
|
-
schema_version: SDK_SCHEMA_VERSION,
|
|
395
|
-
event_type: "probe_event",
|
|
396
|
-
...this.getProjectTokenFields(config),
|
|
397
|
-
sdk_name: SDK_NAME,
|
|
398
|
-
sdk_version: SDK_VERSION,
|
|
399
|
-
service: {
|
|
400
|
-
name: config.service,
|
|
401
|
-
runtime: "browser",
|
|
402
|
-
framework: null,
|
|
403
|
-
environment: config.environment
|
|
404
|
-
},
|
|
405
|
-
occurred_at: new Date().toISOString(),
|
|
406
|
-
correlation: this.createCorrelation(),
|
|
407
|
-
payload: {
|
|
408
|
-
label: normalizedLabel,
|
|
409
|
-
data: probeData,
|
|
410
|
-
activation_id: directive.activationId,
|
|
411
|
-
probe_label_pattern: directive.labelPattern
|
|
412
|
-
}
|
|
413
|
-
}), false);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
catch {
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
264
|
+
this.probeController.capture(label, data);
|
|
419
265
|
}
|
|
420
266
|
async flush() {
|
|
421
|
-
|
|
422
|
-
if (config === null) {
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
267
|
+
await this.analyticsInitialization;
|
|
425
268
|
this.enqueueSuppressionAggregates();
|
|
426
|
-
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
if (this.flushPromise !== null) {
|
|
430
|
-
return this.flushPromise;
|
|
431
|
-
}
|
|
432
|
-
if (this.nextRetryAt !== null && Date.now() < this.nextRetryAt) {
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
if (this.authRejected) {
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
this.clearFlushTimer();
|
|
439
|
-
const events = [...this.bufferedEvents];
|
|
440
|
-
this.flushPromise = (async () => {
|
|
441
|
-
try {
|
|
442
|
-
const response = await config.transport({
|
|
443
|
-
endpoint: config.endpoint,
|
|
444
|
-
headers: this.getTransportHeaders(config),
|
|
445
|
-
events,
|
|
446
|
-
transportMode: config.transportMode,
|
|
447
|
-
timeout_ms: config.requestTimeoutMs
|
|
448
|
-
});
|
|
449
|
-
if (response.status >= 200 && response.status < 300) {
|
|
450
|
-
this.updateRemoteProbeStateFromIngestionResponse(response.body);
|
|
451
|
-
this.nextRetryAt = null;
|
|
452
|
-
this._lastEventAt = Date.now();
|
|
453
|
-
this._consecutiveFailures = 0;
|
|
454
|
-
if (this.bufferedEvents === events || this.sameLeadingEvents(events)) {
|
|
455
|
-
this.bufferedEvents.splice(0, events.length);
|
|
456
|
-
}
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
this._consecutiveFailures++;
|
|
460
|
-
if (response.status === 401 || response.status === 403) {
|
|
461
|
-
this.authRejected = true;
|
|
462
|
-
this.nextRetryAt = null;
|
|
463
|
-
this.reportUnauthorizedTransportFailure(response.status, config.endpoint, response.body);
|
|
464
|
-
this.bufferedEvents = [];
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
if (response.status === 429) {
|
|
468
|
-
this.nextRetryAt = Date.now() + (response.retry_after_ms ?? 1_000);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
catch {
|
|
472
|
-
this._consecutiveFailures++;
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
finally {
|
|
476
|
-
this.flushPromise = null;
|
|
477
|
-
if (this.bufferedEvents.length > 0) {
|
|
478
|
-
const retryDelay = this.nextRetryAt === null ? undefined : Math.max(0, this.nextRetryAt - Date.now());
|
|
479
|
-
this.scheduleFlush(retryDelay);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
})();
|
|
483
|
-
return this.flushPromise;
|
|
269
|
+
await this.eventTransport.flush();
|
|
484
270
|
}
|
|
485
271
|
dispose() {
|
|
486
|
-
this.
|
|
487
|
-
this.flushPromise = null;
|
|
488
|
-
this.bufferedEvents = [];
|
|
272
|
+
this.eventTransport.reset();
|
|
489
273
|
this.breadcrumbs = [];
|
|
490
|
-
this.probeBuffers = new Map();
|
|
491
274
|
this.persistentContext = {};
|
|
492
275
|
this.deviceInfo = null;
|
|
276
|
+
this.browserSessionId = null;
|
|
277
|
+
this.analyticsInitialization = null;
|
|
493
278
|
this.config = null;
|
|
494
279
|
this.sessionSampledIn = true;
|
|
495
280
|
this.sessionEventCount = 0;
|
|
496
|
-
this.nextRetryAt = null;
|
|
497
|
-
this._lastEventAt = null;
|
|
498
|
-
this._consecutiveFailures = 0;
|
|
499
|
-
this.authRejected = false;
|
|
500
281
|
this.suppressionTracker.reset();
|
|
501
|
-
this.
|
|
502
|
-
this.
|
|
503
|
-
this.activeTriggerDirective = null;
|
|
282
|
+
this.probeController.reset();
|
|
283
|
+
this.analyticsController.reset();
|
|
504
284
|
while (this.registeredListeners.length > 0) {
|
|
505
285
|
this.registeredListeners.pop()?.();
|
|
506
286
|
}
|
|
@@ -531,7 +311,7 @@ export class BrowserSdk {
|
|
|
531
311
|
this.originalXmlHttpRequest = null;
|
|
532
312
|
}
|
|
533
313
|
}
|
|
534
|
-
reportUnauthorizedTransportFailure(statusCode, endpoint, body) {
|
|
314
|
+
reportUnauthorizedTransportFailure(lane, statusCode, endpoint, body) {
|
|
535
315
|
const consoleSource = getConsoleSource();
|
|
536
316
|
if (consoleSource === null) {
|
|
537
317
|
return;
|
|
@@ -539,7 +319,8 @@ export class BrowserSdk {
|
|
|
539
319
|
const bodyRecord = normalizeUnknownRecord(body);
|
|
540
320
|
const errorCode = typeof bodyRecord["error"] === "string" && bodyRecord["error"].length > 0 ? bodyRecord["error"] : null;
|
|
541
321
|
const detail = errorCode === null ? "" : ` (${errorCode})`;
|
|
542
|
-
const
|
|
322
|
+
const laneLabel = lane === "debug" ? "browser SDK" : "browser analytics";
|
|
323
|
+
const message = `DebugBundle ${laneLabel} disabled after ingestion returned ${statusCode} for ${endpoint}. ` +
|
|
543
324
|
`Check the project token or relay configuration${detail}.`;
|
|
544
325
|
if (typeof consoleSource.error === "function") {
|
|
545
326
|
consoleSource.error(message);
|
|
@@ -550,7 +331,10 @@ export class BrowserSdk {
|
|
|
550
331
|
installBrowserHooks() {
|
|
551
332
|
const windowSource = getWindowSource();
|
|
552
333
|
if (windowSource !== null) {
|
|
553
|
-
const onPageHide = () => {
|
|
334
|
+
const onPageHide = (event) => {
|
|
335
|
+
if (normalizeUnknownRecord(event)["persisted"] !== true) {
|
|
336
|
+
this.analyticsController.captureSessionSummary();
|
|
337
|
+
}
|
|
554
338
|
this.flushViaBeacon();
|
|
555
339
|
};
|
|
556
340
|
const onError = (event) => {
|
|
@@ -578,21 +362,32 @@ export class BrowserSdk {
|
|
|
578
362
|
const documentSource = getDocumentSource();
|
|
579
363
|
if (documentSource !== null) {
|
|
580
364
|
const onClick = (event) => {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
const selector = buildSelector(target);
|
|
586
|
-
if (selector === null) {
|
|
365
|
+
const captureDebugClick = this.config?.captureClicks === true;
|
|
366
|
+
const captureAnalyticsAction = this.analyticsController.shouldCaptureStructuralActions();
|
|
367
|
+
const captureAnalyticsFriction = this.analyticsController.shouldCaptureFrictionSignals();
|
|
368
|
+
if (!captureDebugClick && !captureAnalyticsAction && !captureAnalyticsFriction) {
|
|
587
369
|
return;
|
|
588
370
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
371
|
+
const targetIdentity = normalizeUnknownRecord(event)["target"];
|
|
372
|
+
const target = normalizeUnknownRecord(targetIdentity);
|
|
373
|
+
if (captureDebugClick) {
|
|
374
|
+
const selector = buildSelector(target);
|
|
375
|
+
if (selector !== null) {
|
|
376
|
+
this.addBreadcrumb({
|
|
377
|
+
ts: new Date().toISOString(),
|
|
378
|
+
breadcrumb_type: "click",
|
|
379
|
+
data: {
|
|
380
|
+
selector
|
|
381
|
+
}
|
|
382
|
+
});
|
|
594
383
|
}
|
|
595
|
-
}
|
|
384
|
+
}
|
|
385
|
+
if (captureAnalyticsAction) {
|
|
386
|
+
this.analyticsController.captureStructuralAction(target);
|
|
387
|
+
}
|
|
388
|
+
if (captureAnalyticsFriction) {
|
|
389
|
+
this.analyticsController.captureFrictionClick(target, targetIdentity);
|
|
390
|
+
}
|
|
596
391
|
};
|
|
597
392
|
const onSubmit = (event) => {
|
|
598
393
|
const target = normalizeUnknownRecord(normalizeUnknownRecord(event)["target"]);
|
|
@@ -644,7 +439,7 @@ export class BrowserSdk {
|
|
|
644
439
|
this.addBreadcrumb(breadcrumb);
|
|
645
440
|
}, (breadcrumb) => {
|
|
646
441
|
this.captureNetworkRequestFailure(breadcrumb);
|
|
647
|
-
}, (url, statusCode, durationMs) => this.
|
|
442
|
+
}, (url, statusCode, durationMs) => shouldCaptureBrowserNetworkRequest(this.config, url, statusCode, durationMs), (url, durationMs) => shouldCaptureFailedBrowserNetworkRequest(this.config, url, durationMs), () => this.getCurrentRoute());
|
|
648
443
|
this.originalFetch = networkHooks.originalFetch;
|
|
649
444
|
this.originalXmlHttpRequest = networkHooks.originalXmlHttpRequest;
|
|
650
445
|
}
|
|
@@ -652,13 +447,13 @@ export class BrowserSdk {
|
|
|
652
447
|
return {
|
|
653
448
|
request_id: null,
|
|
654
449
|
trace_id: null,
|
|
655
|
-
session_id:
|
|
450
|
+
session_id: this.browserSessionId,
|
|
656
451
|
user_id_hash: null
|
|
657
452
|
};
|
|
658
453
|
}
|
|
659
454
|
addBreadcrumb(breadcrumb) {
|
|
660
455
|
const config = this.config;
|
|
661
|
-
if (config === null || this.
|
|
456
|
+
if (config === null || this.eventTransport.debugRejected || !this.shouldCaptureBreadcrumb()) {
|
|
662
457
|
return;
|
|
663
458
|
}
|
|
664
459
|
if (config.breadcrumbsOnErrorOnly !== true) {
|
|
@@ -671,34 +466,6 @@ export class BrowserSdk {
|
|
|
671
466
|
this.breadcrumbs.shift();
|
|
672
467
|
}
|
|
673
468
|
}
|
|
674
|
-
bufferProbe(label, data) {
|
|
675
|
-
const config = this.config;
|
|
676
|
-
if (config === null || this.authRejected) {
|
|
677
|
-
return;
|
|
678
|
-
}
|
|
679
|
-
if (!this.probeBuffers.has(label) && this.probeBuffers.size >= config.maxProbeLabels) {
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
const buffer = this.probeBuffers.get(label) ?? [];
|
|
683
|
-
buffer.push({
|
|
684
|
-
label,
|
|
685
|
-
data,
|
|
686
|
-
timestamp: new Date().toISOString(),
|
|
687
|
-
activation_id: null
|
|
688
|
-
});
|
|
689
|
-
while (buffer.length > config.maxProbeEntriesPerLabel) {
|
|
690
|
-
buffer.shift();
|
|
691
|
-
}
|
|
692
|
-
this.probeBuffers.set(label, buffer);
|
|
693
|
-
}
|
|
694
|
-
consumeProbeData() {
|
|
695
|
-
const items = Array.from(this.probeBuffers.values()).flatMap((buffer) => buffer);
|
|
696
|
-
this.probeBuffers.clear();
|
|
697
|
-
return {
|
|
698
|
-
version: 1,
|
|
699
|
-
items
|
|
700
|
-
};
|
|
701
|
-
}
|
|
702
469
|
consumeBreadcrumbs() {
|
|
703
470
|
const breadcrumbs = [...this.breadcrumbs];
|
|
704
471
|
this.breadcrumbs = [];
|
|
@@ -724,6 +491,7 @@ export class BrowserSdk {
|
|
|
724
491
|
route
|
|
725
492
|
}
|
|
726
493
|
});
|
|
494
|
+
this.analyticsController.captureRouteChange(route);
|
|
727
495
|
}
|
|
728
496
|
createBreadcrumbEvent(breadcrumb) {
|
|
729
497
|
const config = this.config;
|
|
@@ -751,6 +519,33 @@ export class BrowserSdk {
|
|
|
751
519
|
}
|
|
752
520
|
});
|
|
753
521
|
}
|
|
522
|
+
emitProbeEvent(label, data, directive) {
|
|
523
|
+
const config = this.config;
|
|
524
|
+
if (config === null) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
this.enqueueEvent(this.createSdkEventEnvelope(config, {
|
|
528
|
+
schema_version: SDK_SCHEMA_VERSION,
|
|
529
|
+
event_type: "probe_event",
|
|
530
|
+
...this.getProjectTokenFields(config),
|
|
531
|
+
sdk_name: SDK_NAME,
|
|
532
|
+
sdk_version: SDK_VERSION,
|
|
533
|
+
service: {
|
|
534
|
+
name: config.service,
|
|
535
|
+
runtime: "browser",
|
|
536
|
+
framework: null,
|
|
537
|
+
environment: config.environment
|
|
538
|
+
},
|
|
539
|
+
occurred_at: new Date().toISOString(),
|
|
540
|
+
correlation: this.createCorrelation(),
|
|
541
|
+
payload: {
|
|
542
|
+
label,
|
|
543
|
+
data,
|
|
544
|
+
activation_id: directive.activationId,
|
|
545
|
+
probe_label_pattern: directive.labelPattern
|
|
546
|
+
}
|
|
547
|
+
}), false);
|
|
548
|
+
}
|
|
754
549
|
captureNetworkRequestFailure(breadcrumb) {
|
|
755
550
|
const config = this.config;
|
|
756
551
|
if (config === null || breadcrumb.breadcrumb_type !== "network_request") {
|
|
@@ -822,135 +617,48 @@ export class BrowserSdk {
|
|
|
822
617
|
if (beforeSendEvent === null) {
|
|
823
618
|
return;
|
|
824
619
|
}
|
|
825
|
-
const
|
|
620
|
+
const captureRuleResult = applyBrowserCaptureRules({
|
|
621
|
+
config: this.config,
|
|
622
|
+
event: beforeSendEvent,
|
|
623
|
+
currentRoute: this.getCurrentRoute(),
|
|
624
|
+
now: new Date().toISOString()
|
|
625
|
+
});
|
|
626
|
+
if (captureRuleResult.breadcrumb !== null) {
|
|
627
|
+
this.addBreadcrumb(captureRuleResult.breadcrumb);
|
|
628
|
+
}
|
|
629
|
+
const resolvedEvent = captureRuleResult.event;
|
|
826
630
|
if (resolvedEvent === null) {
|
|
827
631
|
return;
|
|
828
632
|
}
|
|
829
633
|
if (!this.shouldCaptureBySampleRate(resolvedEvent)) {
|
|
830
634
|
return;
|
|
831
635
|
}
|
|
832
|
-
const suppressionKey =
|
|
636
|
+
const suppressionKey = buildBrowserSuppressionKey(resolvedEvent);
|
|
833
637
|
if (suppressionKey !== null && !this.suppressionTracker.shouldCapture(suppressionKey, Date.now())) {
|
|
834
|
-
this.
|
|
638
|
+
this.eventTransport.scheduleDebug();
|
|
835
639
|
return;
|
|
836
640
|
}
|
|
837
641
|
this.enqueueInternalEvent(resolvedEvent, countTowardSession, false);
|
|
838
642
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
if (config === null || config.captureRules.length === 0) {
|
|
842
|
-
return event;
|
|
843
|
-
}
|
|
844
|
-
const projectId = config.captureRules[0]?.project_id;
|
|
845
|
-
if (typeof projectId !== "string" || projectId.length === 0) {
|
|
846
|
-
return event;
|
|
847
|
-
}
|
|
848
|
-
try {
|
|
849
|
-
const captureRule = evaluateBrowserCaptureRulesForEvent(config.captureRules, projectId, event, new Date().toISOString());
|
|
850
|
-
if (captureRule === null) {
|
|
851
|
-
return event;
|
|
852
|
-
}
|
|
853
|
-
if (captureRule.outcome === "drop" || captureRule.outcome === "sampled_out") {
|
|
854
|
-
return null;
|
|
855
|
-
}
|
|
856
|
-
if (event.event_type === "frontend_exception" &&
|
|
857
|
-
(captureRule.outcome === "demote" || captureRule.sample_event_class === "context")) {
|
|
858
|
-
this.addBreadcrumb(this.createDemotedExceptionBreadcrumb(event, captureRule));
|
|
859
|
-
return null;
|
|
860
|
-
}
|
|
861
|
-
if (event.event_type === "request_event" &&
|
|
862
|
-
(captureRule.outcome === "demote" || captureRule.sample_event_class === "context")) {
|
|
863
|
-
return null;
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
catch {
|
|
867
|
-
return event;
|
|
868
|
-
}
|
|
869
|
-
return event;
|
|
870
|
-
}
|
|
871
|
-
createDemotedExceptionBreadcrumb(event, captureRule) {
|
|
872
|
-
const payload = event.payload;
|
|
873
|
-
const browserEventRecord = typeof payload["browser_event"] === "object" && payload["browser_event"] !== null
|
|
874
|
-
? payload["browser_event"]
|
|
875
|
-
: null;
|
|
876
|
-
const targetRecord = typeof browserEventRecord?.["target"] === "object" && browserEventRecord["target"] !== null
|
|
877
|
-
? browserEventRecord["target"]
|
|
878
|
-
: null;
|
|
879
|
-
const browserEventKind = browserEventRecord?.["kind"] === "window_error" || browserEventRecord?.["kind"] === "resource_error"
|
|
880
|
-
? browserEventRecord["kind"]
|
|
881
|
-
: undefined;
|
|
882
|
-
const sourceUrl = typeof targetRecord?.["source_url"] === "string"
|
|
883
|
-
? targetRecord["source_url"]
|
|
884
|
-
: typeof browserEventRecord?.["file_name"] === "string"
|
|
885
|
-
? browserEventRecord["file_name"]
|
|
886
|
-
: null;
|
|
887
|
-
return {
|
|
888
|
-
ts: event.occurred_at,
|
|
889
|
-
breadcrumb_type: "console_log",
|
|
890
|
-
route: event.payload.route ?? this.getCurrentRoute(),
|
|
891
|
-
data: {
|
|
892
|
-
level: "error",
|
|
893
|
-
message: `${event.payload.name}: ${event.payload.message}`,
|
|
894
|
-
source: "capture_rule_demoted_exception",
|
|
895
|
-
capture_rule_action: captureRule.action,
|
|
896
|
-
capture_rule_outcome: captureRule.outcome,
|
|
897
|
-
...(browserEventKind === undefined ? {} : { browser_event_kind: browserEventKind }),
|
|
898
|
-
...(sourceUrl === null ? {} : { source_url: sourceUrl })
|
|
899
|
-
}
|
|
900
|
-
};
|
|
643
|
+
enqueueAnalyticsEvent(event) {
|
|
644
|
+
this.eventTransport.enqueueAnalytics(event);
|
|
901
645
|
}
|
|
902
646
|
enqueueInternalEvent(event, countTowardSession = true, applyBeforeSend = true) {
|
|
903
647
|
const config = this.config;
|
|
904
|
-
if (config === null || this.
|
|
648
|
+
if (config === null || this.eventTransport.debugRejected) {
|
|
905
649
|
return;
|
|
906
650
|
}
|
|
907
|
-
if (applyBeforeSend) {
|
|
651
|
+
if (applyBeforeSend && event.event_type !== "analytics_event") {
|
|
908
652
|
const beforeSendEvent = applyBrowserBeforeSend(event, config.beforeSend);
|
|
909
653
|
if (beforeSendEvent === null) {
|
|
910
654
|
return;
|
|
911
655
|
}
|
|
912
656
|
event = beforeSendEvent;
|
|
913
657
|
}
|
|
914
|
-
this.
|
|
658
|
+
this.eventTransport.enqueueDebug(event);
|
|
915
659
|
if (countTowardSession && event.event_type !== "frontend_exception") {
|
|
916
660
|
this.sessionEventCount += 1;
|
|
917
661
|
}
|
|
918
|
-
if (this.bufferedEvents.length >= config.batchSize) {
|
|
919
|
-
queueMicrotask(() => {
|
|
920
|
-
void this.flush();
|
|
921
|
-
});
|
|
922
|
-
return;
|
|
923
|
-
}
|
|
924
|
-
this.scheduleFlush();
|
|
925
|
-
}
|
|
926
|
-
buildSuppressionKey(event) {
|
|
927
|
-
if (event.event_type === "frontend_exception") {
|
|
928
|
-
const stackFrame = event.payload.stack.split("\n")[1]?.trim() ?? null;
|
|
929
|
-
return JSON.stringify({
|
|
930
|
-
event_type: event.event_type,
|
|
931
|
-
name: event.payload.name,
|
|
932
|
-
message: event.payload.message,
|
|
933
|
-
stack_frame: stackFrame,
|
|
934
|
-
route: event.payload.route
|
|
935
|
-
});
|
|
936
|
-
}
|
|
937
|
-
if (event.event_type === "log_event") {
|
|
938
|
-
return JSON.stringify({
|
|
939
|
-
event_type: event.event_type,
|
|
940
|
-
level: event.payload.level,
|
|
941
|
-
message: event.payload.message,
|
|
942
|
-
attributes: event.payload.attributes
|
|
943
|
-
});
|
|
944
|
-
}
|
|
945
|
-
if (event.event_type === "request_event") {
|
|
946
|
-
return JSON.stringify({
|
|
947
|
-
event_type: event.event_type,
|
|
948
|
-
method: event.payload.method,
|
|
949
|
-
path: event.payload.path,
|
|
950
|
-
response_status: event.payload.response_status
|
|
951
|
-
});
|
|
952
|
-
}
|
|
953
|
-
return null;
|
|
954
662
|
}
|
|
955
663
|
shouldCaptureBySampleRate(event) {
|
|
956
664
|
const config = this.config;
|
|
@@ -965,78 +673,9 @@ export class BrowserSdk {
|
|
|
965
673
|
}
|
|
966
674
|
return config.sampleRate >= 1 || Math.random() <= config.sampleRate;
|
|
967
675
|
}
|
|
968
|
-
scheduleFlush(delayMs) {
|
|
969
|
-
const config = this.config;
|
|
970
|
-
if (config === null) {
|
|
971
|
-
return;
|
|
972
|
-
}
|
|
973
|
-
if (this.flushTimer !== null) {
|
|
974
|
-
clearTimeout(this.flushTimer);
|
|
975
|
-
this.flushTimer = null;
|
|
976
|
-
}
|
|
977
|
-
this.flushTimer = setTimeout(() => {
|
|
978
|
-
this.flushTimer = null;
|
|
979
|
-
void this.flush();
|
|
980
|
-
}, delayMs ?? config.flushInterval);
|
|
981
|
-
}
|
|
982
|
-
clearFlushTimer() {
|
|
983
|
-
if (this.flushTimer !== null) {
|
|
984
|
-
clearTimeout(this.flushTimer);
|
|
985
|
-
this.flushTimer = null;
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
676
|
flushViaBeacon() {
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
if (config === null || this.bufferedEvents.length === 0 || navigatorSource === null) {
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
994
|
-
const pendingEvents = [...this.bufferedEvents];
|
|
995
|
-
const body = buildBrowserTransportRequestBody(config.transportMode, pendingEvents);
|
|
996
|
-
const flushViaKeepalive = () => {
|
|
997
|
-
if (config.fetchImpl === null) {
|
|
998
|
-
void this.flush();
|
|
999
|
-
return;
|
|
1000
|
-
}
|
|
1001
|
-
void config
|
|
1002
|
-
.fetchImpl(config.endpoint, {
|
|
1003
|
-
method: "POST",
|
|
1004
|
-
headers: this.getTransportHeaders(config),
|
|
1005
|
-
body,
|
|
1006
|
-
keepalive: true
|
|
1007
|
-
})
|
|
1008
|
-
.then(() => {
|
|
1009
|
-
if (this.bufferedEvents === pendingEvents || this.sameLeadingEvents(pendingEvents)) {
|
|
1010
|
-
this.bufferedEvents.splice(0, pendingEvents.length);
|
|
1011
|
-
}
|
|
1012
|
-
this.nextRetryAt = null;
|
|
1013
|
-
this.clearFlushTimer();
|
|
1014
|
-
})
|
|
1015
|
-
.catch(() => {
|
|
1016
|
-
return;
|
|
1017
|
-
});
|
|
1018
|
-
};
|
|
1019
|
-
if (typeof navigatorSource.sendBeacon !== "function") {
|
|
1020
|
-
flushViaKeepalive();
|
|
1021
|
-
return;
|
|
1022
|
-
}
|
|
1023
|
-
const beaconBody = typeof Blob === "function"
|
|
1024
|
-
? new Blob([body], { type: "application/json" })
|
|
1025
|
-
: body;
|
|
1026
|
-
const accepted = navigatorSource.sendBeacon(config.endpoint, beaconBody);
|
|
1027
|
-
if (accepted) {
|
|
1028
|
-
this.bufferedEvents = [];
|
|
1029
|
-
this.nextRetryAt = null;
|
|
1030
|
-
this.clearFlushTimer();
|
|
1031
|
-
return;
|
|
1032
|
-
}
|
|
1033
|
-
flushViaKeepalive();
|
|
1034
|
-
}
|
|
1035
|
-
sameLeadingEvents(events) {
|
|
1036
|
-
if (this.bufferedEvents.length < events.length) {
|
|
1037
|
-
return false;
|
|
1038
|
-
}
|
|
1039
|
-
return events.every((event, index) => this.bufferedEvents[index]?.event_id === event.event_id);
|
|
677
|
+
this.analyticsController.prepareForUnload();
|
|
678
|
+
this.eventTransport.flushViaBeacon();
|
|
1040
679
|
}
|
|
1041
680
|
shouldCaptureNonExceptionEvent() {
|
|
1042
681
|
const config = this.config;
|
|
@@ -1056,17 +695,6 @@ export class BrowserSdk {
|
|
|
1056
695
|
project_token: config.projectToken
|
|
1057
696
|
};
|
|
1058
697
|
}
|
|
1059
|
-
getTransportHeaders(config) {
|
|
1060
|
-
if (config.projectToken === null) {
|
|
1061
|
-
return {
|
|
1062
|
-
"content-type": "application/json"
|
|
1063
|
-
};
|
|
1064
|
-
}
|
|
1065
|
-
return {
|
|
1066
|
-
"content-type": "application/json",
|
|
1067
|
-
authorization: `Bearer ${config.projectToken}`
|
|
1068
|
-
};
|
|
1069
|
-
}
|
|
1070
698
|
createSdkEventEnvelope(config, input) {
|
|
1071
699
|
const event = createEventEnvelope(input);
|
|
1072
700
|
this.removeEmptyProjectToken(event, config);
|
|
@@ -1107,159 +735,6 @@ export class BrowserSdk {
|
|
|
1107
735
|
}), false);
|
|
1108
736
|
}
|
|
1109
737
|
}
|
|
1110
|
-
shouldCaptureNetworkRequest(url, statusCode, durationMs) {
|
|
1111
|
-
const config = this.config;
|
|
1112
|
-
if (config === null) {
|
|
1113
|
-
return false;
|
|
1114
|
-
}
|
|
1115
|
-
const filter = config.networkFilter;
|
|
1116
|
-
if (filter.urlPatterns.length > 0 && !filter.urlPatterns.some((pattern) => matchesBrowserPattern(url, pattern))) {
|
|
1117
|
-
return false;
|
|
1118
|
-
}
|
|
1119
|
-
if (filter.urlDenyPatterns.some((pattern) => matchesBrowserPattern(url, pattern))) {
|
|
1120
|
-
return false;
|
|
1121
|
-
}
|
|
1122
|
-
if (filter.minResponseTime !== null && durationMs < filter.minResponseTime) {
|
|
1123
|
-
return false;
|
|
1124
|
-
}
|
|
1125
|
-
return matchesStatusCodeFilter(statusCode, filter.statusCodes);
|
|
1126
|
-
}
|
|
1127
|
-
shouldCaptureFailedNetworkRequest(url, durationMs) {
|
|
1128
|
-
const config = this.config;
|
|
1129
|
-
if (config === null) {
|
|
1130
|
-
return false;
|
|
1131
|
-
}
|
|
1132
|
-
const filter = config.networkFilter;
|
|
1133
|
-
if (filter.urlPatterns.length > 0 && !filter.urlPatterns.some((pattern) => matchesBrowserPattern(url, pattern))) {
|
|
1134
|
-
return false;
|
|
1135
|
-
}
|
|
1136
|
-
if (filter.urlDenyPatterns.some((pattern) => matchesBrowserPattern(url, pattern))) {
|
|
1137
|
-
return false;
|
|
1138
|
-
}
|
|
1139
|
-
if (filter.minResponseTime !== null && durationMs < filter.minResponseTime) {
|
|
1140
|
-
return false;
|
|
1141
|
-
}
|
|
1142
|
-
return true;
|
|
1143
|
-
}
|
|
1144
|
-
pruneExpiredRemoteProbeDirectives(nowMs) {
|
|
1145
|
-
const directives = this.remoteProbeState.directives.filter((directive) => Date.parse(directive.expiresAt) > nowMs);
|
|
1146
|
-
if (this.activeTriggerDirective !== null && Date.parse(this.activeTriggerDirective.expiresAt) <= nowMs) {
|
|
1147
|
-
this.activeTriggerDirective = null;
|
|
1148
|
-
}
|
|
1149
|
-
if (directives.length === this.remoteProbeState.directives.length) {
|
|
1150
|
-
return;
|
|
1151
|
-
}
|
|
1152
|
-
this.remoteProbeState = {
|
|
1153
|
-
...this.remoteProbeState,
|
|
1154
|
-
directives
|
|
1155
|
-
};
|
|
1156
|
-
}
|
|
1157
|
-
async refreshRemoteProbeConfig() {
|
|
1158
|
-
const config = this.config;
|
|
1159
|
-
if (config === null || config.fetchImpl === null || config.transportMode !== "direct" || config.projectToken === null) {
|
|
1160
|
-
return;
|
|
1161
|
-
}
|
|
1162
|
-
try {
|
|
1163
|
-
const response = await config.fetchImpl(deriveSdkConfigEndpoint(config.endpoint), {
|
|
1164
|
-
method: "GET",
|
|
1165
|
-
headers: {
|
|
1166
|
-
authorization: `Bearer ${config.projectToken}`
|
|
1167
|
-
}
|
|
1168
|
-
});
|
|
1169
|
-
if (response.status === 304 || typeof response.json !== "function") {
|
|
1170
|
-
return;
|
|
1171
|
-
}
|
|
1172
|
-
const payload = await response.json();
|
|
1173
|
-
const parsed = parseRemoteProbeConfigPayload(payload, Date.now());
|
|
1174
|
-
if (parsed !== null) {
|
|
1175
|
-
this.remoteProbeState = parsed;
|
|
1176
|
-
this.pruneExpiredRemoteProbeDirectives(Date.now());
|
|
1177
|
-
await this.activatePendingTriggerTokenIfPossible();
|
|
1178
|
-
}
|
|
1179
|
-
config.captureRules = parseRemoteCaptureRulesPayload(payload);
|
|
1180
|
-
}
|
|
1181
|
-
catch {
|
|
1182
|
-
return;
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
updateRemoteProbeStateFromIngestionResponse(payload) {
|
|
1186
|
-
const directives = parseIngestionProbeDirectives(payload, Date.now());
|
|
1187
|
-
if (directives === null) {
|
|
1188
|
-
this.pruneExpiredRemoteProbeDirectives(Date.now());
|
|
1189
|
-
return;
|
|
1190
|
-
}
|
|
1191
|
-
this.remoteProbeState = {
|
|
1192
|
-
...this.remoteProbeState,
|
|
1193
|
-
directives
|
|
1194
|
-
};
|
|
1195
|
-
this.pruneExpiredRemoteProbeDirectives(Date.now());
|
|
1196
|
-
}
|
|
1197
|
-
consumeTriggerTokenFromLocation() {
|
|
1198
|
-
const locationSource = getLocationSource();
|
|
1199
|
-
const historySource = getHistorySource();
|
|
1200
|
-
const search = typeof locationSource?.search === "string" ? locationSource.search : "";
|
|
1201
|
-
if (search.length === 0) {
|
|
1202
|
-
return null;
|
|
1203
|
-
}
|
|
1204
|
-
const params = new URLSearchParams(search.startsWith("?") ? search.slice(1) : search);
|
|
1205
|
-
const token = params.get("_debug_probe");
|
|
1206
|
-
if (token === null || token.length === 0) {
|
|
1207
|
-
return null;
|
|
1208
|
-
}
|
|
1209
|
-
params.delete("_debug_probe");
|
|
1210
|
-
const cleanedPath = `${locationSource?.pathname ?? ""}${params.toString().length > 0 ? `?${params.toString()}` : ""}`;
|
|
1211
|
-
historySource?.replaceState({}, "", cleanedPath);
|
|
1212
|
-
return token;
|
|
1213
|
-
}
|
|
1214
|
-
async activatePendingTriggerTokenIfPossible() {
|
|
1215
|
-
if (this.pendingTriggerToken === null) {
|
|
1216
|
-
return;
|
|
1217
|
-
}
|
|
1218
|
-
const directive = await validateBrowserTriggerToken({
|
|
1219
|
-
token: this.pendingTriggerToken,
|
|
1220
|
-
triggerTokenKey: this.remoteProbeState.triggerTokenKey,
|
|
1221
|
-
nowMs: Date.now()
|
|
1222
|
-
});
|
|
1223
|
-
this.pendingTriggerToken = null;
|
|
1224
|
-
this.activeTriggerDirective = directive;
|
|
1225
|
-
}
|
|
1226
|
-
normalizeProbeInput(data) {
|
|
1227
|
-
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
1228
|
-
return { value: data };
|
|
1229
|
-
}
|
|
1230
|
-
return data;
|
|
1231
|
-
}
|
|
1232
|
-
getMatchingRemoteProbeDirectives(label, nowMs) {
|
|
1233
|
-
const config = this.config;
|
|
1234
|
-
if (config === null ||
|
|
1235
|
-
this.remoteProbeState.probesEnabled !== true ||
|
|
1236
|
-
this.remoteProbeState.remoteProbesEnabled !== true) {
|
|
1237
|
-
return [];
|
|
1238
|
-
}
|
|
1239
|
-
this.pruneExpiredRemoteProbeDirectives(nowMs);
|
|
1240
|
-
const activeDirectives = this.activeTriggerDirective === null
|
|
1241
|
-
? this.remoteProbeState.directives
|
|
1242
|
-
: [...this.remoteProbeState.directives, this.activeTriggerDirective];
|
|
1243
|
-
return activeDirectives.filter((directive) => {
|
|
1244
|
-
if (directive.service !== "*" && directive.service !== config.service) {
|
|
1245
|
-
return false;
|
|
1246
|
-
}
|
|
1247
|
-
if (directive.environment !== "*" && directive.environment !== config.environment) {
|
|
1248
|
-
return false;
|
|
1249
|
-
}
|
|
1250
|
-
return this.matchesProbeLabelPattern(directive.labelPattern, label);
|
|
1251
|
-
});
|
|
1252
|
-
}
|
|
1253
|
-
matchesProbeLabelPattern(pattern, label) {
|
|
1254
|
-
if (pattern === "*") {
|
|
1255
|
-
return true;
|
|
1256
|
-
}
|
|
1257
|
-
if (pattern.endsWith(".*")) {
|
|
1258
|
-
const prefix = pattern.slice(0, -2);
|
|
1259
|
-
return label === prefix || label.startsWith(`${prefix}.`);
|
|
1260
|
-
}
|
|
1261
|
-
return pattern === label;
|
|
1262
|
-
}
|
|
1263
738
|
}
|
|
1264
739
|
export function createDebugBundleBrowserSdk() {
|
|
1265
740
|
return new BrowserSdk();
|