@reopt-ai/data-sdk-client 0.1.6 → 0.3.0

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.
Files changed (57) hide show
  1. package/README.md +141 -7
  2. package/dist/client-B1GlWwVq.d.ts +744 -0
  3. package/dist/client-B1GlWwVq.d.ts.map +1 -0
  4. package/dist/client-BCrui21p.d.cts +744 -0
  5. package/dist/client-BCrui21p.d.cts.map +1 -0
  6. package/dist/exceptions-C9Fh0BIR.js +285 -0
  7. package/dist/exceptions-C9Fh0BIR.js.map +1 -0
  8. package/dist/exceptions-CuhGo9A5.cjs +289 -0
  9. package/dist/exceptions-CuhGo9A5.cjs.map +1 -0
  10. package/dist/index.cjs +75 -1874
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +83 -0
  13. package/dist/index.d.cts.map +1 -0
  14. package/dist/index.d.ts +35 -33
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +49 -70
  17. package/dist/index.js.map +1 -1
  18. package/dist/next.cjs +51 -1919
  19. package/dist/next.cjs.map +1 -1
  20. package/dist/next.d.cts +31 -0
  21. package/dist/next.d.cts.map +1 -0
  22. package/dist/next.d.ts +8 -10
  23. package/dist/next.d.ts.map +1 -0
  24. package/dist/next.js +36 -49
  25. package/dist/next.js.map +1 -1
  26. package/dist/observe-D-BHMwrO.cjs +54 -0
  27. package/dist/observe-D-BHMwrO.cjs.map +1 -0
  28. package/dist/observe-D1pNg1qL.js +54 -0
  29. package/dist/observe-D1pNg1qL.js.map +1 -0
  30. package/dist/react.cjs +105 -1863
  31. package/dist/react.cjs.map +1 -1
  32. package/dist/react.d.cts +80 -0
  33. package/dist/react.d.cts.map +1 -0
  34. package/dist/react.d.ts +52 -46
  35. package/dist/react.d.ts.map +1 -0
  36. package/dist/react.js +118 -23
  37. package/dist/react.js.map +1 -1
  38. package/dist/registry-BPhiH_l9.js +1775 -0
  39. package/dist/registry-BPhiH_l9.js.map +1 -0
  40. package/dist/registry-CGqrOtf4.cjs +1810 -0
  41. package/dist/registry-CGqrOtf4.cjs.map +1 -0
  42. package/dist/tracing-DAhaKkl0.cjs +93 -0
  43. package/dist/tracing-DAhaKkl0.cjs.map +1 -0
  44. package/dist/tracing-Dt1RVuQ9.js +93 -0
  45. package/dist/tracing-Dt1RVuQ9.js.map +1 -0
  46. package/package.json +33 -18
  47. package/dist/chunk-4MTDZBRS.js +0 -57
  48. package/dist/chunk-4MTDZBRS.js.map +0 -1
  49. package/dist/chunk-SYCBGBTH.js +0 -1627
  50. package/dist/chunk-SYCBGBTH.js.map +0 -1
  51. package/dist/chunk-YRP3I3OD.js +0 -106
  52. package/dist/chunk-YRP3I3OD.js.map +0 -1
  53. package/dist/client-BCd2fgmx.d.ts +0 -581
  54. package/dist/exceptions-NQHZUDYO.js +0 -9
  55. package/dist/exceptions-NQHZUDYO.js.map +0 -1
  56. package/dist/tracing-LC3NZND7.js +0 -91
  57. package/dist/tracing-LC3NZND7.js.map +0 -1
