@prism-analytics/browser 0.0.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.mjs ADDED
@@ -0,0 +1,804 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ var __async = (__this, __arguments, generator) => {
21
+ return new Promise((resolve, reject) => {
22
+ var fulfilled = (value) => {
23
+ try {
24
+ step(generator.next(value));
25
+ } catch (e) {
26
+ reject(e);
27
+ }
28
+ };
29
+ var rejected = (value) => {
30
+ try {
31
+ step(generator.throw(value));
32
+ } catch (e) {
33
+ reject(e);
34
+ }
35
+ };
36
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
37
+ step((generator = generator.apply(__this, __arguments)).next());
38
+ });
39
+ };
40
+
41
+ // src/index.ts
42
+ import {
43
+ createPrismClient
44
+ } from "@prism-analytics/core";
45
+
46
+ // src/browser-runtime.ts
47
+ function browserCreateId() {
48
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
49
+ return crypto.randomUUID();
50
+ }
51
+ return `prism-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
52
+ }
53
+ function createBrowserStorage() {
54
+ try {
55
+ const probe = "__prism_storage_probe__";
56
+ window.sessionStorage.setItem(probe, "1");
57
+ window.sessionStorage.removeItem(probe);
58
+ window.localStorage.setItem(probe, "1");
59
+ window.localStorage.removeItem(probe);
60
+ } catch (e) {
61
+ return void 0;
62
+ }
63
+ const storeFor = (key) => (
64
+ // R3-F2: queue snapshots, session globals, and SESSION-scoped identity
65
+ // live in sessionStorage (per execution context); persistent-scope
66
+ // identity and globals live in localStorage (cross-launch).
67
+ key.startsWith("prism:queue:") || key.includes(":globals:session:") || key.includes("prism:identity:session:") ? window.sessionStorage : window.localStorage
68
+ );
69
+ return {
70
+ getItem: (key) => __async(null, null, function* () {
71
+ try {
72
+ return storeFor(key).getItem(key);
73
+ } catch (e) {
74
+ return null;
75
+ }
76
+ }),
77
+ setItem: (key, value) => __async(null, null, function* () {
78
+ storeFor(key).setItem(key, value);
79
+ }),
80
+ removeItem: (key) => __async(null, null, function* () {
81
+ storeFor(key).removeItem(key);
82
+ })
83
+ };
84
+ }
85
+ function createBrowserLifecycle() {
86
+ const listeners = {
87
+ foreground: /* @__PURE__ */ new Set(),
88
+ background: /* @__PURE__ */ new Set(),
89
+ "before-unload": /* @__PURE__ */ new Set()
90
+ };
91
+ const fire = (event) => {
92
+ for (const listener of listeners[event]) listener();
93
+ };
94
+ const onVisibility = () => {
95
+ fire(document.visibilityState === "visible" ? "foreground" : "background");
96
+ };
97
+ const onBeforeUnload = () => fire("before-unload");
98
+ document.addEventListener("visibilitychange", onVisibility);
99
+ window.addEventListener("beforeunload", onBeforeUnload);
100
+ return {
101
+ on(event, listener) {
102
+ listeners[event].add(listener);
103
+ let removed = false;
104
+ return () => {
105
+ if (removed) return;
106
+ removed = true;
107
+ listeners[event].delete(listener);
108
+ };
109
+ }
110
+ };
111
+ }
112
+ function captureBrowserContext() {
113
+ var _a, _b;
114
+ const context = {
115
+ platform: "browser",
116
+ kind: "web"
117
+ };
118
+ const width = window.innerWidth || ((_a = window.screen) == null ? void 0 : _a.width) || 0;
119
+ const height = window.innerHeight || ((_b = window.screen) == null ? void 0 : _b.height) || 0;
120
+ if (width > 0 && height > 0) {
121
+ context.screenSize = { width, height };
122
+ }
123
+ const locale = navigator.language;
124
+ if (locale) context.locale = locale;
125
+ try {
126
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
127
+ if (timezone) context.timezone = timezone;
128
+ } catch (e) {
129
+ }
130
+ return context;
131
+ }
132
+ function capturePageContext() {
133
+ let path = "/";
134
+ try {
135
+ path = window.location.pathname || "/";
136
+ } catch (e) {
137
+ }
138
+ let referrer = null;
139
+ try {
140
+ const raw = document.referrer;
141
+ if (raw) referrer = new URL(raw).origin;
142
+ } catch (e) {
143
+ referrer = null;
144
+ }
145
+ return { path, referrer };
146
+ }
147
+ function createBrowserRuntime() {
148
+ const storage = createBrowserStorage();
149
+ return __spreadValues({
150
+ name: "browser",
151
+ now: () => Date.now(),
152
+ createId: browserCreateId,
153
+ transport: {
154
+ post: (url, request) => {
155
+ const controller = new AbortController();
156
+ const onCoreAbort = () => controller.abort();
157
+ request.signal.addEventListener("abort", onCoreAbort);
158
+ const timer = window.setTimeout(
159
+ () => controller.abort(),
160
+ request.timeoutMs
161
+ );
162
+ const bodyBytes = new TextEncoder().encode(request.body).length;
163
+ const keepalive = bodyBytes <= 64 * 1024;
164
+ return fetch(url, {
165
+ method: "POST",
166
+ headers: __spreadValues({}, request.headers),
167
+ body: request.body,
168
+ signal: controller.signal,
169
+ keepalive
170
+ }).then((response) => __async(null, null, function* () {
171
+ return {
172
+ status: response.status,
173
+ headers: Object.fromEntries(response.headers.entries()),
174
+ text: () => response.text()
175
+ };
176
+ })).finally(() => {
177
+ window.clearTimeout(timer);
178
+ request.signal.removeEventListener("abort", onCoreAbort);
179
+ });
180
+ }
181
+ },
182
+ schedule: (delayMs, callback) => {
183
+ const handle = window.setTimeout(callback, delayMs);
184
+ return () => window.clearTimeout(handle);
185
+ },
186
+ context: captureBrowserContext(),
187
+ lifecycle: createBrowserLifecycle()
188
+ }, storage ? { storage } : {});
189
+ }
190
+
191
+ // src/page-tracker.ts
192
+ import {
193
+ INTERNAL_SEAM,
194
+ PAGE_VIEW_EVENT_NAME,
195
+ PAGE_VIEW_LIMITS
196
+ } from "@prism-analytics/core";
197
+ function dropped(reason) {
198
+ return { status: "dropped", reason };
199
+ }
200
+ function hashString(input) {
201
+ let h = 2166136261;
202
+ for (let i = 0; i < input.length; i += 1) {
203
+ h ^= input.charCodeAt(i);
204
+ h = Math.imul(h, 16777619);
205
+ }
206
+ return (h >>> 0).toString(36);
207
+ }
208
+ function safeSessionStorage() {
209
+ try {
210
+ const probe = "__prism_probe__";
211
+ window.sessionStorage.setItem(probe, "1");
212
+ window.sessionStorage.removeItem(probe);
213
+ return window.sessionStorage;
214
+ } catch (e) {
215
+ return null;
216
+ }
217
+ }
218
+ function stableId() {
219
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
220
+ return crypto.randomUUID();
221
+ }
222
+ return `wsess-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
223
+ }
224
+ var historyPatch = null;
225
+ function notify(navigation) {
226
+ if (!historyPatch) return;
227
+ for (const listener of [...historyPatch.listeners]) {
228
+ try {
229
+ listener(navigation);
230
+ } catch (e) {
231
+ }
232
+ }
233
+ }
234
+ function acquireHistoryPatch(listener) {
235
+ if (!historyPatch) {
236
+ const originalPush = window.history.pushState.bind(window.history);
237
+ const originalReplace = window.history.replaceState.bind(window.history);
238
+ const patch = {
239
+ count: 0,
240
+ originalPush,
241
+ originalReplace,
242
+ listeners: /* @__PURE__ */ new Set()
243
+ };
244
+ window.history.pushState = function patchedPush(...args) {
245
+ const result = originalPush.apply(this, args);
246
+ notify("push");
247
+ return result;
248
+ };
249
+ window.history.replaceState = function patchedReplace(...args) {
250
+ const result = originalReplace.apply(this, args);
251
+ notify("replace");
252
+ return result;
253
+ };
254
+ window.addEventListener("popstate", () => notify("pop"));
255
+ historyPatch = patch;
256
+ }
257
+ historyPatch.count += 1;
258
+ historyPatch.listeners.add(listener);
259
+ return () => {
260
+ const current = historyPatch;
261
+ if (!current) return;
262
+ current.listeners.delete(listener);
263
+ current.count -= 1;
264
+ if (current.count <= 0) {
265
+ window.history.pushState = current.originalPush;
266
+ window.history.replaceState = current.originalReplace;
267
+ historyPatch = null;
268
+ }
269
+ };
270
+ }
271
+ function createPageTracker(args) {
272
+ var _a;
273
+ const { client, options } = args;
274
+ const seam = (_a = client[INTERNAL_SEAM]) != null ? _a : null;
275
+ const storage = safeSessionStorage();
276
+ const stateKey = `prism:web-session:${hashString(args.sourceKey)}:${hashString(args.endpoint)}`;
277
+ let disposed = false;
278
+ let removeHistory = null;
279
+ let lastCapturedPath = null;
280
+ let lastManualSignature = null;
281
+ let lastManualAt = 0;
282
+ function loadState() {
283
+ if (!storage) return null;
284
+ try {
285
+ const raw = storage.getItem(stateKey);
286
+ if (!raw) return null;
287
+ const parsed = JSON.parse(raw);
288
+ if ((parsed == null ? void 0 : parsed.v) !== 1 || !parsed.sessionId) return null;
289
+ return parsed;
290
+ } catch (e) {
291
+ return null;
292
+ }
293
+ }
294
+ function saveState(state) {
295
+ if (!storage) return;
296
+ try {
297
+ storage.setItem(stateKey, JSON.stringify(state));
298
+ } catch (e) {
299
+ }
300
+ }
301
+ function clearState() {
302
+ if (!storage) return;
303
+ try {
304
+ storage.removeItem(stateKey);
305
+ } catch (e) {
306
+ }
307
+ }
308
+ function ensureWebSession(now) {
309
+ const stored = loadState();
310
+ if (stored && now - stored.lastActivityAt <= PAGE_VIEW_LIMITS.webSessionInactivityTimeoutMs) {
311
+ seam == null ? void 0 : seam.resumeWebSession({
312
+ sessionId: stored.sessionId,
313
+ startedAt: stored.startedAt
314
+ });
315
+ const sequence = stored.sequence + 1;
316
+ saveState(__spreadProps(__spreadValues({}, stored), { lastActivityAt: now, sequence }));
317
+ return sequence;
318
+ }
319
+ const handle = client.startSession();
320
+ const sessionId = handle.status === "started" ? handle.session.sessionId : stableId();
321
+ const startedAt = now;
322
+ saveState({
323
+ v: 1,
324
+ sessionId,
325
+ startedAt,
326
+ lastActivityAt: now,
327
+ sequence: 1
328
+ });
329
+ return 1;
330
+ }
331
+ function buildCandidate(navigation, override) {
332
+ var _a2;
333
+ let host = "";
334
+ let path = "/";
335
+ try {
336
+ host = window.location.hostname.toLowerCase();
337
+ path = window.location.pathname || "/";
338
+ } catch (e) {
339
+ return null;
340
+ }
341
+ if ((override == null ? void 0 : override.path) && override.path.startsWith("/")) {
342
+ path = override.path;
343
+ }
344
+ if (!host || !isValidPath(path)) return null;
345
+ const stored = loadState();
346
+ const candidate = __spreadValues(__spreadValues({
347
+ host,
348
+ path,
349
+ navigation,
350
+ sequence: stored ? stored.sequence + 1 : 1
351
+ }, stored && lastCapturedPath ? { previousPath: lastCapturedPath } : {}), options.captureTitle ? {
352
+ title: ((_a2 = override == null ? void 0 : override.title) != null ? _a2 : documentTitle()).slice(
353
+ 0,
354
+ PAGE_VIEW_LIMITS.maxTitleLength
355
+ )
356
+ } : {});
357
+ if (typeof options.beforeCapture === "function") {
358
+ try {
359
+ const next = options.beforeCapture(candidate);
360
+ if (!next) return null;
361
+ return next;
362
+ } catch (e) {
363
+ return null;
364
+ }
365
+ }
366
+ return candidate;
367
+ }
368
+ function isValidPath(path) {
369
+ return path.startsWith("/") && path.length <= PAGE_VIEW_LIMITS.maxPathLength && !path.includes("?") && !path.includes("#");
370
+ }
371
+ function documentTitle() {
372
+ var _a2;
373
+ try {
374
+ return typeof document !== "undefined" ? (_a2 = document.title) != null ? _a2 : "" : "";
375
+ } catch (e) {
376
+ return "";
377
+ }
378
+ }
379
+ function externalReferrerHost(host) {
380
+ try {
381
+ const raw = document.referrer;
382
+ if (!raw) return void 0;
383
+ const parsed = new URL(raw);
384
+ const referrerHost = parsed.hostname.toLowerCase();
385
+ if (!referrerHost || referrerHost === host) return void 0;
386
+ return referrerHost.slice(0, PAGE_VIEW_LIMITS.maxAttributionValueLength);
387
+ } catch (e) {
388
+ return void 0;
389
+ }
390
+ }
391
+ function campaignFromQuery() {
392
+ var _a2;
393
+ try {
394
+ const search = window.location.search;
395
+ if (!search) return void 0;
396
+ const params = new URLSearchParams(search);
397
+ const allow = new Set(
398
+ (_a2 = options.campaignParameters) != null ? _a2 : [
399
+ "utm_source",
400
+ "utm_medium",
401
+ "utm_campaign"
402
+ ]
403
+ );
404
+ const bound = (key) => {
405
+ const value = params.get(key);
406
+ if (!value) return void 0;
407
+ return value.slice(0, PAGE_VIEW_LIMITS.maxAttributionValueLength);
408
+ };
409
+ const out = {};
410
+ if (allow.has("utm_source")) out.source = bound("utm_source");
411
+ if (allow.has("utm_medium")) out.medium = bound("utm_medium");
412
+ if (allow.has("utm_campaign")) out.name = bound("utm_campaign");
413
+ if (!out.source && !out.medium && !out.name) return void 0;
414
+ return out;
415
+ } catch (e) {
416
+ return void 0;
417
+ }
418
+ }
419
+ function deliver(candidate) {
420
+ const now = Date.now();
421
+ if (client.collectionState !== "granted") {
422
+ return {
423
+ status: "dropped",
424
+ reason: client.collectionState === "pending" ? "consent-pending" : "consent-denied"
425
+ };
426
+ }
427
+ const sequence = ensureWebSession(now);
428
+ const withSequence = __spreadProps(__spreadValues({}, candidate), { sequence });
429
+ lastCapturedPath = withSequence.path;
430
+ const properties = {
431
+ $page: __spreadValues(__spreadValues({
432
+ host: withSequence.host,
433
+ path: withSequence.path,
434
+ navigation: withSequence.navigation,
435
+ sequence: withSequence.sequence
436
+ }, withSequence.previousPath ? { previousPath: withSequence.previousPath } : {}), withSequence.title ? { title: withSequence.title } : {})
437
+ };
438
+ const referrer = externalReferrerHost(withSequence.host);
439
+ if (referrer) properties.$referrer = { host: referrer };
440
+ const campaign = campaignFromQuery();
441
+ if (campaign) properties.$campaign = campaign;
442
+ const result = seam == null ? void 0 : seam.createReservedEvent(PAGE_VIEW_EVENT_NAME, properties);
443
+ if (!result) {
444
+ return dropped("shutdown");
445
+ }
446
+ if (result.status === "queued") {
447
+ return { status: "queued", eventId: result.eventId };
448
+ }
449
+ if (result.status === "rejected") {
450
+ return { status: "dropped", reason: result.reason };
451
+ }
452
+ return { status: "dropped", reason: result.reason };
453
+ }
454
+ function captureNavigation(navigation, override) {
455
+ if (disposed) {
456
+ return dropped("shutdown");
457
+ }
458
+ const candidate = buildCandidate(navigation, override);
459
+ if (!candidate) {
460
+ return dropped("invalid-page-context");
461
+ }
462
+ if (candidate.path === lastCapturedPath && (navigation === "replace" || navigation === "push" || navigation === "manual")) {
463
+ return dropped("same-path-suppressed");
464
+ }
465
+ return deliver(candidate);
466
+ }
467
+ if (options.mode === "history") {
468
+ removeHistory = acquireHistoryPatch((navigation) => {
469
+ captureNavigation(navigation);
470
+ });
471
+ captureNavigation("initial");
472
+ }
473
+ const controller = {
474
+ mode: options.mode,
475
+ capture(input) {
476
+ var _a2;
477
+ if (disposed) {
478
+ return dropped("shutdown");
479
+ }
480
+ if (options.mode === "history") {
481
+ throw new Error(
482
+ "pageViews.capture() is unavailable in history mode \u2014 navigation is captured automatically"
483
+ );
484
+ }
485
+ const signature = `${window.location.hostname}|${(_a2 = input == null ? void 0 : input.path) != null ? _a2 : window.location.pathname}`;
486
+ const now = Date.now();
487
+ if (signature === lastManualSignature && now - lastManualAt < 500) {
488
+ lastManualAt = now;
489
+ return dropped("duplicate-manual-capture");
490
+ }
491
+ lastManualSignature = signature;
492
+ lastManualAt = now;
493
+ return captureNavigation("manual", input);
494
+ },
495
+ resetForTests() {
496
+ clearState();
497
+ lastCapturedPath = null;
498
+ lastManualSignature = null;
499
+ },
500
+ dispose() {
501
+ if (disposed) return;
502
+ disposed = true;
503
+ if (removeHistory) {
504
+ removeHistory();
505
+ removeHistory = null;
506
+ }
507
+ }
508
+ };
509
+ return controller;
510
+ }
511
+
512
+ // src/error-reporter.ts
513
+ import {
514
+ createPrismErrorReporter,
515
+ framesFromStack
516
+ } from "@prism-analytics/core";
517
+ function describeUnknown(value, depth = 0) {
518
+ if (depth > 2) return "[object]";
519
+ if (typeof value === "string") return value;
520
+ if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0)
521
+ return String(value);
522
+ if (depth === 0 && Array.isArray(value)) return "UnhandledRejection";
523
+ try {
524
+ const seen = /* @__PURE__ */ new Set();
525
+ const text = JSON.stringify(value, (_k, v) => {
526
+ if (typeof v === "object" && v !== null) {
527
+ if (seen.has(v)) return "[Circular]";
528
+ seen.add(v);
529
+ }
530
+ if (typeof v === "function") return "[Function]";
531
+ return v;
532
+ });
533
+ if (text && text.length <= 256) return text;
534
+ return "[object]";
535
+ } catch (e) {
536
+ return "[object]";
537
+ }
538
+ }
539
+ function isPlainRecord(value) {
540
+ return typeof value === "object" && value !== null && !Array.isArray(value);
541
+ }
542
+ function looksLikeErrorReportInput(value) {
543
+ return isPlainRecord(value) && isPlainRecord(value.exception) && typeof value.exception.type === "string";
544
+ }
545
+ function safeMessage(message, fallback = "Error") {
546
+ const text = typeof message === "string" && message.length > 0 ? message : fallback;
547
+ return text.length > 2048 ? `${text.slice(0, 2048)}\u2026` : text;
548
+ }
549
+ function normalizeErrorValue(value) {
550
+ var _a;
551
+ if (typeof value === "string") {
552
+ return { exception: { type: "Error", message: value } };
553
+ }
554
+ if (value instanceof Error) {
555
+ return {
556
+ exception: {
557
+ type: value.name || "Error",
558
+ message: safeMessage(value.message),
559
+ frames: framesFromStack(value.stack)
560
+ }
561
+ };
562
+ }
563
+ if (isPlainRecord(value)) {
564
+ const record = value;
565
+ if (record.type === "unhandledrejection") {
566
+ return normalizeErrorValue((_a = record.reason) != null ? _a : "Unhandled rejection");
567
+ }
568
+ if (looksLikeErrorReportInput(record)) {
569
+ return record;
570
+ }
571
+ const name = record.name;
572
+ const message = record.message;
573
+ if (typeof name === "string" && name.length > 0 && typeof message === "string") {
574
+ return {
575
+ exception: {
576
+ type: name,
577
+ message: safeMessage(message),
578
+ frames: framesFromStack(
579
+ typeof record.stack === "string" ? record.stack : void 0
580
+ )
581
+ }
582
+ };
583
+ }
584
+ if (typeof message === "string" && message.length > 0) {
585
+ return __spreadValues({
586
+ exception: {
587
+ type: message === "Script error." ? "ScriptError" : "Error",
588
+ message: safeMessage(message)
589
+ }
590
+ }, message === "Script error." ? { context: { extras: { scriptError: true } } } : {});
591
+ }
592
+ return {
593
+ exception: {
594
+ type: "UnhandledRejection",
595
+ message: describeUnknown(record)
596
+ }
597
+ };
598
+ }
599
+ if (typeof value === "object" && value !== null) {
600
+ return {
601
+ exception: { type: "Error", message: describeUnknown(value) }
602
+ };
603
+ }
604
+ return {
605
+ exception: { type: "Error", message: describeUnknown(value) }
606
+ };
607
+ }
608
+ var DEDUPE_DEFAULT_MS = 1e3;
609
+ var DEDUPE_MAX_ENTRIES = 100;
610
+ function createBrowserErrorReporter(options) {
611
+ return __async(this, null, function* () {
612
+ var _a;
613
+ if (typeof window === "undefined") {
614
+ throw new Error(
615
+ "@prism-analytics/browser error reporter requires a browser (window is undefined)"
616
+ );
617
+ }
618
+ if (!options.endpoint || options.endpoint.trim().length === 0) {
619
+ throw new Error(
620
+ "endpoint is required \u2014 choose the ingestion origin at runtime"
621
+ );
622
+ }
623
+ if (!options.share || typeof options.share.consent !== "function") {
624
+ throw new Error("share is required \u2014 wire the analytics client's consent");
625
+ }
626
+ const runtime = createBrowserRuntime();
627
+ const reporter = yield createPrismErrorReporter(__spreadValues({
628
+ sourceKey: options.sourceKey,
629
+ endpoint: options.endpoint,
630
+ runtime,
631
+ share: options.share,
632
+ beforeSend: options.beforeSend,
633
+ queue: options.queue
634
+ }, "onDiagnostic" in options && options.onDiagnostic ? { onDiagnostic: options.onDiagnostic } : {}));
635
+ const dedupeMs = (_a = options.dedupeMs) != null ? _a : DEDUPE_DEFAULT_MS;
636
+ const seen = /* @__PURE__ */ new Map();
637
+ let installed = false;
638
+ let handleError = null;
639
+ let handleRejection = null;
640
+ const capture = (value, captureOptions) => {
641
+ var _a2, _b, _c, _d, _e;
642
+ let input;
643
+ try {
644
+ input = normalizeErrorValue(value);
645
+ } catch (error) {
646
+ throw error instanceof Error ? error : new Error("invalid error report");
647
+ }
648
+ const release = (_b = (_a2 = captureOptions == null ? void 0 : captureOptions.release) != null ? _a2 : options.release) != null ? _b : input.release;
649
+ const environment = (_d = (_c = captureOptions == null ? void 0 : captureOptions.environment) != null ? _c : options.environment) != null ? _d : input.environment;
650
+ input = __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, input), release ? { release } : {}), environment ? { environment } : {}), (captureOptions == null ? void 0 : captureOptions.level) ? { level: captureOptions.level } : {}), (captureOptions == null ? void 0 : captureOptions.context) ? { context: captureOptions.context } : {}), (captureOptions == null ? void 0 : captureOptions.breadcrumbs) ? { breadcrumbs: captureOptions.breadcrumbs } : {}), "handled" in (captureOptions != null ? captureOptions : {}) ? { handled: captureOptions == null ? void 0 : captureOptions.handled } : { handled: true });
651
+ const type = input.exception.type;
652
+ const key = `${type}\0${(_e = input.exception.message) != null ? _e : ""}`;
653
+ const now = runtime.now();
654
+ const prior = seen.get(key);
655
+ if (prior && now - prior.ts < dedupeMs) {
656
+ return { status: "deduped", id: prior.id };
657
+ }
658
+ if (seen.size >= DEDUPE_MAX_ENTRIES) {
659
+ for (const k of seen.keys()) {
660
+ seen.delete(k);
661
+ if (seen.size < DEDUPE_MAX_ENTRIES) break;
662
+ }
663
+ }
664
+ const result = reporter.captureException(input);
665
+ if (result.status === "queued") {
666
+ seen.set(key, { ts: now, id: result.id });
667
+ }
668
+ return result;
669
+ };
670
+ const install = () => {
671
+ if (installed) return;
672
+ installed = true;
673
+ handleError = (event) => {
674
+ var _a2, _b;
675
+ const errorEvent = event;
676
+ const message = errorEvent.message;
677
+ const error = errorEvent.error;
678
+ try {
679
+ if (error && error instanceof Error) {
680
+ capture(error, { handled: false });
681
+ } else if (typeof message === "string" && message.length > 0) {
682
+ capture(
683
+ {
684
+ exception: { type: "ScriptError", message },
685
+ context: { extras: { scriptError: message === "Script error." } }
686
+ },
687
+ { handled: false }
688
+ );
689
+ } else {
690
+ const target = errorEvent.target;
691
+ capture(
692
+ {
693
+ exception: {
694
+ type: "ResourceError",
695
+ message: (_b = (_a2 = target == null ? void 0 : target.src) != null ? _a2 : target == null ? void 0 : target.tagName) != null ? _b : "Resource load failed"
696
+ }
697
+ },
698
+ { handled: false }
699
+ );
700
+ }
701
+ } catch (e) {
702
+ }
703
+ };
704
+ handleRejection = (event) => {
705
+ const reason = event.reason;
706
+ try {
707
+ capture(reason != null ? reason : "Unhandled rejection", { handled: false });
708
+ } catch (e) {
709
+ }
710
+ };
711
+ window.addEventListener("error", handleError, true);
712
+ window.addEventListener("unhandledrejection", handleRejection);
713
+ };
714
+ const uninstall = () => {
715
+ if (!installed) return;
716
+ installed = false;
717
+ if (handleError) window.removeEventListener("error", handleError, true);
718
+ if (handleRejection)
719
+ window.removeEventListener("unhandledrejection", handleRejection);
720
+ handleError = null;
721
+ handleRejection = null;
722
+ };
723
+ const bound = {
724
+ reporter,
725
+ get pendingCount() {
726
+ return reporter.pendingCount;
727
+ },
728
+ get installed() {
729
+ return installed;
730
+ },
731
+ captureException: capture,
732
+ install,
733
+ uninstall,
734
+ flush: () => reporter.flush(),
735
+ shutdown: (opts) => __async(null, null, function* () {
736
+ uninstall();
737
+ yield reporter.shutdown(opts);
738
+ }),
739
+ onDiagnostic: (listener) => reporter.onDiagnostic(listener)
740
+ };
741
+ if (options.captureGlobalErrors) install();
742
+ return bound;
743
+ });
744
+ }
745
+
746
+ // src/index.ts
747
+ import { framesFromStack as framesFromStack2 } from "@prism-analytics/core";
748
+ function createBrowserClient(options) {
749
+ return __async(this, null, function* () {
750
+ if (typeof window === "undefined") {
751
+ throw new Error(
752
+ "@prism-analytics/browser requires a browser environment (window is undefined)"
753
+ );
754
+ }
755
+ if (!options.endpoint || options.endpoint.trim().length === 0) {
756
+ throw new Error(
757
+ "endpoint is required \u2014 choose the ingestion origin at runtime"
758
+ );
759
+ }
760
+ const runtime = createBrowserRuntime();
761
+ const client = yield createPrismClient({
762
+ sourceKey: options.sourceKey,
763
+ endpoint: options.endpoint.replace(/\/$/, ""),
764
+ runtime,
765
+ collection: __spreadValues({
766
+ // Session-scoped identity by default (§4): the browser default
767
+ // keeps the anonymous ID for the client lifetime unless the caller
768
+ // explicitly asks for persistent identity.
769
+ anonymousPersistence: "session"
770
+ }, options.collection),
771
+ queue: options.queue,
772
+ sanitize: options.sanitize
773
+ });
774
+ const browserClient = client;
775
+ if (!options.pageViews) {
776
+ browserClient.pageViews = null;
777
+ return browserClient;
778
+ }
779
+ const coreClient = client;
780
+ const tracker = createPageTracker({
781
+ client: coreClient,
782
+ options: options.pageViews,
783
+ sourceKey: options.sourceKey,
784
+ endpoint: options.endpoint.replace(/\/$/, "")
785
+ });
786
+ browserClient.pageViews = {
787
+ mode: tracker.mode,
788
+ capture: (input) => tracker.capture(input)
789
+ };
790
+ const originalShutdown = client.shutdown.bind(client);
791
+ browserClient.shutdown = (shutdownOptions) => __async(null, null, function* () {
792
+ tracker.dispose();
793
+ return originalShutdown(shutdownOptions);
794
+ });
795
+ return browserClient;
796
+ });
797
+ }
798
+ export {
799
+ capturePageContext,
800
+ createBrowserClient,
801
+ createBrowserErrorReporter,
802
+ framesFromStack2 as framesFromStack,
803
+ normalizeErrorValue
804
+ };