@reopt-ai/data-sdk-client 0.1.6 → 0.2.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.
- package/README.md +141 -7
- package/dist/client-B1GlWwVq.d.ts +744 -0
- package/dist/client-B1GlWwVq.d.ts.map +1 -0
- package/dist/client-BCrui21p.d.cts +744 -0
- package/dist/client-BCrui21p.d.cts.map +1 -0
- package/dist/exceptions-C9Fh0BIR.js +285 -0
- package/dist/exceptions-C9Fh0BIR.js.map +1 -0
- package/dist/exceptions-CuhGo9A5.cjs +289 -0
- package/dist/exceptions-CuhGo9A5.cjs.map +1 -0
- package/dist/index.cjs +75 -1874
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +83 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +35 -33
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -70
- package/dist/index.js.map +1 -1
- package/dist/next.cjs +51 -1919
- package/dist/next.cjs.map +1 -1
- package/dist/next.d.cts +31 -0
- package/dist/next.d.cts.map +1 -0
- package/dist/next.d.ts +8 -10
- package/dist/next.d.ts.map +1 -0
- package/dist/next.js +36 -49
- package/dist/next.js.map +1 -1
- package/dist/observe-D-BHMwrO.cjs +54 -0
- package/dist/observe-D-BHMwrO.cjs.map +1 -0
- package/dist/observe-D1pNg1qL.js +54 -0
- package/dist/observe-D1pNg1qL.js.map +1 -0
- package/dist/react.cjs +105 -1863
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +80 -0
- package/dist/react.d.cts.map +1 -0
- package/dist/react.d.ts +52 -46
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +118 -23
- package/dist/react.js.map +1 -1
- package/dist/registry-BPhiH_l9.js +1775 -0
- package/dist/registry-BPhiH_l9.js.map +1 -0
- package/dist/registry-CGqrOtf4.cjs +1810 -0
- package/dist/registry-CGqrOtf4.cjs.map +1 -0
- package/dist/tracing-DAhaKkl0.cjs +93 -0
- package/dist/tracing-DAhaKkl0.cjs.map +1 -0
- package/dist/tracing-Dt1RVuQ9.js +93 -0
- package/dist/tracing-Dt1RVuQ9.js.map +1 -0
- package/package.json +31 -16
- package/dist/chunk-4MTDZBRS.js +0 -57
- package/dist/chunk-4MTDZBRS.js.map +0 -1
- package/dist/chunk-SYCBGBTH.js +0 -1627
- package/dist/chunk-SYCBGBTH.js.map +0 -1
- package/dist/chunk-YRP3I3OD.js +0 -106
- package/dist/chunk-YRP3I3OD.js.map +0 -1
- package/dist/client-BCd2fgmx.d.ts +0 -581
- package/dist/exceptions-NQHZUDYO.js +0 -9
- package/dist/exceptions-NQHZUDYO.js.map +0 -1
- package/dist/tracing-LC3NZND7.js +0 -91
- 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
|