@@ -0,0 +1,1810 @@
1
+ let _reopt_ai_data_contract_identity = require("@reopt-ai/data-contract/identity");
2
+ let _reopt_ai_data_contract_ingest = require("@reopt-ai/data-contract/ingest");
3
+ let _reopt_ai_data_contract_events = require("@reopt-ai/data-contract/events");
4
+ //#region ../data-sdk-core/src/circuit-breaker.ts
5
+ const DEFAULT_CIRCUIT_BREAKER = {
6
+ failureThreshold: 5,
7
+ recoveryTimeout: 6e4
8
+ };
9
+ var CircuitBreaker = class {
10
+ state = "closed";
11
+ consecutiveFailures = 0;
12
+ openedAt = 0;
13
+ failureThreshold;
14
+ recoveryTimeout;
15
+ now;
16
+ constructor(config, now) {
17
+ this.failureThreshold = config?.failureThreshold ?? DEFAULT_CIRCUIT_BREAKER.failureThreshold;
18
+ this.recoveryTimeout = config?.recoveryTimeout ?? DEFAULT_CIRCUIT_BREAKER.recoveryTimeout;
19
+ this.now = now;
20
+ }
21
+ getState() {
22
+ return this.state;
23
+ }
24
+ allowRequest() {
25
+ if (this.state !== "open") return true;
26
+ if (this.now() - this.openedAt < this.recoveryTimeout) return false;
27
+ this.state = "half-open";
28
+ return true;
29
+ }
30
+ recordSuccess() {
31
+ this.state = "closed";
32
+ this.consecutiveFailures = 0;
33
+ }
34
+ recordFailure() {
35
+ this.consecutiveFailures++;
36
+ if (this.state === "half-open" || this.consecutiveFailures >= this.failureThreshold) {
37
+ const wasOpen = this.state === "open";
38
+ this.state = "open";
39
+ this.openedAt = this.now();
40
+ return !wasOpen;
41
+ }
42
+ return false;
43
+ }
44
+ recordRejection() {
45
+ if (this.state === "half-open") this.state = "closed";
46
+ this.consecutiveFailures = 0;
47
+ }
48
+ };
49
+ const CONSENT_STORAGE_KEY = "consent";
50
+ var ConsentManager = class {
51
+ state = {};
52
+ paused = false;
53
+ storage;
54
+ onChange;
55
+ constructor(config, storage, onChange) {
56
+ const persist = config?.persist ?? true;
57
+ this.storage = persist ? storage : void 0;
58
+ this.onChange = onChange;
59
+ const defaultConsent = config?.defaultConsent ?? true;
60
+ for (const category of config?.categories ?? ["analytics"]) this.state[category] = defaultConsent;
61
+ const stored = this.storage ? readStoredConsent(this.storage) : null;
62
+ if (stored) Object.assign(this.state, stored);
63
+ }
64
+ get(category) {
65
+ return this.state[category] ?? false;
66
+ }
67
+ set(category, allowed) {
68
+ this.state[category] = allowed;
69
+ this.persist();
70
+ }
71
+ setAll(allowed) {
72
+ for (const key of Object.keys(this.state)) this.state[key] = allowed;
73
+ this.persist();
74
+ }
75
+ snapshot() {
76
+ return { ...this.state };
77
+ }
78
+ replace(state) {
79
+ this.state = {
80
+ ...this.state,
81
+ ...state
82
+ };
83
+ this.persist();
84
+ }
85
+ pause() {
86
+ this.paused = true;
87
+ }
88
+ resume() {
89
+ this.paused = false;
90
+ }
91
+ isPaused() {
92
+ return this.paused;
93
+ }
94
+ enqueueBlockReason(category) {
95
+ if (this.paused) return "tracking_paused";
96
+ if (!this.get(category)) return "consent_denied";
97
+ return null;
98
+ }
99
+ flushBlockReason() {
100
+ if (this.paused) return "tracking_paused";
101
+ if (!Object.values(this.state).some((allowed) => allowed === true)) return "consent_denied";
102
+ return null;
103
+ }
104
+ persist() {
105
+ this.onChange?.(this.snapshot());
106
+ if (!this.storage) return;
107
+ try {
108
+ this.storage.setItem(CONSENT_STORAGE_KEY, JSON.stringify(this.state));
109
+ } catch {}
110
+ }
111
+ };
112
+ function readStoredConsent(storage) {
113
+ try {
114
+ const raw = storage.getItem(CONSENT_STORAGE_KEY);
115
+ if (!raw) return null;
116
+ const parsed = JSON.parse(raw);
117
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
118
+ const state = {};
119
+ for (const [category, allowed] of Object.entries(parsed)) if (typeof allowed === "boolean") state[category] = allowed;
120
+ return state;
121
+ } catch {
122
+ return null;
123
+ }
124
+ }
125
+ //#endregion
126
+ //#region ../data-sdk-core/src/validate.ts
127
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
128
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
129
+ const isId = (value, required) => value === void 0 ? !required : typeof value === "number" || typeof value === "string" && value.length <= 500 && (!required || value.length > 0);
130
+ const isName = (value, max = 200) => typeof value === "string" && value.length > 0 && value.length <= max;
131
+ function validateStructurally(event) {
132
+ const bad = [];
133
+ if (!isRecord(event)) return [{
134
+ field: "",
135
+ message: "not an object"
136
+ }];
137
+ if (!UUID.test(String(event.eventId))) bad.push("eventId");
138
+ if (!Number.isInteger(event.timestamp) || event.timestamp < 0) bad.push("timestamp");
139
+ const p = event.payload;
140
+ if (!isRecord(p)) return [...bad, "payload"].map(field);
141
+ switch (event.type) {
142
+ case "track":
143
+ if (!isName(p.name)) bad.push("payload.name");
144
+ else if (_reopt_ai_data_contract_events.RESERVED_EVENT_NAMES.includes(p.name)) bad.push("payload.name:reserved");
145
+ if (!isId(p.profileId, false)) bad.push("payload.profileId");
146
+ break;
147
+ case "identify":
148
+ if (!isId(p.profileId, true)) bad.push("payload.profileId");
149
+ break;
150
+ case "increment":
151
+ case "decrement":
152
+ if (!isId(p.profileId, true)) bad.push("payload.profileId");
153
+ if (!isName(p.property)) bad.push("payload.property");
154
+ if (p.value !== void 0 && !(typeof p.value === "number" && p.value > 0)) bad.push("payload.value");
155
+ break;
156
+ default: bad.push("type");
157
+ }
158
+ if (p.properties !== void 0 && !isRecord(p.properties)) bad.push("payload.properties");
159
+ return bad.map(field);
160
+ }
161
+ function field(name) {
162
+ const [path, reason] = name.split(":");
163
+ return {
164
+ field: path,
165
+ message: reason ?? "invalid"
166
+ };
167
+ }
168
+ //#endregion
169
+ //#region ../data-sdk-core/src/events.ts
170
+ function validateEvent(event) {
171
+ const errors = validateStructurally(event);
172
+ if (typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
173
+ const result = _reopt_ai_data_contract_ingest.zTrackHandlerPayload.safeParse(event);
174
+ if (!result.success) return {
175
+ valid: false,
176
+ errors: result.error.issues.map((issue) => ({
177
+ field: issue.path.join("."),
178
+ message: issue.message
179
+ }))
180
+ };
181
+ if (errors.length > 0) {
182
+ console.warn("[reopt] structural validator disagrees with the contract schema — report this", errors);
183
+ return { valid: true };
184
+ }
185
+ }
186
+ return errors.length === 0 ? { valid: true } : {
187
+ valid: false,
188
+ errors
189
+ };
190
+ }
191
+ function stamp(deps, draft) {
192
+ return {
193
+ ...draft,
194
+ eventId: deps.createId(),
195
+ timestamp: deps.now()
196
+ };
197
+ }
198
+ function buildTrackEvent(deps, options, fallbackProfileId) {
199
+ return stamp(deps, {
200
+ type: "track",
201
+ payload: {
202
+ name: options.name,
203
+ properties: options.properties,
204
+ profileId: options.profileId ?? fallbackProfileId ?? void 0
205
+ }
206
+ });
207
+ }
208
+ function buildIdentifyEvent(deps, options) {
209
+ return stamp(deps, {
210
+ type: "identify",
211
+ payload: {
212
+ profileId: options.profileId,
213
+ ...options.firstName && { firstName: options.firstName },
214
+ ...options.lastName && { lastName: options.lastName },
215
+ ...options.email && { email: options.email },
216
+ ...options.avatar && { avatar: options.avatar },
217
+ properties: options.properties
218
+ }
219
+ });
220
+ }
221
+ function buildCounterEvent(deps, type, options, fallbackProfileId) {
222
+ return stamp(deps, {
223
+ type,
224
+ payload: {
225
+ profileId: options.profileId ?? fallbackProfileId ?? "",
226
+ property: options.property,
227
+ value: options.value ?? 1
228
+ }
229
+ });
230
+ }
231
+ function buildPageViewOptions(options, context) {
232
+ const utm = options.utm ?? context.utm ?? {};
233
+ return {
234
+ name: "$pageview",
235
+ consentCategory: options.consentCategory,
236
+ identity: options.identity,
237
+ properties: {
238
+ path: options.path ?? context.path ?? "/",
239
+ origin: options.origin ?? context.origin ?? "",
240
+ title: options.title ?? context.title ?? "",
241
+ referrer: options.referrer ?? context.referrer ?? "",
242
+ ...Object.fromEntries(Object.entries(utm).filter(([, value]) => value)),
243
+ ...context.properties,
244
+ ...options.properties
245
+ }
246
+ };
247
+ }
248
+ function normalizePersistedEvent(deps, event) {
249
+ if (!event || typeof event !== "object") return null;
250
+ const candidate = event;
251
+ if (candidate.type !== "track" && candidate.type !== "identify" && candidate.type !== "increment" && candidate.type !== "decrement") return null;
252
+ if (!candidate.payload || typeof candidate.payload !== "object") return null;
253
+ return {
254
+ ...candidate,
255
+ eventId: candidate.eventId ?? deps.createId(),
256
+ timestamp: candidate.timestamp ?? deps.now()
257
+ };
258
+ }
259
+ //#endregion
260
+ //#region ../data-sdk-core/src/ids.ts
261
+ let lastTimestamp = -1;
262
+ let sequence = 0;
263
+ const HEX = "0123456789abcdef";
264
+ function randomBytes(length) {
265
+ const bytes = new Uint8Array(length);
266
+ const cryptoApi = globalThis.crypto;
267
+ if (cryptoApi && typeof cryptoApi.getRandomValues === "function") {
268
+ cryptoApi.getRandomValues(bytes);
269
+ return bytes;
270
+ }
271
+ for (let index = 0; index < length; index++) bytes[index] = Math.floor(Math.random() * 256);
272
+ return bytes;
273
+ }
274
+ function hex(bytes) {
275
+ let out = "";
276
+ for (const byte of bytes) out += HEX[byte >> 4] + HEX[byte & 15];
277
+ return out;
278
+ }
279
+ function uuidv7(now = Date.now()) {
280
+ let timestamp = Math.max(0, Math.floor(now));
281
+ if (timestamp === lastTimestamp) {
282
+ sequence = sequence + 1 & 4095;
283
+ if (sequence === 0) {
284
+ timestamp = lastTimestamp + 1;
285
+ lastTimestamp = timestamp;
286
+ }
287
+ } else if (timestamp < lastTimestamp) {
288
+ timestamp = lastTimestamp;
289
+ sequence = sequence + 1 & 4095;
290
+ if (sequence === 0) {
291
+ timestamp = lastTimestamp + 1;
292
+ lastTimestamp = timestamp;
293
+ }
294
+ } else {
295
+ lastTimestamp = timestamp;
296
+ sequence = randomBytes(2)[0] & 15;
297
+ }
298
+ const bytes = new Uint8Array(16);
299
+ bytes[0] = timestamp / 2 ** 40 & 255;
300
+ bytes[1] = timestamp / 2 ** 32 & 255;
301
+ bytes[2] = timestamp / 2 ** 24 & 255;
302
+ bytes[3] = timestamp / 2 ** 16 & 255;
303
+ bytes[4] = timestamp / 256 & 255;
304
+ bytes[5] = timestamp & 255;
305
+ bytes[6] = 112 | sequence >> 8;
306
+ bytes[7] = sequence & 255;
307
+ const random = randomBytes(8);
308
+ bytes[8] = 128 | random[0] & 63;
309
+ for (let index = 1; index < 8; index++) bytes[8 + index] = random[index];
310
+ const text = hex(bytes);
311
+ return `${text.slice(0, 8)}-${text.slice(8, 12)}-${text.slice(12, 16)}-${text.slice(16, 20)}-${text.slice(20)}`;
312
+ }
313
+ function uuidv4() {
314
+ const cryptoApi = globalThis.crypto;
315
+ if (cryptoApi && typeof cryptoApi.randomUUID === "function") return cryptoApi.randomUUID();
316
+ const bytes = randomBytes(16);
317
+ bytes[6] = 64 | bytes[6] & 15;
318
+ bytes[8] = 128 | bytes[8] & 63;
319
+ const text = hex(bytes);
320
+ return `${text.slice(0, 8)}-${text.slice(8, 12)}-${text.slice(12, 16)}-${text.slice(16, 20)}-${text.slice(20)}`;
321
+ }
322
+ //#endregion
323
+ //#region ../data-sdk-core/src/queue.ts
324
+ const QUEUE_STORAGE_KEY = "queue";
325
+ const PERSIST_DEBOUNCE_MS = 250;
326
+ function serializedSize(value) {
327
+ const text = JSON.stringify(value);
328
+ if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(text).length;
329
+ let bytes = 0;
330
+ for (let index = 0; index < text.length; index++) {
331
+ const code = text.charCodeAt(index);
332
+ bytes += code < 128 ? 1 : code < 2048 ? 2 : 3;
333
+ }
334
+ return bytes;
335
+ }
336
+ function identityKey(identity) {
337
+ if (!identity) return "";
338
+ return `${identity.deviceId ?? ""}${identity.sessionId ?? ""}`;
339
+ }
340
+ var EventQueue = class {
341
+ entries = [];
342
+ inFlight = [];
343
+ persistTimeout = null;
344
+ options;
345
+ constructor(options) {
346
+ this.options = options;
347
+ }
348
+ get length() {
349
+ return this.entries.length;
350
+ }
351
+ push(entry) {
352
+ let dropped = 0;
353
+ if (this.entries.length >= this.options.maxSize) {
354
+ dropped = this.entries.length - this.options.maxSize + 1;
355
+ this.entries.splice(0, dropped);
356
+ }
357
+ this.entries.push(entry);
358
+ this.schedulePersist();
359
+ return dropped;
360
+ }
361
+ take(maxCount, maxBytes) {
362
+ if (this.entries.length === 0) return [];
363
+ const key = identityKey(this.entries[0].identity);
364
+ let count = 0;
365
+ let bytes = 2;
366
+ while (count < this.entries.length && count < maxCount) {
367
+ const entry = this.entries[count];
368
+ if (count > 0 && identityKey(entry.identity) !== key) break;
369
+ const size = serializedSize(entry.event) + (count > 0 ? 1 : 0);
370
+ if (count > 0 && bytes + size > maxBytes) break;
371
+ bytes += size;
372
+ count++;
373
+ }
374
+ this.inFlight = this.entries.splice(0, count);
375
+ return this.inFlight;
376
+ }
377
+ settle() {
378
+ this.inFlight = [];
379
+ }
380
+ requeue(entries) {
381
+ this.entries.unshift(...entries);
382
+ this.inFlight = [];
383
+ }
384
+ clear() {
385
+ this.entries = [];
386
+ }
387
+ peekAll() {
388
+ return this.entries;
389
+ }
390
+ schedulePersist() {
391
+ if (!this.options.persist || !this.options.storage) return;
392
+ if (this.persistTimeout) return;
393
+ this.persistTimeout = setTimeout(() => {
394
+ this.persistTimeout = null;
395
+ this.persistNow();
396
+ }, PERSIST_DEBOUNCE_MS);
397
+ }
398
+ persistNow() {
399
+ if (this.persistTimeout) {
400
+ clearTimeout(this.persistTimeout);
401
+ this.persistTimeout = null;
402
+ }
403
+ if (!this.options.persist || !this.options.storage) return;
404
+ try {
405
+ this.options.storage.setItem(QUEUE_STORAGE_KEY, JSON.stringify([...this.inFlight, ...this.entries].map((entry) => entry.event)));
406
+ } catch {
407
+ this.options.log?.("Failed to persist queue");
408
+ }
409
+ }
410
+ restore() {
411
+ if (!this.options.persist || !this.options.storage) return 0;
412
+ try {
413
+ const raw = this.options.storage.getItem(QUEUE_STORAGE_KEY);
414
+ if (!raw) return 0;
415
+ const payloads = JSON.parse(raw);
416
+ if (!Array.isArray(payloads)) return 0;
417
+ const restored = payloads.flatMap((payload) => {
418
+ const event = this.options.normalize(payload);
419
+ return event ? [{ event }] : [];
420
+ });
421
+ this.entries.push(...restored);
422
+ this.options.storage.removeItem(QUEUE_STORAGE_KEY);
423
+ return restored.length;
424
+ } catch {
425
+ this.options.log?.("Failed to load persisted queue");
426
+ return 0;
427
+ }
428
+ }
429
+ dispose() {
430
+ if (this.persistTimeout) {
431
+ clearTimeout(this.persistTimeout);
432
+ this.persistTimeout = null;
433
+ }
434
+ }
435
+ };
436
+ //#endregion
437
+ //#region ../data-sdk-core/src/retry.ts
438
+ var TransportError = class extends Error {
439
+ status;
440
+ preserveBatch;
441
+ constructor(message, status, preserveBatch = false) {
442
+ super(message);
443
+ this.name = "TransportError";
444
+ this.status = status;
445
+ this.preserveBatch = preserveBatch;
446
+ }
447
+ };
448
+ function isRetryableTransportError(error) {
449
+ if (error instanceof TransportError && typeof error.status === "number") {
450
+ if (error.status === 429) return true;
451
+ if (error.status >= 400 && error.status < 500) return false;
452
+ }
453
+ return true;
454
+ }
455
+ const DEFAULT_RETRY = {
456
+ maxRetries: 3,
457
+ baseDelay: 1e3,
458
+ maxDelay: 3e4,
459
+ jitter: .1
460
+ };
461
+ function resolveRetryConfig(config) {
462
+ return {
463
+ maxRetries: config?.maxRetries ?? DEFAULT_RETRY.maxRetries,
464
+ baseDelay: config?.baseDelay ?? DEFAULT_RETRY.baseDelay,
465
+ maxDelay: config?.maxDelay ?? DEFAULT_RETRY.maxDelay,
466
+ jitter: config?.jitter ?? DEFAULT_RETRY.jitter
467
+ };
468
+ }
469
+ function computeBackoff(attempt, config, random = Math.random) {
470
+ const delay = Math.min(Math.pow(2, attempt) * config.baseDelay, config.maxDelay);
471
+ const jitterAmount = delay * config.jitter * (random() * 2 - 1);
472
+ return Math.max(0, delay + jitterAmount);
473
+ }
474
+ async function withRetry(operation, config, hooks = {}) {
475
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) try {
476
+ return await operation(attempt);
477
+ } catch (error) {
478
+ if (!isRetryableTransportError(error) || attempt === config.maxRetries) throw error;
479
+ const waitMs = computeBackoff(attempt, config, hooks.random);
480
+ hooks.onRetry?.(attempt + 1, waitMs, error);
481
+ await new Promise((resolve) => setTimeout(resolve, waitMs));
482
+ }
483
+ throw new TransportError("Retry loop exhausted", 500, true);
484
+ }
485
+ //#endregion
486
+ //#region ../data-sdk-core/src/storage.ts
487
+ function memoryStorage() {
488
+ const store = new Map();
489
+ return {
490
+ getItem: (key) => store.get(key) ?? null,
491
+ setItem: (key, value) => {
492
+ store.set(key, value);
493
+ },
494
+ removeItem: (key) => {
495
+ store.delete(key);
496
+ }
497
+ };
498
+ }
499
+ function safeStorage(backend, onError) {
500
+ return {
501
+ getItem: (key) => {
502
+ try {
503
+ return backend.getItem(key);
504
+ } catch (error) {
505
+ onError?.("getItem", error);
506
+ return null;
507
+ }
508
+ },
509
+ setItem: (key, value) => {
510
+ try {
511
+ backend.setItem(key, value);
512
+ } catch (error) {
513
+ onError?.("setItem", error);
514
+ }
515
+ },
516
+ removeItem: (key) => {
517
+ try {
518
+ backend.removeItem(key);
519
+ } catch (error) {
520
+ onError?.("removeItem", error);
521
+ }
522
+ }
523
+ };
524
+ }
525
+ function prefixedStorage(backend, prefix) {
526
+ return {
527
+ getItem: (key) => backend.getItem(prefix + key),
528
+ setItem: (key, value) => backend.setItem(prefix + key, value),
529
+ removeItem: (key) => backend.removeItem(prefix + key)
530
+ };
531
+ }
532
+ //#endregion
533
+ //#region ../data-sdk-core/src/types.ts
534
+ function isWriteKeyAuth(auth) {
535
+ return "writeKey" in auth;
536
+ }
537
+ //#endregion
538
+ //#region ../data-sdk-core/src/transport.ts
539
+ function readIngestCounts(rawBody) {
540
+ let body;
541
+ try {
542
+ body = JSON.parse(rawBody);
543
+ } catch (error) {
544
+ throw new TransportError(`Track response did not match contract: ${error instanceof Error ? error.message : String(error)}`, 409, true);
545
+ }
546
+ const record = typeof body === "object" && body !== null ? body : null;
547
+ const count = (value) => typeof value === "number" && Number.isInteger(value) && value >= 0;
548
+ if (!record || record.status !== "ok" && record.status !== "accepted" || !count(record.accepted) || !count(record.duplicates) || !Array.isArray(record.rejected)) throw new TransportError("Track response did not match contract: unexpected shape", 409, true);
549
+ const session = record.session;
550
+ const credential = session && typeof session.id === "string" && typeof session.token === "string" ? {
551
+ id: session.id,
552
+ token: session.token
553
+ } : void 0;
554
+ return {
555
+ accepted: record.accepted,
556
+ duplicates: record.duplicates,
557
+ rejected: record.rejected.length,
558
+ ...credential ? { session: credential } : {}
559
+ };
560
+ }
561
+ function resolveBaseUrl(value) {
562
+ if (!value) throw new Error("[reopt] `baseUrl` is required (your reopt-data origin, or the /ingest proxy prefix)");
563
+ return value.replace(/\/+$/, "");
564
+ }
565
+ function createTransport(options) {
566
+ const url = `${resolveBaseUrl(options.baseUrl)}/api/track`;
567
+ const headers = (identity) => {
568
+ const result = { "Content-Type": "application/json" };
569
+ const deviceId = identity?.deviceId ?? options.getDeviceId();
570
+ if (deviceId) result[_reopt_ai_data_contract_identity.DEVICE_ID_HEADER] = deviceId;
571
+ const sessionId = identity?.sessionId !== void 0 ? identity.sessionId : options.getSessionId?.();
572
+ if (sessionId) result[_reopt_ai_data_contract_identity.SESSION_ID_HEADER] = sessionId;
573
+ if (isWriteKeyAuth(options.auth)) result[_reopt_ai_data_contract_identity.WRITE_KEY_HEADER] = options.auth.writeKey;
574
+ else {
575
+ result[_reopt_ai_data_contract_identity.CLIENT_ID_HEADER] = options.auth.clientId;
576
+ result[_reopt_ai_data_contract_identity.CLIENT_SECRET_HEADER] = options.auth.clientSecret;
577
+ }
578
+ return result;
579
+ };
580
+ let reportedVersion;
581
+ const send = async (batch, sendOptions = {}) => {
582
+ const fetchImpl = options.fetch ?? globalThis.fetch;
583
+ if (!fetchImpl) throw new TransportError("No fetch implementation available", void 0, true);
584
+ const response = await fetchImpl(url, {
585
+ method: "POST",
586
+ headers: headers(sendOptions.identity),
587
+ body: JSON.stringify(batch),
588
+ ...sendOptions.keepalive ? { keepalive: true } : {}
589
+ });
590
+ const rawBody = await response.text();
591
+ if (!response.ok) {
592
+ let code;
593
+ try {
594
+ const parsed = JSON.parse(rawBody);
595
+ if (typeof parsed.code === "string") code = parsed.code;
596
+ } catch {}
597
+ const preserveBatch = code === "quota_exceeded";
598
+ const effectiveStatus = code === "quota_exceeded" ? 402 : response.status;
599
+ throw new TransportError(`Failed to send batch: ${rawBody}`, effectiveStatus, preserveBatch);
600
+ }
601
+ const serverVersion = response.headers.get(_reopt_ai_data_contract_identity.CONTRACT_VERSION_HEADER);
602
+ if (serverVersion !== _reopt_ai_data_contract_identity.CONTRACT_VERSION && reportedVersion !== serverVersion) {
603
+ reportedVersion = serverVersion;
604
+ options.onVersionMismatch?.(serverVersion);
605
+ }
606
+ const counts = readIngestCounts(rawBody);
607
+ if (counts.accepted + counts.duplicates + counts.rejected !== batch.length) throw new TransportError("Track response counts do not reconcile with the submitted batch", 409, true);
608
+ return {
609
+ sent: counts.accepted + counts.duplicates,
610
+ failed: counts.rejected,
611
+ ...counts.session ? { session: counts.session } : {}
612
+ };
613
+ };
614
+ return {
615
+ url,
616
+ headers,
617
+ send
618
+ };
619
+ }
620
+ //#endregion
621
+ //#region ../data-sdk-core/src/client.ts
622
+ const DEFAULT_BATCH = {
623
+ size: 100,
624
+ intervalMs: 1e3,
625
+ maxBytes: 4e5
626
+ };
627
+ const KEEPALIVE_MAX_BYTES = 6e4;
628
+ var ReoptCore = class {
629
+ runtime;
630
+ transport;
631
+ queue;
632
+ consent;
633
+ breaker;
634
+ retry;
635
+ batch;
636
+ factory;
637
+ debug;
638
+ deviceId;
639
+ profileId = null;
640
+ globalProperties = {};
641
+ activeFlush = null;
642
+ flushTimeout = null;
643
+ removeFlushSignal = null;
644
+ closed = false;
645
+ generation = 0;
646
+ lastFlushRequeued = false;
647
+ constructor(config) {
648
+ this.runtime = config.runtime;
649
+ this.debug = config.debug ?? false;
650
+ this.deviceId = config.runtime.deviceId;
651
+ const now = config.runtime.now ?? (() => Date.now());
652
+ this.factory = {
653
+ now,
654
+ createId: config.runtime.createId ?? (() => uuidv7(now()))
655
+ };
656
+ const rawStorage = config.runtime.storage;
657
+ const storage = rawStorage ? prefixedStorage(safeStorage(rawStorage, (operation) => this.log(`storage ${operation} failed`)), config.storagePrefix ?? "reopt_") : void 0;
658
+ this.batch = {
659
+ size: config.batch?.size ?? DEFAULT_BATCH.size,
660
+ intervalMs: config.batch?.intervalMs ?? DEFAULT_BATCH.intervalMs,
661
+ maxBytes: config.batch?.maxBytes ?? DEFAULT_BATCH.maxBytes
662
+ };
663
+ this.retry = resolveRetryConfig(config.retry);
664
+ this.breaker = new CircuitBreaker(config.circuitBreaker, now);
665
+ this.transport = createTransport({
666
+ baseUrl: config.baseUrl,
667
+ auth: config.auth,
668
+ getDeviceId: () => this.deviceId,
669
+ getSessionId: config.runtime.getSessionId,
670
+ fetch: config.runtime.fetch,
671
+ onVersionMismatch: (serverVersion) => this.log("server contract version", serverVersion)
672
+ });
673
+ this.consent = new ConsentManager(config.consent, storage);
674
+ this.queue = new EventQueue({
675
+ maxSize: config.maxQueueSize ?? 1e4,
676
+ storage,
677
+ persist: config.enableOfflineBuffer ?? storage !== void 0,
678
+ normalize: (entry) => normalizePersistedEvent(this.factory, entry),
679
+ log: (...args) => this.log(...args)
680
+ });
681
+ const restored = this.queue.restore();
682
+ if (restored > 0) {
683
+ this.log(`restored ${restored}`);
684
+ this.scheduleFlush();
685
+ }
686
+ if ((config.autoFlushOnUnload ?? config.runtime.onFlushSignal !== void 0) && config.runtime.onFlushSignal) this.removeFlushSignal = config.runtime.onFlushSignal(() => this.flushOnSignal());
687
+ }
688
+ log(...args) {
689
+ if (this.debug) console.log("[reopt]", ...args);
690
+ }
691
+ getDeviceId() {
692
+ return this.deviceId;
693
+ }
694
+ setDeviceId(deviceId) {
695
+ this.deviceId = deviceId;
696
+ }
697
+ setProfileId(profileId) {
698
+ this.profileId = profileId;
699
+ this.log("profile", profileId);
700
+ }
701
+ getProfileId() {
702
+ return this.profileId;
703
+ }
704
+ setConsent(category, allowed) {
705
+ this.consent.set(category, allowed);
706
+ this.log("consent", category, allowed);
707
+ }
708
+ getConsent(category) {
709
+ return this.consent.get(category);
710
+ }
711
+ setAllConsent(allowed) {
712
+ this.consent.setAll(allowed);
713
+ this.log("consent all", allowed);
714
+ }
715
+ getConsentState() {
716
+ return this.consent.snapshot();
717
+ }
718
+ replaceConsentState(state) {
719
+ this.consent.replace(state);
720
+ }
721
+ pauseTracking() {
722
+ this.consent.pause();
723
+ this.log("paused");
724
+ }
725
+ resumeTracking() {
726
+ this.consent.resume();
727
+ this.log("resumed");
728
+ if (this.queue.length > 0) this.scheduleFlush();
729
+ }
730
+ register(properties) {
731
+ this.globalProperties = {
732
+ ...this.globalProperties,
733
+ ...properties
734
+ };
735
+ }
736
+ unregister(...keys) {
737
+ for (const key of keys) delete this.globalProperties[key];
738
+ }
739
+ getGlobalProperties() {
740
+ return { ...this.globalProperties };
741
+ }
742
+ track(options) {
743
+ const merged = Object.keys(this.globalProperties).length > 0 ? {
744
+ ...options,
745
+ properties: {
746
+ ...this.globalProperties,
747
+ ...options.properties
748
+ }
749
+ } : options;
750
+ const event = buildTrackEvent(this.factory, merged, options.identity ? null : this.profileId);
751
+ return this.enqueue(event, options.consentCategory ?? "analytics", options.identity);
752
+ }
753
+ identify(options) {
754
+ if (!options.identity) this.profileId = options.profileId;
755
+ const event = buildIdentifyEvent(this.factory, options);
756
+ return this.enqueue(event, options.consentCategory ?? "analytics", options.identity);
757
+ }
758
+ increment(options) {
759
+ const event = buildCounterEvent(this.factory, "increment", options, options.identity ? null : this.profileId);
760
+ return this.enqueue(event, options.consentCategory ?? "analytics", options.identity);
761
+ }
762
+ decrement(options) {
763
+ const event = buildCounterEvent(this.factory, "decrement", options, options.identity ? null : this.profileId);
764
+ return this.enqueue(event, options.consentCategory ?? "analytics", options.identity);
765
+ }
766
+ pageView(options = {}) {
767
+ return this.track(buildPageViewOptions(options, this.runtime.getPageContext?.() ?? {}));
768
+ }
769
+ screenView(screenName, properties, identity) {
770
+ return this.track({
771
+ name: "$screen_view",
772
+ properties: {
773
+ screen_name: screenName,
774
+ ...properties
775
+ },
776
+ identity
777
+ });
778
+ }
779
+ get pending() {
780
+ return this.queue.length;
781
+ }
782
+ enqueue(event, category, identity) {
783
+ const blockReason = this.consent.enqueueBlockReason(category);
784
+ if (blockReason) {
785
+ this.log("dropped:", blockReason);
786
+ return {
787
+ eventId: event.eventId,
788
+ queued: false,
789
+ reason: blockReason
790
+ };
791
+ }
792
+ const validation = validateEvent(event);
793
+ if (!validation.valid) {
794
+ this.log("invalid:", validation.errors);
795
+ return {
796
+ eventId: event.eventId,
797
+ queued: false,
798
+ reason: "validation_failed",
799
+ errors: validation.errors
800
+ };
801
+ }
802
+ if (serializedSize(event) + 2 > this.batch.maxBytes) {
803
+ this.log("too large");
804
+ return {
805
+ eventId: event.eventId,
806
+ queued: false,
807
+ reason: "payload_too_large",
808
+ errors: [{
809
+ field: "payload",
810
+ message: `serialized event exceeds ${this.batch.maxBytes} bytes`
811
+ }]
812
+ };
813
+ }
814
+ const entry = identity ? {
815
+ event,
816
+ identity
817
+ } : { event };
818
+ const dropped = this.queue.push(entry);
819
+ if (dropped > 0) this.log("queue full, dropped", dropped);
820
+ if (this.queue.length >= this.batch.size) this.flush();
821
+ else this.scheduleFlush();
822
+ return {
823
+ eventId: event.eventId,
824
+ queued: true
825
+ };
826
+ }
827
+ scheduleFlush() {
828
+ if (this.flushTimeout || this.closed) return;
829
+ this.flushTimeout = setTimeout(() => {
830
+ this.flushTimeout = null;
831
+ this.flush();
832
+ }, this.batch.intervalMs);
833
+ }
834
+ clearScheduledFlush() {
835
+ if (!this.flushTimeout) return;
836
+ clearTimeout(this.flushTimeout);
837
+ this.flushTimeout = null;
838
+ }
839
+ flush(options = {}) {
840
+ return options.drain ? this.flushAll(options) : this.flushExclusive(options);
841
+ }
842
+ flushOnSignal() {
843
+ this.queue.persistNow();
844
+ if (this.queue.length === 0 || this.consent.flushBlockReason()) return;
845
+ const lastChance = this.runtime.sendLastChance;
846
+ if (lastChance) {
847
+ const batch = this.queue.take(this.batch.size, KEEPALIVE_MAX_BYTES);
848
+ const body = JSON.stringify(batch.map((entry) => entry.event));
849
+ if (lastChance(this.transport.url, body, this.transport.headers(batch[0]?.identity))) {
850
+ this.queue.settle();
851
+ this.queue.persistNow();
852
+ if (this.queue.length > 0) this.flushOnSignal();
853
+ return;
854
+ }
855
+ this.queue.requeue(batch);
856
+ }
857
+ while (this.queue.length > 0) {
858
+ const batch = this.queue.take(this.batch.size, Math.min(this.batch.maxBytes, KEEPALIVE_MAX_BYTES));
859
+ const events = batch.map((entry) => entry.event);
860
+ const identity = this.identityFor(batch);
861
+ this.queue.settle();
862
+ this.transport.send(events, {
863
+ keepalive: true,
864
+ identity
865
+ }).catch((error) => {
866
+ this.log("keepalive failed", error);
867
+ });
868
+ }
869
+ this.queue.persistNow();
870
+ }
871
+ identityFor(batch) {
872
+ return batch[0]?.identity ?? { deviceId: this.deviceId };
873
+ }
874
+ flushExclusive(options) {
875
+ if (this.activeFlush) return this.activeFlush;
876
+ const activeFlush = this.flushOnce(options).finally(() => {
877
+ if (this.activeFlush === activeFlush) this.activeFlush = null;
878
+ });
879
+ this.activeFlush = activeFlush;
880
+ return activeFlush;
881
+ }
882
+ async flushAll(options) {
883
+ let sent = 0;
884
+ let failed = 0;
885
+ let status = "idle";
886
+ while (this.queue.length > 0) {
887
+ const pendingBefore = this.queue.length;
888
+ const result = await this.flushExclusive({
889
+ ...options,
890
+ drain: true
891
+ });
892
+ sent += result.sent;
893
+ failed += result.failed;
894
+ status = result.status;
895
+ if (!(!this.lastFlushRequeued && (result.sent > 0 || this.queue.length < pendingBefore))) return {
896
+ status,
897
+ sent,
898
+ failed,
899
+ pending: result.pending
900
+ };
901
+ }
902
+ return {
903
+ status: sent > 0 ? "success" : status,
904
+ sent,
905
+ failed,
906
+ pending: this.queue.length
907
+ };
908
+ }
909
+ async flushOnce(options) {
910
+ if (this.queue.length === 0) {
911
+ this.clearScheduledFlush();
912
+ return {
913
+ status: "idle",
914
+ sent: 0,
915
+ failed: 0,
916
+ pending: 0
917
+ };
918
+ }
919
+ if (this.consent.flushBlockReason()) return {
920
+ status: "skipped",
921
+ sent: 0,
922
+ failed: 0,
923
+ pending: this.queue.length
924
+ };
925
+ if (!this.breaker.allowRequest()) {
926
+ this.log("breaker open");
927
+ return {
928
+ status: "skipped",
929
+ sent: 0,
930
+ failed: 0,
931
+ pending: this.queue.length
932
+ };
933
+ }
934
+ if (this.breaker.getState() === "half-open") this.log("breaker half-open");
935
+ const maxBytes = options.keepalive ? Math.min(this.batch.maxBytes, KEEPALIVE_MAX_BYTES) : this.batch.maxBytes;
936
+ const batch = this.queue.take(this.batch.size, maxBytes);
937
+ const events = batch.map((entry) => entry.event);
938
+ const identity = this.identityFor(batch);
939
+ const generation = this.generation;
940
+ this.lastFlushRequeued = false;
941
+ try {
942
+ const delivery = await withRetry(() => this.transport.send(events, {
943
+ ...options,
944
+ identity
945
+ }), this.retry, { onRetry: (attempt, waitMs) => this.log("retry", attempt, Math.round(waitMs)) });
946
+ this.log("flushed", delivery.sent, delivery.failed);
947
+ if (delivery.session && !batch[0]?.identity) this.runtime.onSession?.(delivery.session);
948
+ if (this.breaker.getState() === "half-open") this.log("breaker closed");
949
+ this.breaker.recordSuccess();
950
+ this.queue.settle();
951
+ this.queue.persistNow();
952
+ this.continueOrSettle(options);
953
+ return {
954
+ status: delivery.failed > 0 ? "failed" : "success",
955
+ sent: delivery.sent,
956
+ failed: delivery.failed,
957
+ pending: this.queue.length
958
+ };
959
+ } catch (error) {
960
+ const preserve = error instanceof TransportError && error.preserveBatch;
961
+ if (!isRetryableTransportError(error) && !preserve) {
962
+ this.log("rejected", batch.length, error);
963
+ this.breaker.recordRejection();
964
+ this.queue.settle();
965
+ this.queue.persistNow();
966
+ this.continueOrSettle(options);
967
+ return {
968
+ status: "failed",
969
+ sent: 0,
970
+ failed: batch.length,
971
+ pending: this.queue.length
972
+ };
973
+ }
974
+ this.log("flush failed", error);
975
+ if (generation !== this.generation) {
976
+ this.queue.settle();
977
+ return {
978
+ status: "failed",
979
+ sent: 0,
980
+ failed: batch.length,
981
+ pending: this.queue.length
982
+ };
983
+ }
984
+ this.lastFlushRequeued = true;
985
+ this.queue.requeue(batch);
986
+ this.queue.persistNow();
987
+ if (!isRetryableTransportError(error)) return {
988
+ status: "failed",
989
+ sent: 0,
990
+ failed: batch.length,
991
+ pending: this.queue.length
992
+ };
993
+ if (this.breaker.recordFailure()) this.log("breaker opened");
994
+ return {
995
+ status: "failed",
996
+ sent: 0,
997
+ failed: batch.length,
998
+ pending: this.queue.length
999
+ };
1000
+ }
1001
+ }
1002
+ continueOrSettle(options) {
1003
+ if (!options.drain && this.queue.length > 0) this.scheduleFlush();
1004
+ else if (this.queue.length === 0) this.clearScheduledFlush();
1005
+ }
1006
+ reset() {
1007
+ this.generation += 1;
1008
+ this.profileId = null;
1009
+ this.globalProperties = {};
1010
+ this.queue.clear();
1011
+ this.queue.settle();
1012
+ this.queue.persistNow();
1013
+ const regenerate = this.runtime.regenerateDeviceId;
1014
+ if (regenerate) this.deviceId = regenerate();
1015
+ this.log("reset");
1016
+ }
1017
+ async close() {
1018
+ this.closed = true;
1019
+ this.removeFlushSignal?.();
1020
+ this.removeFlushSignal = null;
1021
+ this.clearScheduledFlush();
1022
+ this.queue.dispose();
1023
+ this.queue.persistNow();
1024
+ const result = await this.flush({ drain: true });
1025
+ this.clearScheduledFlush();
1026
+ return result;
1027
+ }
1028
+ };
1029
+ //#endregion
1030
+ //#region src/capture/exception-properties.ts
1031
+ const MAX_STACK_CHARS = 8e3;
1032
+ function describeErrorFlat(value, source) {
1033
+ const path = typeof location !== "undefined" ? location.pathname : "";
1034
+ const handled = source === "captureException";
1035
+ if (value instanceof Error) return {
1036
+ $exception_type: value.name || "Error",
1037
+ $exception_message: value.message,
1038
+ ...value.stack ? { $exception_stack: value.stack.slice(0, MAX_STACK_CHARS) } : {},
1039
+ $exception_source: source,
1040
+ $exception_handled: handled,
1041
+ path
1042
+ };
1043
+ return {
1044
+ $exception_type: typeof value === "object" && value !== null ? "UnknownError" : typeof value,
1045
+ $exception_message: safeString(value),
1046
+ $exception_source: source,
1047
+ $exception_handled: handled,
1048
+ path
1049
+ };
1050
+ }
1051
+ function safeString(value) {
1052
+ try {
1053
+ return typeof value === "string" ? value : JSON.stringify(value) ?? String(value);
1054
+ } catch {
1055
+ return String(value);
1056
+ }
1057
+ }
1058
+ //#endregion
1059
+ //#region src/steps.ts
1060
+ const MAX_STEPS = 20;
1061
+ const MAX_MESSAGE_CHARS = 200;
1062
+ const MAX_DATA_BYTES = 1024;
1063
+ const steps = [];
1064
+ function boundData(data) {
1065
+ if (!data) return void 0;
1066
+ try {
1067
+ const json = JSON.stringify(data);
1068
+ return json && json.length <= MAX_DATA_BYTES ? data : void 0;
1069
+ } catch {
1070
+ return;
1071
+ }
1072
+ }
1073
+ function addExceptionStep(step) {
1074
+ const data = boundData(step.data);
1075
+ steps.push({
1076
+ timestamp: Date.now(),
1077
+ category: step.category,
1078
+ message: step.message.slice(0, MAX_MESSAGE_CHARS),
1079
+ ...data ? { data } : {}
1080
+ });
1081
+ if (steps.length > MAX_STEPS) steps.splice(0, steps.length - MAX_STEPS);
1082
+ }
1083
+ function readExceptionSteps() {
1084
+ return steps.slice();
1085
+ }
1086
+ //#endregion
1087
+ //#region src/capture/pageleave.ts
1088
+ var PageLeaveTracker = class {
1089
+ current = null;
1090
+ now;
1091
+ constructor(now) {
1092
+ this.now = now;
1093
+ }
1094
+ enter(path, origin, scrollDepth) {
1095
+ const left = this.leave(scrollDepth);
1096
+ this.current = {
1097
+ path,
1098
+ origin,
1099
+ startedAt: this.now()
1100
+ };
1101
+ return left;
1102
+ }
1103
+ leave(scrollDepth) {
1104
+ const page = this.current;
1105
+ if (!page) return null;
1106
+ this.current = null;
1107
+ const seconds = Math.max(0, Math.round((this.now() - page.startedAt) / 1e3));
1108
+ return {
1109
+ path: page.path,
1110
+ origin: page.origin,
1111
+ duration: seconds,
1112
+ ...scrollDepth !== null ? {
1113
+ scroll_depth: scrollDepth,
1114
+ scroll_depth_bucket: (0, _reopt_ai_data_contract_events.scrollDepthBucket)(scrollDepth)
1115
+ } : {}
1116
+ };
1117
+ }
1118
+ resume() {}
1119
+ peek() {
1120
+ return this.current;
1121
+ }
1122
+ };
1123
+ //#endregion
1124
+ //#region src/capture/scroll-depth.ts
1125
+ function currentDepth() {
1126
+ if (typeof window === "undefined" || typeof document === "undefined") return null;
1127
+ const root = document.documentElement;
1128
+ const scrollable = Math.max(root.scrollHeight, document.body?.scrollHeight ?? 0) - window.innerHeight;
1129
+ if (!Number.isFinite(scrollable) || scrollable <= 0) return 100;
1130
+ const scrolled = window.scrollY ?? root.scrollTop ?? 0;
1131
+ return Math.max(0, Math.min(100, Math.round(scrolled / scrollable * 100)));
1132
+ }
1133
+ function startScrollDepth() {
1134
+ let max = null;
1135
+ const sample = () => {
1136
+ const depth = currentDepth();
1137
+ if (depth !== null && (max === null || depth > max)) max = depth;
1138
+ };
1139
+ if (typeof window !== "undefined") {
1140
+ window.addEventListener("scroll", sample, { passive: true });
1141
+ window.addEventListener("resize", sample, { passive: true });
1142
+ }
1143
+ sample();
1144
+ return {
1145
+ read: () => {
1146
+ sample();
1147
+ return max;
1148
+ },
1149
+ reset: () => {
1150
+ max = null;
1151
+ sample();
1152
+ },
1153
+ stop: () => {
1154
+ if (typeof window === "undefined") return;
1155
+ window.removeEventListener("scroll", sample);
1156
+ window.removeEventListener("resize", sample);
1157
+ }
1158
+ };
1159
+ }
1160
+ //#endregion
1161
+ //#region src/capture/utm.ts
1162
+ const UTM_KEYS = [
1163
+ "utm_source",
1164
+ "utm_medium",
1165
+ "utm_campaign",
1166
+ "utm_term",
1167
+ "utm_content"
1168
+ ];
1169
+ function extractUTMParams(search = typeof location !== "undefined" ? location.search : "") {
1170
+ try {
1171
+ const params = new URLSearchParams(search);
1172
+ const utm = {};
1173
+ for (const key of UTM_KEYS) {
1174
+ const value = params.get(key);
1175
+ if (value) utm[key] = value;
1176
+ }
1177
+ return utm;
1178
+ } catch {
1179
+ return {};
1180
+ }
1181
+ }
1182
+ //#endregion
1183
+ //#region src/capture/web-vitals.ts
1184
+ function webVitalProperties(metric, path) {
1185
+ return {
1186
+ metric_name: metric.name,
1187
+ metric_id: metric.id,
1188
+ value: metric.value,
1189
+ ...metric.delta !== void 0 ? { delta: metric.delta } : {},
1190
+ ...metric.rating ? { rating: metric.rating } : {},
1191
+ ...metric.navigationType ? { navigation_type: metric.navigationType } : {},
1192
+ path
1193
+ };
1194
+ }
1195
+ //#endregion
1196
+ //#region src/config.ts
1197
+ function resolveCapture(config, pageviewDefault) {
1198
+ return {
1199
+ pageview: config?.pageview ?? pageviewDefault,
1200
+ pageleave: config?.pageleave ?? true,
1201
+ scrollDepth: config?.scrollDepth ?? true,
1202
+ exceptions: config?.exceptions ?? false,
1203
+ exceptionRateLimit: config?.exceptionRateLimit ?? {},
1204
+ exceptionSteps: config?.exceptionSteps ?? false
1205
+ };
1206
+ }
1207
+ function resolveTracingHosts(option) {
1208
+ if (!option) return [];
1209
+ if (Array.isArray(option)) return option;
1210
+ if (typeof location === "undefined" || !location.hostname) return [];
1211
+ return [location.hostname];
1212
+ }
1213
+ //#endregion
1214
+ //#region src/identity/cookie-storage.ts
1215
+ function readCookie(name) {
1216
+ if (typeof document === "undefined") return null;
1217
+ const prefix = `${name}=`;
1218
+ for (const part of document.cookie.split(";")) {
1219
+ const trimmed = part.trim();
1220
+ if (trimmed.startsWith(prefix)) return trimmed.slice(prefix.length);
1221
+ }
1222
+ return null;
1223
+ }
1224
+ function writeCookie(name, value, options = {}) {
1225
+ if (typeof document === "undefined") return;
1226
+ let cookie = `${name}=${value}; Path=/; Max-Age=${options.maxAgeSeconds ?? _reopt_ai_data_contract_identity.COOKIE_MAX_AGE_SECONDS}; SameSite=Lax`;
1227
+ if (options.domain) cookie += `; Domain=${options.domain}`;
1228
+ if (typeof location !== "undefined" && location.protocol === "https:") cookie += "; Secure";
1229
+ document.cookie = cookie;
1230
+ }
1231
+ function deleteCookie(name, options = {}) {
1232
+ if (typeof document === "undefined") return;
1233
+ let cookie = `${name}=; Path=/; Max-Age=0; SameSite=Lax`;
1234
+ if (options.domain) cookie += `; Domain=${options.domain}`;
1235
+ document.cookie = cookie;
1236
+ }
1237
+ function cookiesAvailable() {
1238
+ if (typeof document === "undefined") return false;
1239
+ try {
1240
+ const probe = "reopt_probe";
1241
+ document.cookie = `${probe}=1; Path=/; Max-Age=60; SameSite=Lax`;
1242
+ const ok = readCookie(probe) === "1";
1243
+ document.cookie = `${probe}=; Path=/; Max-Age=0; SameSite=Lax`;
1244
+ return ok;
1245
+ } catch {
1246
+ return false;
1247
+ }
1248
+ }
1249
+ //#endregion
1250
+ //#region src/identity/device.ts
1251
+ const LEGACY_DEVICE_KEY = "device_id";
1252
+ function localStorageBackend() {
1253
+ try {
1254
+ if (typeof window === "undefined" || !window.localStorage) return null;
1255
+ const storage = window.localStorage;
1256
+ const probe = "reopt_probe";
1257
+ storage.setItem(probe, "1");
1258
+ storage.removeItem(probe);
1259
+ return storage;
1260
+ } catch {
1261
+ return null;
1262
+ }
1263
+ }
1264
+ function makeStore(kind, writeKey, backend) {
1265
+ const deviceName = (0, _reopt_ai_data_contract_identity.deviceCookieName)(writeKey);
1266
+ const consentName = (0, _reopt_ai_data_contract_identity.consentCookieName)(writeKey);
1267
+ const readConsent = () => (0, _reopt_ai_data_contract_identity.parseConsentCookie)(backend.getItem(consentName));
1268
+ return {
1269
+ kind,
1270
+ readDevice: () => (0, _reopt_ai_data_contract_identity.parseDeviceCookie)(backend.getItem(deviceName)),
1271
+ writeDevice: (state) => backend.setItem(deviceName, (0, _reopt_ai_data_contract_identity.serializeDeviceCookie)(state)),
1272
+ clearDevice: () => backend.removeItem(deviceName),
1273
+ readConsent,
1274
+ writeConsent: (state) => backend.setItem(consentName, (0, _reopt_ai_data_contract_identity.serializeConsentCookie)(state)),
1275
+ consentBackend: () => ({
1276
+ getItem: () => {
1277
+ const state = readConsent();
1278
+ return state ? JSON.stringify(state) : null;
1279
+ },
1280
+ setItem: (_key, value) => backend.setItem(consentName, encodeURIComponent(value)),
1281
+ removeItem: () => backend.removeItem(consentName)
1282
+ })
1283
+ };
1284
+ }
1285
+ function cookieBackend(cookie) {
1286
+ return {
1287
+ getItem: readCookie,
1288
+ setItem: (name, value) => writeCookie(name, value, cookie),
1289
+ removeItem: (name) => deleteCookie(name, cookie)
1290
+ };
1291
+ }
1292
+ function createIdentityStore(writeKey, config) {
1293
+ const kind = config?.storage ?? "auto";
1294
+ const cookie = {
1295
+ domain: config?.cookieDomain,
1296
+ maxAgeSeconds: config?.cookieMaxAgeSeconds
1297
+ };
1298
+ if (kind === "cookie" || kind === "auto" && cookiesAvailable()) return makeStore("cookie", writeKey, cookieBackend(cookie));
1299
+ const local = kind === "memory" ? null : localStorageBackend();
1300
+ if (local) return makeStore("localStorage", writeKey, local);
1301
+ return makeStore("memory", writeKey, memoryStorage());
1302
+ }
1303
+ function resolveDeviceId(store, bootstrapDeviceId, storagePrefix) {
1304
+ const stored = store.readDevice();
1305
+ if (stored) return {
1306
+ deviceId: stored.deviceId,
1307
+ source: "store"
1308
+ };
1309
+ if ((0, _reopt_ai_data_contract_identity.isValidIdentityId)(bootstrapDeviceId)) {
1310
+ store.writeDevice({ deviceId: bootstrapDeviceId });
1311
+ return {
1312
+ deviceId: bootstrapDeviceId,
1313
+ source: "bootstrap"
1314
+ };
1315
+ }
1316
+ const local = localStorageBackend();
1317
+ const legacy = local?.getItem(`${storagePrefix}${LEGACY_DEVICE_KEY}`);
1318
+ if ((0, _reopt_ai_data_contract_identity.isValidIdentityId)(legacy)) {
1319
+ store.writeDevice({ deviceId: legacy });
1320
+ local?.removeItem(`${storagePrefix}${LEGACY_DEVICE_KEY}`);
1321
+ return {
1322
+ deviceId: legacy,
1323
+ source: "legacy"
1324
+ };
1325
+ }
1326
+ const generated = uuidv4();
1327
+ store.writeDevice({ deviceId: generated });
1328
+ return {
1329
+ deviceId: generated,
1330
+ source: "generated"
1331
+ };
1332
+ }
1333
+ function regenerateDeviceId(store) {
1334
+ const deviceId = uuidv4();
1335
+ store.writeDevice({ deviceId });
1336
+ return deviceId;
1337
+ }
1338
+ //#endregion
1339
+ //#region src/client.ts
1340
+ const CLOCK_SKEW_THRESHOLD_MS = 3e4;
1341
+ const MAX_CLOCK_SKEW_MS = 864e5;
1342
+ function routedStorage(consentKey, consent, rest) {
1343
+ const pick = (key) => key === consentKey ? consent : rest;
1344
+ return {
1345
+ getItem: (key) => pick(key).getItem(key),
1346
+ setItem: (key, value) => pick(key).setItem(key, value),
1347
+ removeItem: (key) => pick(key).removeItem(key)
1348
+ };
1349
+ }
1350
+ function normalizedPath(normalize, pathname) {
1351
+ if (!normalize) return { path: pathname };
1352
+ try {
1353
+ const result = normalize(pathname);
1354
+ if (typeof result === "string") return { path: result || pathname };
1355
+ if (result && typeof result.path === "string" && result.path) return result;
1356
+ } catch {}
1357
+ return { path: pathname };
1358
+ }
1359
+ function pageContext(normalize) {
1360
+ const hasWindow = typeof window !== "undefined";
1361
+ const hasDocument = typeof document !== "undefined";
1362
+ const normalized = normalizedPath(normalize, hasWindow ? window.location.pathname : "/");
1363
+ return {
1364
+ path: normalized.path,
1365
+ origin: hasWindow ? window.location.origin : "",
1366
+ title: hasDocument ? document.title : "",
1367
+ referrer: hasDocument ? document.referrer : "",
1368
+ utm: hasWindow ? extractUTMParams(window.location.search) : {},
1369
+ ...normalized.properties ? { properties: normalized.properties } : {}
1370
+ };
1371
+ }
1372
+ const DISABLED_BASE_URL = "https://reopt.invalid";
1373
+ function readCaptureExceptionOptions(value) {
1374
+ if (!value) return {};
1375
+ if ("fingerprint" in value || "level" in value || "properties" in value) return value;
1376
+ return { properties: value };
1377
+ }
1378
+ var ReoptClient = class extends ReoptCore {
1379
+ writeKey;
1380
+ capture;
1381
+ store;
1382
+ pageLeave;
1383
+ scroll;
1384
+ uninstallers = [];
1385
+ torndown = false;
1386
+ exceptionChunk;
1387
+ exceptionChunkPromise;
1388
+ session;
1389
+ optedOut;
1390
+ normalize;
1391
+ observer;
1392
+ disabled;
1393
+ constructor(config, defaults = { pageview: true }) {
1394
+ const disabled = !config.writeKey || !config.baseUrl;
1395
+ if (disabled) console.warn(`[reopt] ${!config.writeKey ? "writeKey" : "baseUrl"} missing — analytics disabled`);
1396
+ const writeKey = config.writeKey || "disabled";
1397
+ const storagePrefix = config.storagePrefix ?? "reopt_";
1398
+ const rawStore = createIdentityStore(writeKey, {
1399
+ ...config.identity,
1400
+ ...disabled ? { storage: "memory" } : {}
1401
+ });
1402
+ const optedOut = { value: (0, _reopt_ai_data_contract_identity.isOptedOut)(rawStore.readConsent() ?? config.bootstrap?.consent ?? null) || config.consent?.defaultConsent === false };
1403
+ const store = {
1404
+ ...rawStore,
1405
+ writeDevice: (state) => {
1406
+ if (!optedOut.value) rawStore.writeDevice(state);
1407
+ }
1408
+ };
1409
+ const identity = resolveDeviceId(store, config.bootstrap?.deviceId, storagePrefix);
1410
+ const now = correctedClock(config.bootstrap);
1411
+ const queueStorage = disabled ? memoryStorage() : config.queueStorage ?? localStorageBackend() ?? memoryStorage();
1412
+ const transportFetch = config.fetch ?? (typeof window !== "undefined" ? window.fetch.bind(window) : void 0);
1413
+ const box = {};
1414
+ const session = { header: store.readDevice()?.sessionId ?? null };
1415
+ const runtime = {
1416
+ deviceId: identity.deviceId,
1417
+ getSessionId: () => (0, _reopt_ai_data_contract_identity.parseSessionHeader)(session.header) ? session.header : null,
1418
+ onSession: (credential) => {
1419
+ session.header = (0, _reopt_ai_data_contract_identity.formatSessionHeader)(credential);
1420
+ const current = store.readDevice();
1421
+ store.writeDevice({
1422
+ ...current,
1423
+ deviceId: current?.deviceId ?? identity.deviceId,
1424
+ sessionId: session.header
1425
+ });
1426
+ },
1427
+ storage: routedStorage(`${storagePrefix}consent`, store.consentBackend(), queueStorage),
1428
+ now,
1429
+ fetch: transportFetch,
1430
+ getPageContext: () => pageContext(config.normalizePath),
1431
+ regenerateDeviceId: () => regenerateDeviceId(store),
1432
+ onFlushSignal: (flush) => {
1433
+ if (typeof window === "undefined") return () => {};
1434
+ const onHide = () => {
1435
+ box.leave?.();
1436
+ flush();
1437
+ };
1438
+ const onVisibility = () => {
1439
+ if (document.visibilityState === "hidden") onHide();
1440
+ };
1441
+ window.addEventListener("pagehide", onHide);
1442
+ document.addEventListener("visibilitychange", onVisibility);
1443
+ return () => {
1444
+ window.removeEventListener("pagehide", onHide);
1445
+ document.removeEventListener("visibilitychange", onVisibility);
1446
+ };
1447
+ }
1448
+ };
1449
+ super({
1450
+ auth: { writeKey },
1451
+ baseUrl: disabled ? DISABLED_BASE_URL : config.baseUrl,
1452
+ runtime,
1453
+ debug: config.debug,
1454
+ batch: config.batch,
1455
+ maxQueueSize: config.maxQueueSize,
1456
+ retry: config.retry,
1457
+ circuitBreaker: config.circuitBreaker,
1458
+ consent: config.consent,
1459
+ storagePrefix
1460
+ });
1461
+ this.writeKey = config.writeKey;
1462
+ this.disabled = disabled;
1463
+ this.session = session;
1464
+ this.optedOut = optedOut;
1465
+ this.normalize = config.normalizePath;
1466
+ this.store = store;
1467
+ this.capture = resolveCapture(config.capture, defaults.pageview);
1468
+ if (config.observe) {
1469
+ const pending = [];
1470
+ let sink = (fact) => pending.push(fact);
1471
+ this.observer = (fact) => sink(fact);
1472
+ Promise.resolve().then(() => require("./observe-D-BHMwrO.cjs")).then(({ createObservationSink }) => {
1473
+ sink = createObservationSink(config.observe);
1474
+ for (const fact of pending.splice(0)) sink(fact);
1475
+ }).catch(() => {
1476
+ pending.length = 0;
1477
+ });
1478
+ }
1479
+ this.pageLeave = new PageLeaveTracker(now);
1480
+ this.scroll = !disabled && this.capture.scrollDepth && typeof window !== "undefined" ? startScrollDepth() : null;
1481
+ this.observe([
1482
+ "config",
1483
+ Date.now(),
1484
+ disabled,
1485
+ config.baseUrl,
1486
+ Boolean(config.writeKey),
1487
+ config.debug ?? false,
1488
+ this.capture,
1489
+ config.consent?.persist ?? true,
1490
+ config.batch?.intervalMs ?? 1e3
1491
+ ]);
1492
+ this.observeIdentity("initialized");
1493
+ if (disabled) {
1494
+ this.pauseTracking();
1495
+ return;
1496
+ }
1497
+ if (config.properties) this.register(config.properties);
1498
+ const release = (config.release ?? globalThis.__REOPT_RELEASE__)?.trim();
1499
+ if (release) this.register({ $release_id: release });
1500
+ box.leave = () => this.emitPageLeave();
1501
+ this.log(`Device ${identity.deviceId} (${identity.source}, ${store.kind})`);
1502
+ if (config.bootstrap?.consent && !store.readConsent() && Object.keys(config.bootstrap.consent).length > 0) this.replaceConsentState(config.bootstrap.consent);
1503
+ const hosts = resolveTracingHosts(config.tracingHeaders);
1504
+ if (hosts.length > 0) Promise.resolve().then(() => require("./tracing-DAhaKkl0.cjs")).then(({ installTracingHeaders }) => {
1505
+ if (!this.torndown) this.uninstallers.push(installTracingHeaders({
1506
+ hosts,
1507
+ getDeviceId: () => this.getDeviceId()
1508
+ }));
1509
+ });
1510
+ if (this.capture.exceptions) this.loadExceptionChunk().then((chunk) => {
1511
+ if (!this.torndown) this.uninstallers.push(chunk.installExceptionCapture((properties) => this.trackException(properties), {
1512
+ rateLimit: this.capture.exceptionRateLimit,
1513
+ onThrottled: (type) => this.log(`exception rate limit reached for ${type} — further ones are dropped`)
1514
+ }));
1515
+ });
1516
+ if (typeof document !== "undefined") {
1517
+ const onVisible = () => {
1518
+ if (document.visibilityState === "visible" && !this.pageLeave.peek()) {
1519
+ const context = pageContext(this.normalize);
1520
+ this.pageLeave.enter(context.path ?? "/", context.origin ?? "", null);
1521
+ this.scroll?.reset();
1522
+ }
1523
+ };
1524
+ document.addEventListener("visibilitychange", onVisible);
1525
+ this.uninstallers.push(() => document.removeEventListener("visibilitychange", onVisible));
1526
+ }
1527
+ if (this.capture.pageview && typeof document !== "undefined") this.pageView();
1528
+ }
1529
+ get identityStorage() {
1530
+ return this.store.kind;
1531
+ }
1532
+ observe(fact) {
1533
+ this.observer?.(fact);
1534
+ }
1535
+ observeIdentity(action) {
1536
+ this.observe([
1537
+ "identity",
1538
+ Date.now(),
1539
+ action,
1540
+ this.getDeviceId(),
1541
+ this.getProfileId(),
1542
+ this.store.kind
1543
+ ]);
1544
+ }
1545
+ observeEvent(eventType, name, result) {
1546
+ if (this.disabled) return;
1547
+ this.observe([
1548
+ "event",
1549
+ Date.now(),
1550
+ eventType,
1551
+ name,
1552
+ result,
1553
+ this.pending
1554
+ ]);
1555
+ }
1556
+ track(options) {
1557
+ const result = super.track(options);
1558
+ this.observeEvent("track", options.name, result);
1559
+ return result;
1560
+ }
1561
+ pageView(options = {}) {
1562
+ const context = pageContext(this.normalize);
1563
+ const path = options.path ?? context.path ?? "/";
1564
+ const origin = options.origin ?? context.origin ?? "";
1565
+ if (this.capture.exceptionSteps) addExceptionStep({
1566
+ category: "navigation",
1567
+ message: path
1568
+ });
1569
+ if (this.capture.pageleave) {
1570
+ const left = this.pageLeave.enter(path, origin, this.scroll?.read() ?? null);
1571
+ if (left) this.track({
1572
+ name: "$pageleave",
1573
+ properties: left
1574
+ });
1575
+ }
1576
+ this.scroll?.reset();
1577
+ return super.pageView(options);
1578
+ }
1579
+ setConsent(category, allowed) {
1580
+ super.setConsent(category, allowed);
1581
+ this.syncOptOut();
1582
+ this.observe([
1583
+ "consent",
1584
+ Date.now(),
1585
+ category,
1586
+ allowed
1587
+ ]);
1588
+ }
1589
+ setAllConsent(allowed) {
1590
+ super.setAllConsent(allowed);
1591
+ this.syncOptOut();
1592
+ this.observe([
1593
+ "consent",
1594
+ Date.now(),
1595
+ "all",
1596
+ allowed
1597
+ ]);
1598
+ }
1599
+ replaceConsentState(state) {
1600
+ super.replaceConsentState(state);
1601
+ this.syncOptOut();
1602
+ for (const [category, allowed] of Object.entries(state)) this.observe([
1603
+ "consent",
1604
+ Date.now(),
1605
+ category,
1606
+ allowed
1607
+ ]);
1608
+ }
1609
+ pauseTracking() {
1610
+ super.pauseTracking();
1611
+ this.observe([
1612
+ "tracking",
1613
+ Date.now(),
1614
+ true
1615
+ ]);
1616
+ }
1617
+ resumeTracking() {
1618
+ super.resumeTracking();
1619
+ this.observe([
1620
+ "tracking",
1621
+ Date.now(),
1622
+ false
1623
+ ]);
1624
+ }
1625
+ setProfileId(profileId) {
1626
+ super.setProfileId(profileId);
1627
+ this.observeIdentity("profile_changed");
1628
+ }
1629
+ syncOptOut() {
1630
+ const now = (0, _reopt_ai_data_contract_identity.isOptedOut)(this.getConsentState());
1631
+ if (now === this.optedOut.value) return;
1632
+ this.optedOut.value = now;
1633
+ if (now) {
1634
+ this.store.clearDevice();
1635
+ this.session.header = null;
1636
+ } else this.store.writeDevice({ deviceId: this.getDeviceId() });
1637
+ }
1638
+ identify(options) {
1639
+ const result = super.identify(options);
1640
+ if (!options.identity && result.queued) this.store.writeDevice({
1641
+ deviceId: this.getDeviceId(),
1642
+ profileId: String(options.profileId)
1643
+ });
1644
+ this.observeEvent("identify", "identify", result);
1645
+ this.observeIdentity("identified");
1646
+ return result;
1647
+ }
1648
+ increment(options) {
1649
+ const result = super.increment(options);
1650
+ this.observeEvent("increment", "increment", result);
1651
+ return result;
1652
+ }
1653
+ decrement(options) {
1654
+ const result = super.decrement(options);
1655
+ this.observeEvent("decrement", "decrement", result);
1656
+ return result;
1657
+ }
1658
+ captureWebVital(metric) {
1659
+ const { path, properties } = this.currentPath();
1660
+ return this.track({
1661
+ name: "$web_vitals",
1662
+ properties: {
1663
+ ...properties,
1664
+ ...webVitalProperties(metric, path)
1665
+ }
1666
+ });
1667
+ }
1668
+ currentPath() {
1669
+ return normalizedPath(this.normalize, typeof location !== "undefined" ? location.pathname : "/");
1670
+ }
1671
+ captureException(error, optionsOrProperties) {
1672
+ const options = readCaptureExceptionOptions(optionsOrProperties);
1673
+ const described = this.exceptionChunk ? this.exceptionChunk.describeError(error, "captureException") : describeErrorFlat(error, "captureException");
1674
+ this.loadExceptionChunk();
1675
+ const extra = { ...options?.properties };
1676
+ if (options?.level) extra.$exception_level = options.level;
1677
+ if (options?.fingerprint) extra.$exception_fingerprint = options.fingerprint;
1678
+ return this.trackException({
1679
+ ...described,
1680
+ ...extra,
1681
+ $exception_handled: true
1682
+ });
1683
+ }
1684
+ addExceptionStep(step) {
1685
+ addExceptionStep(step);
1686
+ }
1687
+ loadExceptionChunk() {
1688
+ this.exceptionChunkPromise ??= Promise.resolve().then(() => require("./exceptions-CuhGo9A5.cjs")).then((chunk) => {
1689
+ this.exceptionChunk = chunk;
1690
+ return chunk;
1691
+ });
1692
+ return this.exceptionChunkPromise;
1693
+ }
1694
+ trackException(properties) {
1695
+ const current = this.currentPath();
1696
+ const steps = readExceptionSteps();
1697
+ return this.track({
1698
+ name: "$exception",
1699
+ properties: {
1700
+ ...current.properties,
1701
+ ...properties,
1702
+ ...steps.length > 0 ? { $exception_steps: steps } : {},
1703
+ path: current.path
1704
+ }
1705
+ });
1706
+ }
1707
+ emitPageLeave() {
1708
+ if (!this.capture.pageleave) return;
1709
+ const left = this.pageLeave.leave(this.scroll?.read() ?? null);
1710
+ if (left) this.track({
1711
+ name: "$pageleave",
1712
+ properties: left
1713
+ });
1714
+ }
1715
+ reset() {
1716
+ super.reset();
1717
+ this.session.header = null;
1718
+ this.store.writeDevice({ deviceId: this.getDeviceId() });
1719
+ this.observeIdentity("reset");
1720
+ }
1721
+ async close() {
1722
+ this.torndown = true;
1723
+ this.emitPageLeave();
1724
+ this.scroll?.stop();
1725
+ for (const uninstall of this.uninstallers.splice(0)) uninstall();
1726
+ return super.close();
1727
+ }
1728
+ };
1729
+ function correctedClock(bootstrap) {
1730
+ if (!bootstrap || !Number.isFinite(bootstrap.serverTimeMs)) return () => Date.now();
1731
+ const skew = bootstrap.serverTimeMs - Date.now();
1732
+ if (Math.abs(skew) < CLOCK_SKEW_THRESHOLD_MS || Math.abs(skew) > MAX_CLOCK_SKEW_MS) return () => Date.now();
1733
+ return () => Date.now() + skew;
1734
+ }
1735
+ function usableBootstrap(bootstrap) {
1736
+ if (!bootstrap) return null;
1737
+ if (bootstrap.deviceId && !(0, _reopt_ai_data_contract_identity.isValidIdentityId)(bootstrap.deviceId)) return {
1738
+ ...bootstrap,
1739
+ deviceId: ""
1740
+ };
1741
+ return bootstrap;
1742
+ }
1743
+ //#endregion
1744
+ //#region src/registry.ts
1745
+ function registry() {
1746
+ if (typeof window === "undefined") return null;
1747
+ const holder = window;
1748
+ if (!holder.__reopt) holder.__reopt = { clients: new Map() };
1749
+ return holder.__reopt;
1750
+ }
1751
+ function getOrCreateClient(config, defaults) {
1752
+ const store = registry();
1753
+ const existing = store?.clients.get(config.writeKey);
1754
+ if (existing) return existing;
1755
+ const client = new ReoptClient({
1756
+ ...config,
1757
+ bootstrap: usableBootstrap(config.bootstrap)
1758
+ }, defaults);
1759
+ store?.clients.set(config.writeKey, client);
1760
+ return client;
1761
+ }
1762
+ function getClient(writeKey) {
1763
+ const store = registry();
1764
+ if (!store) return null;
1765
+ if (writeKey) return store.clients.get(writeKey) ?? null;
1766
+ const first = store.clients.values().next();
1767
+ return first.done ? null : first.value;
1768
+ }
1769
+ function forgetClient(client) {
1770
+ registry()?.clients.delete(client.writeKey);
1771
+ }
1772
+ //#endregion
1773
+ Object.defineProperty(exports, "MAX_STACK_CHARS", {
1774
+ enumerable: true,
1775
+ get: function() {
1776
+ return MAX_STACK_CHARS;
1777
+ }
1778
+ });
1779
+ Object.defineProperty(exports, "ReoptClient", {
1780
+ enumerable: true,
1781
+ get: function() {
1782
+ return ReoptClient;
1783
+ }
1784
+ });
1785
+ Object.defineProperty(exports, "extractUTMParams", {
1786
+ enumerable: true,
1787
+ get: function() {
1788
+ return extractUTMParams;
1789
+ }
1790
+ });
1791
+ Object.defineProperty(exports, "forgetClient", {
1792
+ enumerable: true,
1793
+ get: function() {
1794
+ return forgetClient;
1795
+ }
1796
+ });
1797
+ Object.defineProperty(exports, "getClient", {
1798
+ enumerable: true,
1799
+ get: function() {
1800
+ return getClient;
1801
+ }
1802
+ });
1803
+ Object.defineProperty(exports, "getOrCreateClient", {
1804
+ enumerable: true,
1805
+ get: function() {
1806
+ return getOrCreateClient;
1807
+ }
1808
+ });
1809
+
1810
+ //# sourceMappingURL=registry-CGqrOtf4.cjs.map