@greenhat-services/magpie-browser 0.0.0-stage → 0.1.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/dist/index.js ADDED
@@ -0,0 +1,597 @@
1
+ import { truncate, normalize, TRACE_HEADER, formatTraceHeader, scrubUrl, parseTraceHeader, getClient, Client, setClient, endpoint, SDK_VERSION } from '@greenhat-services/magpie-core';
2
+ export * from '@greenhat-services/magpie-core';
3
+
4
+ // src/index.ts
5
+ var CONSOLE_LEVELS = [
6
+ ["debug", "debug"],
7
+ ["log", "info"],
8
+ ["info", "info"],
9
+ ["warn", "warning"],
10
+ ["error", "error"]
11
+ ];
12
+ function consoleBreadcrumbs() {
13
+ return {
14
+ name: "ConsoleBreadcrumbs",
15
+ setup(client) {
16
+ if (typeof console === "undefined") return;
17
+ const originals = /* @__PURE__ */ new Map();
18
+ let inside = false;
19
+ for (const [method, level] of CONSOLE_LEVELS) {
20
+ const original = console[method];
21
+ if (typeof original !== "function") continue;
22
+ originals.set(method, original);
23
+ console[method] = function(...args) {
24
+ if (!inside) {
25
+ inside = true;
26
+ try {
27
+ client.addBreadcrumb({ category: "log", level, message: args.map(format).join(" ") });
28
+ } finally {
29
+ inside = false;
30
+ }
31
+ }
32
+ return original.apply(this, args);
33
+ };
34
+ }
35
+ return () => {
36
+ for (const [method, original] of originals) console[method] = original;
37
+ };
38
+ }
39
+ };
40
+ }
41
+ function format(value) {
42
+ if (typeof value === "string") return truncate(value, 300);
43
+ if (value instanceof Error) return truncate(`${value.name}: ${value.message}`, 300);
44
+ try {
45
+ return truncate(JSON.stringify(normalize(value, 2, 100, 10)) ?? String(value), 300);
46
+ } catch {
47
+ return String(value);
48
+ }
49
+ }
50
+ function clickBreadcrumbs() {
51
+ return {
52
+ name: "ClickBreadcrumbs",
53
+ setup(client) {
54
+ if (typeof document === "undefined") return;
55
+ const onClick = (event) => {
56
+ const el = event.target;
57
+ if (!el || !("tagName" in el)) return;
58
+ client.addBreadcrumb({ category: "ui.click", message: describeElement(el) });
59
+ };
60
+ document.addEventListener("click", onClick, { capture: true, passive: true });
61
+ return () => document.removeEventListener("click", onClick, { capture: true });
62
+ }
63
+ };
64
+ }
65
+ function describeElement(el) {
66
+ const parts = [];
67
+ for (let node = el, depth = 0; node && depth < 3; node = node.parentElement, depth++) {
68
+ const tag = node.tagName.toLowerCase();
69
+ if (tag === "html" || tag === "body") break;
70
+ let part = tag;
71
+ if (node.id) part += `#${node.id}`;
72
+ const classes = (typeof node.className === "string" ? node.className : "").split(/\s+/).filter(Boolean).slice(0, 3);
73
+ if (classes.length) part += `.${classes.join(".")}`;
74
+ const testId = node.getAttribute("data-testid");
75
+ if (testId) part += `[data-testid=${testId}]`;
76
+ parts.unshift(part);
77
+ if (node.id) break;
78
+ }
79
+ return truncate(parts.join(" > ") || el.tagName.toLowerCase(), 200);
80
+ }
81
+ function historyBreadcrumbs(onNavigate) {
82
+ return {
83
+ name: "HistoryBreadcrumbs",
84
+ setup(client) {
85
+ if (typeof window === "undefined" || !window.history) return;
86
+ let from = location.href;
87
+ const record = () => {
88
+ const to = location.href;
89
+ if (to === from) return;
90
+ client.addBreadcrumb({ category: "navigation", data: { from: scrubUrl(from), to: scrubUrl(to) } });
91
+ from = to;
92
+ onNavigate?.(to);
93
+ };
94
+ const restore = [];
95
+ for (const method of ["pushState", "replaceState"]) {
96
+ const original = history[method];
97
+ history[method] = function(...args) {
98
+ const result = original.apply(this, args);
99
+ record();
100
+ return result;
101
+ };
102
+ restore.push(() => {
103
+ history[method] = original;
104
+ });
105
+ }
106
+ window.addEventListener("popstate", record);
107
+ restore.push(() => window.removeEventListener("popstate", record));
108
+ return () => restore.forEach((r) => r());
109
+ }
110
+ };
111
+ }
112
+
113
+ // src/globalHandlers.ts
114
+ function globalHandlers() {
115
+ return {
116
+ name: "GlobalHandlers",
117
+ setup(client) {
118
+ if (typeof window === "undefined") return;
119
+ const onError = (event) => {
120
+ const target = event.target;
121
+ if (target && target !== window && "tagName" in target) {
122
+ const src = target.src || target.href || "";
123
+ client.addBreadcrumb({ category: "resource", level: "warning", message: `Failed to load <${target.tagName.toLowerCase()}> ${src}`.trim() });
124
+ return;
125
+ }
126
+ const e = event;
127
+ const mechanism = { type: "onerror", handled: false };
128
+ if (e.error !== void 0 && e.error !== null) {
129
+ client.captureException(e.error, { mechanism });
130
+ return;
131
+ }
132
+ if (!e.filename && /^Script error\.?$/i.test(e.message)) return;
133
+ const error = new Error(e.message);
134
+ error.name = /^(\w+Error):/.exec(e.message)?.[1] ?? "Error";
135
+ error.stack = e.filename ? `${error.name}: ${e.message}
136
+ at ${e.filename}:${e.lineno || 0}:${e.colno || 0}` : void 0;
137
+ client.captureException(error, { mechanism });
138
+ };
139
+ const onRejection = (event) => {
140
+ client.captureException(event.reason, { mechanism: { type: "onunhandledrejection", handled: false } });
141
+ };
142
+ window.addEventListener("error", onError, true);
143
+ window.addEventListener("unhandledrejection", onRejection);
144
+ return () => {
145
+ window.removeEventListener("error", onError, true);
146
+ window.removeEventListener("unhandledrejection", onRejection);
147
+ };
148
+ }
149
+ };
150
+ }
151
+
152
+ // src/idle.ts
153
+ var trackers = /* @__PURE__ */ new WeakMap();
154
+ function idleFor(tx) {
155
+ return trackers.get(tx);
156
+ }
157
+ function trackIdle(tx, options) {
158
+ let pending = 0;
159
+ let loaded = !options.waitForLoad || typeof document !== "undefined" && document.readyState === "complete";
160
+ let lastActivity = Date.now();
161
+ let timer;
162
+ let ended = false;
163
+ const cleanup = [];
164
+ const end = (backdate) => {
165
+ if (ended) return;
166
+ ended = true;
167
+ if (timer) clearTimeout(timer);
168
+ cleanup.forEach((c) => c());
169
+ const endAt = Math.max(tx.startTimestamp, (backdate ? lastActivity : Date.now()) / 1e3);
170
+ options.beforeEnd?.(tx, endAt);
171
+ tx.end(endAt);
172
+ options.onEnd?.(tx);
173
+ };
174
+ const arm = () => {
175
+ if (timer) clearTimeout(timer);
176
+ if (ended || pending > 0 || !loaded) return;
177
+ timer = setTimeout(() => end(true), options.idleTimeout);
178
+ };
179
+ const idle = {
180
+ requestStarted() {
181
+ pending++;
182
+ lastActivity = Date.now();
183
+ if (timer) clearTimeout(timer);
184
+ },
185
+ requestFinished() {
186
+ pending = Math.max(0, pending - 1);
187
+ lastActivity = Date.now();
188
+ arm();
189
+ },
190
+ finish: () => end(false)
191
+ };
192
+ trackers.set(tx, idle);
193
+ const max = setTimeout(() => end(false), options.maxDuration);
194
+ cleanup.push(() => clearTimeout(max));
195
+ if (typeof window !== "undefined") {
196
+ if (!loaded) {
197
+ const onLoad = () => {
198
+ loaded = true;
199
+ lastActivity = Date.now();
200
+ arm();
201
+ };
202
+ window.addEventListener("load", onLoad, { once: true });
203
+ cleanup.push(() => window.removeEventListener("load", onLoad));
204
+ }
205
+ const onHide = () => {
206
+ if (document.visibilityState === "hidden") end(true);
207
+ };
208
+ document.addEventListener("visibilitychange", onHide);
209
+ cleanup.push(() => document.removeEventListener("visibilitychange", onHide));
210
+ }
211
+ arm();
212
+ return idle;
213
+ }
214
+
215
+ // src/http.ts
216
+ var refusedHosts = /* @__PURE__ */ new Set();
217
+ function httpIntegration() {
218
+ return {
219
+ name: "Http",
220
+ setup(client) {
221
+ if (typeof window === "undefined") return;
222
+ const restore = [];
223
+ const skip = (url) => !!client.dsn && absolute(url).startsWith(client.dsn.origin);
224
+ if (typeof window.fetch === "function") {
225
+ const original = window.fetch;
226
+ window.fetch = function patchedFetch(input, init2) {
227
+ const info = describe(input, init2);
228
+ if (skip(info.url)) return original.call(this, input, init2);
229
+ const crossOrigin = !sameOrigin(absolute(info.url));
230
+ const host = hostOf(info.url);
231
+ const call = begin(client, info, crossOrigin && refusedHosts.has(host));
232
+ let args = [input, init2];
233
+ if (call.header) {
234
+ const headers = new Headers(init2?.headers ?? (typeof Request !== "undefined" && input instanceof Request ? input.headers : void 0));
235
+ if (!headers.has(TRACE_HEADER)) headers.set(TRACE_HEADER, call.header);
236
+ args = [input, { ...init2, headers }];
237
+ }
238
+ const done = (response) => {
239
+ call.finish(response.status);
240
+ return response;
241
+ };
242
+ const fail = (error) => {
243
+ call.finish(0);
244
+ throw error;
245
+ };
246
+ return original.apply(this, args).then(done, (error) => {
247
+ if (call.header && crossOrigin && error instanceof TypeError) {
248
+ refusedHosts.add(host);
249
+ if (client.options.debug) console.warn(`[magpie] ${host} rejected the ${TRACE_HEADER} header (its CORS policy does not allow it), so it will not be sent there again. Remove it from tracePropagationTargets, or allow the header on that API.`);
250
+ if (info.method === "GET" || info.method === "HEAD") return original.call(this, input, init2).then(done, fail);
251
+ }
252
+ return fail(error);
253
+ });
254
+ };
255
+ restore.push(() => {
256
+ window.fetch = original;
257
+ });
258
+ }
259
+ if (typeof XMLHttpRequest !== "undefined") {
260
+ const proto = XMLHttpRequest.prototype;
261
+ const open = proto.open;
262
+ const send = proto.send;
263
+ const state = /* @__PURE__ */ new WeakMap();
264
+ proto.open = function(method, url, ...rest) {
265
+ state.set(this, { method: String(method).toUpperCase(), url: String(url) });
266
+ return open.call(this, method, url, ...rest);
267
+ };
268
+ proto.send = function(body) {
269
+ const info = state.get(this);
270
+ if (info && !skip(info.url)) {
271
+ const call = begin(client, info);
272
+ if (call.header) {
273
+ try {
274
+ this.setRequestHeader(TRACE_HEADER, call.header);
275
+ } catch {
276
+ }
277
+ }
278
+ this.addEventListener("loadend", () => call.finish(this.status));
279
+ }
280
+ return send.call(this, body);
281
+ };
282
+ restore.push(() => {
283
+ proto.open = open;
284
+ proto.send = send;
285
+ });
286
+ }
287
+ return () => restore.forEach((r) => r());
288
+ }
289
+ };
290
+ }
291
+ function begin(client, info, withholdHeader = false) {
292
+ const started = performance.now();
293
+ const parent = client.activeTransaction;
294
+ const idle = parent ? idleFor(parent) : void 0;
295
+ const tracing = (client.options.tracesSampleRate ?? 0) > 0 && parent !== void 0;
296
+ let span;
297
+ if (parent?.sampled) {
298
+ span = parent.startChild({ op: "http.client", description: `${info.method} ${stripQuery(info.url)}`, data: { method: info.method } });
299
+ idle?.requestStarted();
300
+ } else if (parent) {
301
+ idle?.requestStarted();
302
+ }
303
+ const target = absolute(info.url);
304
+ const header = !withholdHeader && tracing && parent && (sameOrigin(target) || client.shouldPropagate(target)) ? formatTraceHeader(parent.traceId, span?.spanId ?? parent.spanId, parent.sampled) : void 0;
305
+ return {
306
+ header,
307
+ finish(status) {
308
+ const duration = Math.round(performance.now() - started);
309
+ const failed = status === 0 || status >= 400;
310
+ if (span) {
311
+ if (status) span.setData("status_code", status);
312
+ span.setStatus(status === 0 ? "unknown_error" : status >= 500 ? "internal_error" : "ok");
313
+ span.end();
314
+ }
315
+ if (parent) idle?.requestFinished();
316
+ client.addBreadcrumb({
317
+ category: "http",
318
+ type: "http",
319
+ level: status >= 500 || status === 0 ? "error" : failed ? "warning" : "info",
320
+ message: `${info.method} ${scrubUrl(info.url)}`,
321
+ data: { method: info.method, ...status ? { status_code: status } : { status_code: "failed" }, duration_ms: duration }
322
+ });
323
+ }
324
+ };
325
+ }
326
+ function describe(input, init2) {
327
+ const isRequest = typeof Request !== "undefined" && input instanceof Request;
328
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : isRequest ? input.url : String(input);
329
+ return { method: (init2?.method ?? (isRequest ? input.method : "GET")).toUpperCase(), url };
330
+ }
331
+ function absolute(url) {
332
+ try {
333
+ return new URL(url, location.href).href;
334
+ } catch {
335
+ return url;
336
+ }
337
+ }
338
+ function hostOf(url) {
339
+ try {
340
+ return new URL(url, location.href).host;
341
+ } catch {
342
+ return url;
343
+ }
344
+ }
345
+ function sameOrigin(absoluteUrl) {
346
+ try {
347
+ return new URL(absoluteUrl).origin === location.origin;
348
+ } catch {
349
+ return false;
350
+ }
351
+ }
352
+ function stripQuery(url) {
353
+ return scrubUrl(url).split(/[?#]/)[0];
354
+ }
355
+ var KEEPALIVE_LIMIT = 6e4;
356
+ function fetchTransport(dsn, fetchImpl = (...args) => globalThis.fetch(...args)) {
357
+ const url = (payload) => `${endpoint(dsn, payload.type === "transaction" ? "transactions" : "events")}?magpie_key=${encodeURIComponent(dsn.publicKey)}`;
358
+ return {
359
+ async send(payload) {
360
+ try {
361
+ const body = JSON.stringify(payload);
362
+ const response = await fetchImpl(url(payload), {
363
+ method: "POST",
364
+ body,
365
+ headers: { "Content-Type": "text/plain" },
366
+ keepalive: body.length < KEEPALIVE_LIMIT,
367
+ mode: "cors",
368
+ credentials: "omit",
369
+ referrerPolicy: "no-referrer"
370
+ });
371
+ return { ok: response.ok, status: response.status, retryAfter: parseRetryAfter(response.headers.get("retry-after")) };
372
+ } catch {
373
+ return { ok: false };
374
+ }
375
+ }
376
+ };
377
+ }
378
+ function parseRetryAfter(value) {
379
+ const seconds = Number(value);
380
+ return value !== null && Number.isFinite(seconds) && seconds > 0 ? Math.min(seconds, 3600) : void 0;
381
+ }
382
+
383
+ // src/uaparse.ts
384
+ function parseUserAgent(ua) {
385
+ const first = (re) => re.exec(ua)?.[1];
386
+ const major = (v) => v ? v.split(".")[0] : "";
387
+ let browser = { name: "Unknown", version: "" };
388
+ const rules = [
389
+ ["Edge", /\bEdgA?i?O?S?\/([\d.]+)/],
390
+ ["Opera", /\bOPR\/([\d.]+)/],
391
+ ["Samsung Internet", /\bSamsungBrowser\/([\d.]+)/],
392
+ ["Firefox", /\b(?:Firefox|FxiOS)\/([\d.]+)/],
393
+ ["Chrome", /\b(?:Headless)?(?:Chrome|Chromium|CriOS)\/([\d.]+)/],
394
+ ["Safari", /\bVersion\/([\d.]+).*\bSafari\//]
395
+ ];
396
+ for (const [name, re] of rules) {
397
+ const v = first(re);
398
+ if (v) {
399
+ browser = { name, version: major(v) };
400
+ break;
401
+ }
402
+ }
403
+ let os = { name: "Unknown" };
404
+ if (/\bWindows NT/.test(ua)) {
405
+ const nt = first(/\bWindows NT ([\d.]+)/);
406
+ os = { name: "Windows", version: nt === "10.0" ? "10" : nt === "6.3" ? "8.1" : nt === "6.2" ? "8" : nt === "6.1" ? "7" : nt };
407
+ } else if (/\b(iPhone|iPad|iPod)\b/.test(ua)) {
408
+ os = { name: "iOS", version: first(/\bOS ([\d_]+)/)?.replace(/_/g, ".") };
409
+ } else if (/\bAndroid\b/.test(ua)) {
410
+ os = { name: "Android", version: first(/\bAndroid ([\d.]+)/) };
411
+ } else if (/\bMac OS X\b/.test(ua)) {
412
+ os = { name: "macOS", version: first(/\bMac OS X ([\d_.]+)/)?.replace(/_/g, ".") };
413
+ } else if (/\bCrOS\b/.test(ua)) {
414
+ os = { name: "Chrome OS" };
415
+ } else if (/\bLinux\b/.test(ua)) {
416
+ os = { name: "Linux" };
417
+ }
418
+ const device = /\b(iPad|Tablet)\b/.test(ua) || /\bAndroid\b/.test(ua) && !/\bMobile\b/.test(ua) ? "tablet" : /\b(iPhone|iPod|Mobile)\b/.test(ua) ? "mobile" : "desktop";
419
+ return { browser, os, device };
420
+ }
421
+
422
+ // src/runtime.ts
423
+ var SDK_NAME = "magpie.javascript.browser";
424
+ function browserRuntime(options = {}) {
425
+ let agent;
426
+ const ua = () => typeof navigator === "undefined" ? "" : navigator.userAgent;
427
+ const parsed = () => agent ??= parseUserAgent(ua());
428
+ return {
429
+ sdk: { name: options.sdkName ?? SDK_NAME, version: SDK_VERSION },
430
+ platform: "javascript",
431
+ // The caller passes the original fetch, taken before it is patched, so delivering an event is never itself traced.
432
+ createTransport: (dsn) => fetchTransport(dsn, options.fetch),
433
+ contexts() {
434
+ const a = parsed();
435
+ return {
436
+ browser: { name: a.browser.name, version: a.browser.version },
437
+ os: { name: a.os.name, ...a.os.version ? { version: a.os.version } : {} },
438
+ device: {
439
+ type: a.device,
440
+ ...typeof screen !== "undefined" ? { screen_resolution: `${screen.width}x${screen.height}` } : {},
441
+ ...typeof navigator !== "undefined" && navigator.language ? { language: navigator.language } : {}
442
+ }
443
+ };
444
+ },
445
+ request() {
446
+ if (typeof location === "undefined") return void 0;
447
+ return {
448
+ url: location.href,
449
+ headers: {
450
+ "User-Agent": ua(),
451
+ ...options.sendDefaultPii && typeof document !== "undefined" && document.referrer ? { Referer: document.referrer } : {}
452
+ }
453
+ };
454
+ }
455
+ };
456
+ }
457
+ var DEFAULTS = { pageload: true, history: false, idleTimeout: 1e3, maxDuration: 15e3 };
458
+ var settings = /* @__PURE__ */ new WeakMap();
459
+ function tracingIntegration(options = {}) {
460
+ return {
461
+ name: "Tracing",
462
+ setup(client) {
463
+ if (typeof window === "undefined" || !((client.options.tracesSampleRate ?? 0) > 0)) return;
464
+ const config = { ...DEFAULTS, ...options };
465
+ settings.set(client, config);
466
+ const teardown = [];
467
+ if (config.pageload) startPageload(client, config);
468
+ if (config.history) {
469
+ const onChange = () => startNavigation(client, location.pathname);
470
+ window.addEventListener("popstate", onChange);
471
+ teardown.push(() => window.removeEventListener("popstate", onChange));
472
+ }
473
+ return () => {
474
+ idleFor(client.activeTransaction)?.finish();
475
+ teardown.forEach((t) => t());
476
+ };
477
+ }
478
+ };
479
+ }
480
+ function startNavigation(client, name) {
481
+ const config = settings.get(client);
482
+ if (!config) return void 0;
483
+ if (client.activeTransaction) idleFor(client.activeTransaction)?.finish();
484
+ const tx = client.startTransaction({ name, op: "navigation" });
485
+ client.activeTransaction = tx;
486
+ client.scope.setTransactionName(name);
487
+ trackIdle(tx, {
488
+ idleTimeout: config.idleTimeout,
489
+ maxDuration: config.maxDuration,
490
+ onEnd: (ended) => {
491
+ if (client.activeTransaction === ended) client.activeTransaction = void 0;
492
+ }
493
+ });
494
+ return tx;
495
+ }
496
+ function renameActiveTransaction(client, name) {
497
+ client.activeTransaction?.setName(name);
498
+ client.scope.setTransactionName(name);
499
+ }
500
+ function startPageload(client, config) {
501
+ const meta = document.querySelector('meta[name="magpie-trace"]')?.getAttribute("content");
502
+ const parent = parseTraceHeader(meta);
503
+ const tx = client.startTransaction({
504
+ name: location.pathname,
505
+ op: "pageload",
506
+ startTimestamp: performance.timeOrigin / 1e3,
507
+ ...parent ? { traceId: parent.traceId, parentSpanId: parent.spanId, sampled: parent.sampled } : {}
508
+ });
509
+ client.activeTransaction = tx;
510
+ client.scope.setTransactionName(tx.name);
511
+ const lcp = observeLargestPaint();
512
+ trackIdle(tx, {
513
+ idleTimeout: config.idleTimeout,
514
+ maxDuration: config.maxDuration,
515
+ waitForLoad: true,
516
+ beforeEnd: (t) => addPageloadSpans(client, t, lcp()),
517
+ onEnd: (ended) => {
518
+ if (client.activeTransaction === ended) client.activeTransaction = void 0;
519
+ }
520
+ });
521
+ }
522
+ function observeLargestPaint() {
523
+ let value;
524
+ try {
525
+ const observer = new PerformanceObserver((list) => {
526
+ const last = list.getEntries().at(-1);
527
+ if (last) value = last.startTime;
528
+ });
529
+ observer.observe({ type: "largest-contentful-paint", buffered: true });
530
+ } catch {
531
+ }
532
+ return () => value;
533
+ }
534
+ function addPageloadSpans(client, tx, lcp) {
535
+ if (!tx.sampled) return;
536
+ try {
537
+ const origin = performance.timeOrigin;
538
+ const at = (ms) => (origin + ms) / 1e3;
539
+ const add = (op, description, from, to) => {
540
+ if (!(to > from) && !(op === "ui.paint" && to === from)) return;
541
+ const span = tx.startChild({ op, description, startTimestamp: at(from) });
542
+ span.end(at(to));
543
+ };
544
+ const nav = performance.getEntriesByType("navigation")[0];
545
+ if (nav) {
546
+ add("browser.dns", "DNS lookup", nav.domainLookupStart, nav.domainLookupEnd);
547
+ add("browser.connect", "Connection", nav.connectStart, nav.connectEnd);
548
+ add("browser.request", "Waiting for the first byte", nav.requestStart, nav.responseStart);
549
+ add("browser.response", "Downloading the document", nav.responseStart, nav.responseEnd);
550
+ add("browser.dom", "Building the page", nav.responseEnd, nav.domContentLoadedEventEnd);
551
+ add("browser.load", "Loading everything else", nav.domContentLoadedEventEnd, nav.loadEventEnd);
552
+ }
553
+ const fcp = performance.getEntriesByName("first-contentful-paint")[0];
554
+ if (fcp) add("ui.paint", "first-contentful-paint", fcp.startTime, fcp.startTime);
555
+ if (lcp !== void 0) add("ui.paint", "largest-contentful-paint", lcp, lcp);
556
+ const ingest = client.dsn?.origin;
557
+ performance.getEntriesByType("resource").filter((r) => {
558
+ const entry = r;
559
+ return ["script", "link", "css", "img"].includes(entry.initiatorType) && entry.responseEnd > 0 && !(ingest && entry.name.startsWith(ingest));
560
+ }).slice(0, 25).forEach((r) => {
561
+ const entry = r;
562
+ add(`resource.${entry.initiatorType}`, entry.name.split(/[?#]/)[0], entry.startTime, entry.responseEnd);
563
+ });
564
+ } catch {
565
+ }
566
+ }
567
+ var injectedRelease = () => globalThis.__MAGPIE_RELEASE__;
568
+ function init(options = {}) {
569
+ if (typeof window === "undefined") return void 0;
570
+ const previous = getClient();
571
+ if (previous) void previous.close(0);
572
+ const { captureUnhandled = true, breadcrumbs = true, tracing, sdkName, integrations = [], ...rest } = options;
573
+ const crumbs = breadcrumbs === false ? {} : { console: true, clicks: true, history: true, http: true, ...breadcrumbs === true ? {} : breadcrumbs };
574
+ const tracesOn = (rest.tracesSampleRate ?? 0) > 0 && tracing !== false;
575
+ const nativeFetch = typeof window.fetch === "function" ? window.fetch.bind(window) : void 0;
576
+ const client = new Client(
577
+ { ...rest, release: rest.release ?? injectedRelease() },
578
+ browserRuntime({ sendDefaultPii: rest.sendDefaultPii, sdkName, fetch: nativeFetch })
579
+ );
580
+ const defaults = [
581
+ ...captureUnhandled ? [globalHandlers()] : [],
582
+ ...crumbs.console ? [consoleBreadcrumbs()] : [],
583
+ ...crumbs.clicks ? [clickBreadcrumbs()] : [],
584
+ ...crumbs.history ? [historyBreadcrumbs()] : [],
585
+ // Request breadcrumbs and spans share one patch, and tracing needs it, so it runs when either is wanted.
586
+ ...crumbs.http || tracesOn ? [httpIntegration()] : [],
587
+ ...tracesOn ? [tracingIntegration(typeof tracing === "object" ? tracing : {})] : []
588
+ ];
589
+ setClient(client);
590
+ client.scope.setTransactionName(location.pathname);
591
+ client.setupIntegrations([...defaults.filter((i) => i.name !== "Tracing"), ...defaults.filter((i) => i.name === "Tracing"), ...integrations]);
592
+ return client;
593
+ }
594
+
595
+ export { clickBreadcrumbs, consoleBreadcrumbs, describeElement, fetchTransport, globalHandlers, historyBreadcrumbs, httpIntegration, init, parseUserAgent, renameActiveTransaction, startNavigation, tracingIntegration };
596
+ //# sourceMappingURL=index.js.map
597
+ //# sourceMappingURL=index.js.map