@drivemetadata-ai/sdk 0.1.1-beta.2 → 0.1.1-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/angular/index.cjs +780 -94
- package/dist/angular/index.cjs.map +1 -1
- package/dist/angular/index.d.cts +2 -2
- package/dist/angular/index.d.ts +2 -2
- package/dist/angular/index.js +780 -94
- package/dist/angular/index.js.map +1 -1
- package/dist/browser/index.cjs +781 -95
- package/dist/browser/index.cjs.map +1 -1
- package/dist/browser/index.d.cts +2 -2
- package/dist/browser/index.d.ts +2 -2
- package/dist/browser/index.js +781 -95
- package/dist/browser/index.js.map +1 -1
- package/dist/next/index.cjs +780 -94
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +1 -1
- package/dist/next/index.d.ts +1 -1
- package/dist/next/index.js +780 -94
- package/dist/next/index.js.map +1 -1
- package/dist/node/index.cjs +81 -9
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.js +81 -9
- package/dist/node/index.js.map +1 -1
- package/dist/react/index.cjs +780 -94
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +780 -94
- package/dist/react/index.js.map +1 -1
- package/dist/{types--V8TVIqT.d.cts → types-mgbdL1V7.d.cts} +17 -4
- package/dist/{types--V8TVIqT.d.ts → types-mgbdL1V7.d.ts} +17 -4
- package/docs/architecture.md +109 -0
- package/docs/index.md +1 -0
- package/docs/integration.md +26 -0
- package/docs/npm-browser-sdk.md +4 -0
- package/package.json +3 -2
package/dist/angular/index.js
CHANGED
|
@@ -13,12 +13,62 @@ var __decorateParam = (index, decorator) => (target, key) => decorator(target, k
|
|
|
13
13
|
// src/angular/dmd-analytics.service.ts
|
|
14
14
|
import { Inject, Injectable, Optional } from "@angular/core";
|
|
15
15
|
|
|
16
|
+
// src/browser/core/browser-config.ts
|
|
17
|
+
function setIfDefined(target, key, value) {
|
|
18
|
+
if (value !== void 0) {
|
|
19
|
+
target[key] = value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function normalizeBrowserConfig(input) {
|
|
23
|
+
const normalized = {
|
|
24
|
+
...input,
|
|
25
|
+
clientId: input.clientId ?? "",
|
|
26
|
+
workspaceId: input.workspaceId ?? "",
|
|
27
|
+
appId: input.appId ?? ""
|
|
28
|
+
};
|
|
29
|
+
setIfDefined(normalized, "apiHost", input.apiHost);
|
|
30
|
+
setIfDefined(normalized, "capturePageview", input.capturePageview);
|
|
31
|
+
setIfDefined(normalized, "capturePageleave", input.capturePageleave);
|
|
32
|
+
setIfDefined(normalized, "captureDeadClicks", input.captureDeadClicks);
|
|
33
|
+
setIfDefined(normalized, "crossSubdomainCookie", input.crossSubdomainCookie);
|
|
34
|
+
setIfDefined(normalized, "sessionIdleTimeoutSeconds", input.sessionIdleTimeoutSeconds);
|
|
35
|
+
setIfDefined(normalized, "schemaValidation", input.schemaValidation);
|
|
36
|
+
setIfDefined(normalized, "beforeSend", input.beforeSend);
|
|
37
|
+
setIfDefined(normalized, "persistence", input.disablePersistence === true ? "none" : input.persistence);
|
|
38
|
+
return normalized;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/core/uuid.ts
|
|
42
|
+
function createUuid() {
|
|
43
|
+
const cryptoApi = globalThis.crypto;
|
|
44
|
+
if (typeof cryptoApi?.randomUUID === "function") {
|
|
45
|
+
return cryptoApi.randomUUID();
|
|
46
|
+
}
|
|
47
|
+
const bytes = new Uint8Array(16);
|
|
48
|
+
if (typeof cryptoApi?.getRandomValues === "function") {
|
|
49
|
+
cryptoApi.getRandomValues(bytes);
|
|
50
|
+
} else {
|
|
51
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
52
|
+
bytes[index] = Math.floor(Math.random() * 256);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
bytes[6] = (bytes[6] ?? 0) & 15 | 64;
|
|
56
|
+
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
57
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
58
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
59
|
+
}
|
|
60
|
+
function isUuid(value) {
|
|
61
|
+
return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
62
|
+
}
|
|
63
|
+
function ensureUuid(value) {
|
|
64
|
+
return isUuid(value) ? value : createUuid();
|
|
65
|
+
}
|
|
66
|
+
|
|
16
67
|
// src/core/backend-payload.ts
|
|
17
68
|
function formatUtcTimestamp(value) {
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
return date.toISOString().replace("T", " ").slice(0, 19);
|
|
69
|
+
const date = typeof value === "string" || typeof value === "number" || value instanceof Date ? new Date(value) : /* @__PURE__ */ new Date();
|
|
70
|
+
const safeDate = Number.isNaN(date.getTime()) ? /* @__PURE__ */ new Date() : date;
|
|
71
|
+
return safeDate.toISOString().replace("T", " ").slice(0, 19);
|
|
22
72
|
}
|
|
23
73
|
function cleanObject(value) {
|
|
24
74
|
if (Array.isArray(value)) {
|
|
@@ -49,15 +99,19 @@ function createBackendCollectorPayload(input) {
|
|
|
49
99
|
};
|
|
50
100
|
const metaData = {
|
|
51
101
|
...normalizedEventData,
|
|
52
|
-
requestId: input.requestId,
|
|
102
|
+
requestId: ensureUuid(typeof input.requestId === "string" ? input.requestId : void 0),
|
|
53
103
|
timestamp,
|
|
54
104
|
eventType: input.eventType,
|
|
55
105
|
requestFrom: input.requestFrom ?? "3",
|
|
56
106
|
clientId: input.clientId,
|
|
57
107
|
workspaceId: input.workspaceId,
|
|
58
108
|
token: input.token,
|
|
59
|
-
anonymousId:
|
|
60
|
-
|
|
109
|
+
anonymousId: ensureUuid(
|
|
110
|
+
typeof eventData.anonymousId === "string" ? eventData.anonymousId : typeof input.anonymousId === "string" ? input.anonymousId : void 0
|
|
111
|
+
),
|
|
112
|
+
sessionId: ensureUuid(
|
|
113
|
+
typeof eventData.sessionId === "string" ? eventData.sessionId : typeof input.sessionId === "string" ? input.sessionId : void 0
|
|
114
|
+
),
|
|
61
115
|
ua: input.ua,
|
|
62
116
|
appDetails: { app_id: input.appId },
|
|
63
117
|
page: { ...page2, url: page2.url ?? input.pageUrl },
|
|
@@ -68,12 +122,45 @@ function createBackendCollectorPayload(input) {
|
|
|
68
122
|
return cleanObject(payload);
|
|
69
123
|
}
|
|
70
124
|
|
|
71
|
-
// src/core/
|
|
72
|
-
|
|
73
|
-
|
|
125
|
+
// src/core/backend-schema.ts
|
|
126
|
+
var requiredMetaDataFields = [
|
|
127
|
+
"requestId",
|
|
128
|
+
"timestamp",
|
|
129
|
+
"eventType",
|
|
130
|
+
"requestFrom",
|
|
131
|
+
"clientId",
|
|
132
|
+
"workspaceId",
|
|
133
|
+
"anonymousId",
|
|
134
|
+
"sessionId"
|
|
135
|
+
];
|
|
136
|
+
function isPresent(value) {
|
|
137
|
+
return value !== null && value !== void 0 && value !== "";
|
|
138
|
+
}
|
|
139
|
+
function validateBackendCollectorPayload(payload) {
|
|
140
|
+
const errors = [];
|
|
141
|
+
const metaData = payload.metaData;
|
|
142
|
+
if (!metaData || typeof metaData !== "object" || Array.isArray(metaData)) {
|
|
143
|
+
return {
|
|
144
|
+
ok: false,
|
|
145
|
+
errors: ["metaData is required"]
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const metadataRecord = metaData;
|
|
149
|
+
for (const field of requiredMetaDataFields) {
|
|
150
|
+
if (!isPresent(metadataRecord[field])) {
|
|
151
|
+
errors.push(`metaData.${field} is required`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (isPresent(metadataRecord.eventType) && typeof metadataRecord.eventType !== "string") {
|
|
155
|
+
errors.push("metaData.eventType must be a string");
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
ok: errors.length === 0,
|
|
159
|
+
errors
|
|
160
|
+
};
|
|
74
161
|
}
|
|
75
162
|
|
|
76
|
-
// src/
|
|
163
|
+
// src/core/consent.ts
|
|
77
164
|
var purposes = [
|
|
78
165
|
"analytics",
|
|
79
166
|
"advertising",
|
|
@@ -113,6 +200,335 @@ function canCollectPurpose(consent, purpose) {
|
|
|
113
200
|
return consent[purpose] === "granted";
|
|
114
201
|
}
|
|
115
202
|
|
|
203
|
+
// src/core/event-schema.ts
|
|
204
|
+
var reservedKeys = /* @__PURE__ */ new Set(["messageId", "timestamp", "type", "event", "anonymousId", "userId", "context"]);
|
|
205
|
+
function validateEventEnvelope(envelope, options = {}) {
|
|
206
|
+
if (options.mode === "off") return { ok: true, errors: [] };
|
|
207
|
+
const errors = [];
|
|
208
|
+
if (typeof envelope.type !== "string") errors.push("type must be a string");
|
|
209
|
+
if (envelope.type === "track" && typeof envelope.event !== "string") {
|
|
210
|
+
errors.push("track event must include event name");
|
|
211
|
+
}
|
|
212
|
+
if (typeof envelope.messageId !== "string") errors.push("messageId must be a string");
|
|
213
|
+
if (typeof envelope.timestamp !== "string") errors.push("timestamp must be an ISO string");
|
|
214
|
+
for (const key of Object.keys(envelope.properties ?? {})) {
|
|
215
|
+
if (reservedKeys.has(key)) errors.push(`properties.${key} is reserved`);
|
|
216
|
+
}
|
|
217
|
+
return { ok: errors.length === 0, errors };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/core/environment.ts
|
|
221
|
+
function getBrowserWindow() {
|
|
222
|
+
return typeof window === "undefined" ? void 0 : window;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/core/privacy.ts
|
|
226
|
+
var sensitiveKeys = /* @__PURE__ */ new Set([
|
|
227
|
+
"email",
|
|
228
|
+
"phone",
|
|
229
|
+
"mobile",
|
|
230
|
+
"address",
|
|
231
|
+
"address1",
|
|
232
|
+
"address2",
|
|
233
|
+
"first_name",
|
|
234
|
+
"last_name",
|
|
235
|
+
"name",
|
|
236
|
+
"token",
|
|
237
|
+
"secret",
|
|
238
|
+
"password",
|
|
239
|
+
"session",
|
|
240
|
+
"cookie"
|
|
241
|
+
]);
|
|
242
|
+
function sanitizeValue(value, allow, path = []) {
|
|
243
|
+
if (Array.isArray(value)) {
|
|
244
|
+
return value.map((item) => sanitizeValue(item, allow, path));
|
|
245
|
+
}
|
|
246
|
+
if (value && typeof value === "object") {
|
|
247
|
+
return Object.fromEntries(
|
|
248
|
+
Object.entries(value).filter(([key]) => {
|
|
249
|
+
const lowerKey = key.toLowerCase();
|
|
250
|
+
const lowerPath = path.map((item) => item.toLowerCase());
|
|
251
|
+
const isEcommerceItemName = lowerKey === "name" && lowerPath.includes("ecommerce") && lowerPath.includes("items");
|
|
252
|
+
return isEcommerceItemName || !sensitiveKeys.has(lowerKey) || allow.has(lowerKey);
|
|
253
|
+
}).map(([key, nestedValue]) => [key, sanitizeValue(nestedValue, allow, [...path, key])])
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
return value;
|
|
257
|
+
}
|
|
258
|
+
function sanitizeProperties(input, allowRawKeys = []) {
|
|
259
|
+
const allow = new Set(allowRawKeys.map((key) => key.toLowerCase()));
|
|
260
|
+
return sanitizeValue(input, allow);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/core/attribution.ts
|
|
264
|
+
var dmdUtmKeys = [
|
|
265
|
+
"utm_source",
|
|
266
|
+
"utm_medium",
|
|
267
|
+
"utm_campaign",
|
|
268
|
+
"utm_term",
|
|
269
|
+
"utm_content",
|
|
270
|
+
"utm_id"
|
|
271
|
+
];
|
|
272
|
+
var dmdCampaignKeys = ["campaign_id", "ad_id"];
|
|
273
|
+
var dmdClickIdSources = [
|
|
274
|
+
["gclid", 2],
|
|
275
|
+
["fbclid", 3],
|
|
276
|
+
["ScCid", 1],
|
|
277
|
+
["li_fat_id", 4]
|
|
278
|
+
];
|
|
279
|
+
function cleanAttributionRecord(record) {
|
|
280
|
+
const cleaned = Object.fromEntries(
|
|
281
|
+
Object.entries(record).filter(([, value]) => value !== null && value !== void 0 && value !== "")
|
|
282
|
+
);
|
|
283
|
+
return Object.keys(cleaned).length > 0 ? cleaned : void 0;
|
|
284
|
+
}
|
|
285
|
+
function objectValue(value) {
|
|
286
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
287
|
+
}
|
|
288
|
+
function mergeAttributionRecords(explicitProperties, stored) {
|
|
289
|
+
const attributionData = objectValue(explicitProperties.attributionData);
|
|
290
|
+
const utmParameter = objectValue(explicitProperties.utmParameter);
|
|
291
|
+
return {
|
|
292
|
+
...explicitProperties,
|
|
293
|
+
...stored.attributionData || attributionData ? { attributionData: { ...stored.attributionData ?? {}, ...attributionData ?? {} } } : {},
|
|
294
|
+
...stored.utmParameter || utmParameter ? { utmParameter: { ...stored.utmParameter ?? {}, ...utmParameter ?? {} } } : {}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// src/browser/core/attribution.ts
|
|
299
|
+
function getCurrentUrl() {
|
|
300
|
+
return getBrowserWindow()?.location?.href;
|
|
301
|
+
}
|
|
302
|
+
function getCookie(name) {
|
|
303
|
+
const cookie = getBrowserWindow()?.document?.cookie;
|
|
304
|
+
if (!cookie) return void 0;
|
|
305
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
306
|
+
const match = new RegExp(`(?:^|; )${escapedName}=([^;]*)`).exec(cookie);
|
|
307
|
+
return match ? decodeURIComponent(match[1] ?? "") : void 0;
|
|
308
|
+
}
|
|
309
|
+
function getParams(url) {
|
|
310
|
+
return new URL(url, "https://placeholder.local").searchParams;
|
|
311
|
+
}
|
|
312
|
+
function setIfPresent(persistence, key, value) {
|
|
313
|
+
if (value !== null && value !== "") {
|
|
314
|
+
persistence.setItem(key, value);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function getStoredString(persistence, key) {
|
|
318
|
+
const value = persistence.getItem(key);
|
|
319
|
+
return value === null || value === "" ? void 0 : value;
|
|
320
|
+
}
|
|
321
|
+
function getStoredNumberOrString(persistence, key) {
|
|
322
|
+
const value = getStoredString(persistence, key);
|
|
323
|
+
if (value === void 0) return void 0;
|
|
324
|
+
const parsed = Number.parseInt(value, 10);
|
|
325
|
+
return Number.isNaN(parsed) ? value : parsed;
|
|
326
|
+
}
|
|
327
|
+
function captureAttributionFromUrl(persistence, url = getCurrentUrl()) {
|
|
328
|
+
if (!url) return;
|
|
329
|
+
const params = getParams(url);
|
|
330
|
+
for (const key of dmdUtmKeys) {
|
|
331
|
+
setIfPresent(persistence, key, params.get(key));
|
|
332
|
+
}
|
|
333
|
+
for (const key of dmdCampaignKeys) {
|
|
334
|
+
setIfPresent(persistence, key, params.get(key));
|
|
335
|
+
}
|
|
336
|
+
for (const [param, sdkPubId] of dmdClickIdSources) {
|
|
337
|
+
const value = params.get(param);
|
|
338
|
+
if (value) {
|
|
339
|
+
persistence.setItem("unique_id", value);
|
|
340
|
+
persistence.setItem("sdk_pub_id", String(sdkPubId));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function getStoredAttributionData(persistence) {
|
|
345
|
+
return cleanAttributionRecord({
|
|
346
|
+
unique_id: getStoredString(persistence, "unique_id"),
|
|
347
|
+
sdk_pub_id: getStoredNumberOrString(persistence, "sdk_pub_id"),
|
|
348
|
+
fbc: getStoredString(persistence, "fbc") ?? getCookie("_fbc"),
|
|
349
|
+
fbp: getStoredString(persistence, "fbp") ?? getCookie("_fbp"),
|
|
350
|
+
campaign_id: getStoredString(persistence, "campaign_id"),
|
|
351
|
+
ad_id: getStoredString(persistence, "ad_id")
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
function getStoredUtmParameter(persistence) {
|
|
355
|
+
return cleanAttributionRecord(Object.fromEntries(dmdUtmKeys.map((key) => [key, getStoredString(persistence, key)])));
|
|
356
|
+
}
|
|
357
|
+
function mergeStoredAttribution(properties, persistence) {
|
|
358
|
+
const stored = {};
|
|
359
|
+
const attributionData = getStoredAttributionData(persistence);
|
|
360
|
+
const utmParameter = getStoredUtmParameter(persistence);
|
|
361
|
+
if (attributionData !== void 0) stored.attributionData = attributionData;
|
|
362
|
+
if (utmParameter !== void 0) stored.utmParameter = utmParameter;
|
|
363
|
+
return mergeAttributionRecords(properties, stored);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/browser/core/autocapture.ts
|
|
367
|
+
function installAutocapture(config) {
|
|
368
|
+
const trackedPageUrls = /* @__PURE__ */ new Set();
|
|
369
|
+
const timers = /* @__PURE__ */ new Set();
|
|
370
|
+
const pageviewDelayMs = config.pageviewDelayMs ?? 500;
|
|
371
|
+
const history = config.browserWindow.history;
|
|
372
|
+
const originalPushState = history?.pushState;
|
|
373
|
+
const originalReplaceState = history?.replaceState;
|
|
374
|
+
function clearTimer(timer) {
|
|
375
|
+
timers.delete(timer);
|
|
376
|
+
clearTimeout(timer);
|
|
377
|
+
}
|
|
378
|
+
function canonicalUrl() {
|
|
379
|
+
return config.browserWindow.location.href;
|
|
380
|
+
}
|
|
381
|
+
function trackCurrentPageview() {
|
|
382
|
+
if (!config.capturePageview) return;
|
|
383
|
+
const url = canonicalUrl();
|
|
384
|
+
if (trackedPageUrls.has(url)) return;
|
|
385
|
+
trackedPageUrls.add(url);
|
|
386
|
+
config.onPageView();
|
|
387
|
+
}
|
|
388
|
+
function schedulePageview(delayMs) {
|
|
389
|
+
if (!config.capturePageview) return;
|
|
390
|
+
const timer = setTimeout(() => {
|
|
391
|
+
timers.delete(timer);
|
|
392
|
+
trackCurrentPageview();
|
|
393
|
+
}, delayMs);
|
|
394
|
+
timers.add(timer);
|
|
395
|
+
}
|
|
396
|
+
function handleRouteChange() {
|
|
397
|
+
config.onRouteChange?.();
|
|
398
|
+
trackCurrentPageview();
|
|
399
|
+
}
|
|
400
|
+
function wrapHistoryMethod(method) {
|
|
401
|
+
if (!history) return;
|
|
402
|
+
const original = history[method];
|
|
403
|
+
if (typeof original !== "function") return;
|
|
404
|
+
history[method] = function wrappedHistoryMethod(...args) {
|
|
405
|
+
const result = original.apply(this, args);
|
|
406
|
+
const timer = setTimeout(() => {
|
|
407
|
+
timers.delete(timer);
|
|
408
|
+
handleRouteChange();
|
|
409
|
+
}, 0);
|
|
410
|
+
timers.add(timer);
|
|
411
|
+
return result;
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
function handlePopOrHashChange() {
|
|
415
|
+
handleRouteChange();
|
|
416
|
+
}
|
|
417
|
+
function handlePageLeave() {
|
|
418
|
+
if (config.capturePageleave) {
|
|
419
|
+
config.onPageLeave();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
schedulePageview(pageviewDelayMs);
|
|
423
|
+
wrapHistoryMethod("pushState");
|
|
424
|
+
wrapHistoryMethod("replaceState");
|
|
425
|
+
const canListen = typeof config.browserWindow.addEventListener === "function" && typeof config.browserWindow.removeEventListener === "function";
|
|
426
|
+
if (canListen) {
|
|
427
|
+
config.browserWindow.addEventListener("popstate", handlePopOrHashChange);
|
|
428
|
+
config.browserWindow.addEventListener("hashchange", handlePopOrHashChange);
|
|
429
|
+
config.browserWindow.addEventListener("beforeunload", handlePageLeave);
|
|
430
|
+
}
|
|
431
|
+
return {
|
|
432
|
+
cleanup() {
|
|
433
|
+
for (const timer of Array.from(timers)) {
|
|
434
|
+
clearTimer(timer);
|
|
435
|
+
}
|
|
436
|
+
if (history && originalPushState) history.pushState = originalPushState;
|
|
437
|
+
if (history && originalReplaceState) history.replaceState = originalReplaceState;
|
|
438
|
+
if (canListen) {
|
|
439
|
+
config.browserWindow.removeEventListener("popstate", handlePopOrHashChange);
|
|
440
|
+
config.browserWindow.removeEventListener("hashchange", handlePopOrHashChange);
|
|
441
|
+
config.browserWindow.removeEventListener("beforeunload", handlePageLeave);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/core/payload-size.ts
|
|
448
|
+
var preserveStringKeys = /* @__PURE__ */ new Set([
|
|
449
|
+
"requestId",
|
|
450
|
+
"eventType",
|
|
451
|
+
"requestFrom",
|
|
452
|
+
"clientId",
|
|
453
|
+
"workspaceId",
|
|
454
|
+
"anonymousId",
|
|
455
|
+
"sessionId",
|
|
456
|
+
"token"
|
|
457
|
+
]);
|
|
458
|
+
function payloadByteLength(payload) {
|
|
459
|
+
const serialized = JSON.stringify(payload);
|
|
460
|
+
if (typeof TextEncoder !== "undefined") {
|
|
461
|
+
return new TextEncoder().encode(serialized).length;
|
|
462
|
+
}
|
|
463
|
+
return serialized.length;
|
|
464
|
+
}
|
|
465
|
+
function clonePayload(payload) {
|
|
466
|
+
try {
|
|
467
|
+
return JSON.parse(JSON.stringify(payload));
|
|
468
|
+
} catch {
|
|
469
|
+
return void 0;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function truncateValue(value, truncateStringLength, key) {
|
|
473
|
+
if (typeof value === "string") {
|
|
474
|
+
if (key && preserveStringKeys.has(key)) return value;
|
|
475
|
+
if (value.length <= truncateStringLength) return value;
|
|
476
|
+
return `${value.slice(0, truncateStringLength)}...[TRUNCATED]`;
|
|
477
|
+
}
|
|
478
|
+
if (Array.isArray(value)) {
|
|
479
|
+
return value.map((item) => truncateValue(item, truncateStringLength));
|
|
480
|
+
}
|
|
481
|
+
if (value && typeof value === "object") {
|
|
482
|
+
return Object.fromEntries(
|
|
483
|
+
Object.entries(value).map(([childKey, childValue]) => [
|
|
484
|
+
childKey,
|
|
485
|
+
truncateValue(childValue, truncateStringLength, childKey)
|
|
486
|
+
])
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
return value;
|
|
490
|
+
}
|
|
491
|
+
function markPayloadTruncated(payload) {
|
|
492
|
+
if (payload.metaData && typeof payload.metaData === "object" && !Array.isArray(payload.metaData)) {
|
|
493
|
+
return {
|
|
494
|
+
...payload,
|
|
495
|
+
metaData: {
|
|
496
|
+
...payload.metaData,
|
|
497
|
+
payloadTruncated: true
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
...payload,
|
|
503
|
+
payloadTruncated: true
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
function truncatePayload(payload, truncateStringLength) {
|
|
507
|
+
const cloned = clonePayload(payload);
|
|
508
|
+
if (!cloned) return void 0;
|
|
509
|
+
return markPayloadTruncated(truncateValue(cloned, truncateStringLength));
|
|
510
|
+
}
|
|
511
|
+
function getPayloadMessageId(payload) {
|
|
512
|
+
const value = payload.metaData?.requestId ?? payload.messageId;
|
|
513
|
+
return value === void 0 ? void 0 : String(value);
|
|
514
|
+
}
|
|
515
|
+
function preparePayloadForSizePolicy(payload, options = {}) {
|
|
516
|
+
const maxPayloadBytes = options.maxPayloadBytes ?? 64e3;
|
|
517
|
+
const payloadSizePolicy = options.payloadSizePolicy ?? "drop";
|
|
518
|
+
const payloadTruncateStringLength = options.payloadTruncateStringLength ?? 1024;
|
|
519
|
+
if (payloadByteLength(payload) <= maxPayloadBytes) {
|
|
520
|
+
return { ok: true, payload, truncated: false };
|
|
521
|
+
}
|
|
522
|
+
if (payloadSizePolicy === "truncate") {
|
|
523
|
+
const truncatedPayload = truncatePayload(payload, payloadTruncateStringLength);
|
|
524
|
+
if (truncatedPayload && payloadByteLength(truncatedPayload) <= maxPayloadBytes) {
|
|
525
|
+
return { ok: true, payload: truncatedPayload, truncated: true };
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
const messageId = getPayloadMessageId(payload);
|
|
529
|
+
return messageId === void 0 ? { ok: false, reason: "payload_too_large" } : { ok: false, reason: "payload_too_large", messageId };
|
|
530
|
+
}
|
|
531
|
+
|
|
116
532
|
// src/browser/core/delivery.ts
|
|
117
533
|
function createId(prefix) {
|
|
118
534
|
return `${prefix}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
|
|
@@ -144,7 +560,6 @@ function createDeliveryManager(config) {
|
|
|
144
560
|
const lockTtlMs = config.lockTtlMs ?? 5e3;
|
|
145
561
|
const tabId = config.tabId ?? createId("tab");
|
|
146
562
|
const batchSize = config.batchSize ?? 25;
|
|
147
|
-
const maxPayloadBytes = config.maxPayloadBytes ?? 64e3;
|
|
148
563
|
function recordDrop(event) {
|
|
149
564
|
diagnostics.dropped.push(event);
|
|
150
565
|
config.onDrop?.(event);
|
|
@@ -153,12 +568,22 @@ function createDeliveryManager(config) {
|
|
|
153
568
|
diagnostics.lastError = error.message;
|
|
154
569
|
config.onError?.(error);
|
|
155
570
|
}
|
|
156
|
-
function
|
|
157
|
-
const
|
|
158
|
-
if (
|
|
159
|
-
|
|
571
|
+
function preparePayloadForSend(payload) {
|
|
572
|
+
const sizePolicyOptions = {};
|
|
573
|
+
if (config.maxPayloadBytes !== void 0) sizePolicyOptions.maxPayloadBytes = config.maxPayloadBytes;
|
|
574
|
+
if (config.payloadSizePolicy !== void 0) sizePolicyOptions.payloadSizePolicy = config.payloadSizePolicy;
|
|
575
|
+
if (config.payloadTruncateStringLength !== void 0) {
|
|
576
|
+
sizePolicyOptions.payloadTruncateStringLength = config.payloadTruncateStringLength;
|
|
160
577
|
}
|
|
161
|
-
|
|
578
|
+
const prepared = preparePayloadForSizePolicy(payload, sizePolicyOptions);
|
|
579
|
+
if (prepared.ok) return prepared.payload;
|
|
580
|
+
const diagnostic = {
|
|
581
|
+
reason: prepared.reason,
|
|
582
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
583
|
+
};
|
|
584
|
+
if (prepared.messageId !== void 0) diagnostic.messageId = prepared.messageId;
|
|
585
|
+
recordDrop(diagnostic);
|
|
586
|
+
return void 0;
|
|
162
587
|
}
|
|
163
588
|
function recordStorageUnavailable() {
|
|
164
589
|
recordDrop({
|
|
@@ -248,7 +673,19 @@ function createDeliveryManager(config) {
|
|
|
248
673
|
idempotencyKey: String(payload.idempotencyKey ?? createIdempotencyKey(payload, messageId))
|
|
249
674
|
};
|
|
250
675
|
}
|
|
676
|
+
function tryBeacon(body) {
|
|
677
|
+
if (!config.useBeacon) return false;
|
|
678
|
+
const sendBeacon = globalThis.navigator?.sendBeacon;
|
|
679
|
+
if (typeof sendBeacon !== "function") return false;
|
|
680
|
+
try {
|
|
681
|
+
const blob = new Blob([JSON.stringify(body)], { type: "application/json" });
|
|
682
|
+
return sendBeacon.call(globalThis.navigator, config.endpoint, blob);
|
|
683
|
+
} catch {
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
251
687
|
async function deliver(body) {
|
|
688
|
+
if (tryBeacon(body)) return;
|
|
252
689
|
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
253
690
|
if (typeof fetchImpl !== "function") {
|
|
254
691
|
throw new Error("fetch_unavailable");
|
|
@@ -265,26 +702,22 @@ function createDeliveryManager(config) {
|
|
|
265
702
|
return {
|
|
266
703
|
async send(payload) {
|
|
267
704
|
const body = withEnvelope(payload);
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
messageId: String(body.metaData?.requestId ?? body.messageId),
|
|
271
|
-
reason: "payload_too_large",
|
|
272
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
273
|
-
});
|
|
705
|
+
const preparedBody = preparePayloadForSend(body);
|
|
706
|
+
if (!preparedBody) {
|
|
274
707
|
return;
|
|
275
708
|
}
|
|
276
709
|
diagnostics.inFlight += 1;
|
|
277
710
|
try {
|
|
278
|
-
await deliver(
|
|
711
|
+
await deliver(preparedBody);
|
|
279
712
|
} catch (error) {
|
|
280
713
|
const deliveryError = error instanceof Error ? error : new Error(String(error));
|
|
281
714
|
recordError(deliveryError);
|
|
282
715
|
enqueue({
|
|
283
|
-
messageId: String(body
|
|
716
|
+
messageId: String(getPayloadMessageId(body)),
|
|
284
717
|
savedAt: Date.now(),
|
|
285
718
|
attempts: 1,
|
|
286
719
|
lastError: deliveryError.message,
|
|
287
|
-
payload:
|
|
720
|
+
payload: preparedBody
|
|
288
721
|
});
|
|
289
722
|
} finally {
|
|
290
723
|
diagnostics.inFlight -= 1;
|
|
@@ -397,60 +830,268 @@ function createDeliveryManager(config) {
|
|
|
397
830
|
};
|
|
398
831
|
}
|
|
399
832
|
|
|
400
|
-
// src/browser/core/
|
|
401
|
-
var
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
"name",
|
|
411
|
-
"token",
|
|
412
|
-
"secret",
|
|
413
|
-
"password",
|
|
414
|
-
"session",
|
|
415
|
-
"cookie"
|
|
416
|
-
]);
|
|
417
|
-
function sanitizeValue(value, allow) {
|
|
418
|
-
if (Array.isArray(value)) {
|
|
419
|
-
return value.map((item) => sanitizeValue(item, allow));
|
|
833
|
+
// src/browser/core/identity.ts
|
|
834
|
+
var ANONYMOUS_ID_STORAGE_KEY = "dmd_anonymous_id";
|
|
835
|
+
var LEGACY_ANONYMOUS_ID_STORAGE_KEY = "anonymousId";
|
|
836
|
+
function getUrlParam(name) {
|
|
837
|
+
const href = getBrowserWindow()?.location?.href;
|
|
838
|
+
if (!href) return null;
|
|
839
|
+
try {
|
|
840
|
+
return new URL(href).searchParams.get(name);
|
|
841
|
+
} catch {
|
|
842
|
+
return null;
|
|
420
843
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
844
|
+
}
|
|
845
|
+
function resolveAnonymousId(persistence) {
|
|
846
|
+
const urlAnonymousId = getUrlParam("aid");
|
|
847
|
+
if (isUuid(urlAnonymousId)) {
|
|
848
|
+
persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, urlAnonymousId);
|
|
849
|
+
return urlAnonymousId;
|
|
850
|
+
}
|
|
851
|
+
const storedAnonymousId = persistence.getItem(ANONYMOUS_ID_STORAGE_KEY);
|
|
852
|
+
if (isUuid(storedAnonymousId)) {
|
|
853
|
+
return storedAnonymousId;
|
|
854
|
+
}
|
|
855
|
+
const legacyAnonymousId = persistence.getItem(LEGACY_ANONYMOUS_ID_STORAGE_KEY);
|
|
856
|
+
if (isUuid(legacyAnonymousId)) {
|
|
857
|
+
persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, legacyAnonymousId);
|
|
858
|
+
return legacyAnonymousId;
|
|
859
|
+
}
|
|
860
|
+
const anonymousId = createUuid();
|
|
861
|
+
persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, anonymousId);
|
|
862
|
+
return anonymousId;
|
|
863
|
+
}
|
|
864
|
+
function resetAnonymousId(persistence) {
|
|
865
|
+
const anonymousId = createUuid();
|
|
866
|
+
persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, anonymousId);
|
|
867
|
+
return anonymousId;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// src/browser/core/persistence.ts
|
|
871
|
+
function createPersistenceHealth(requested) {
|
|
872
|
+
return {
|
|
873
|
+
requested,
|
|
874
|
+
cookieFallbackUsed: false,
|
|
875
|
+
memoryFallbackUsed: requested === "memory",
|
|
876
|
+
failures: []
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
function recordFailure(health, backend, operation, error) {
|
|
880
|
+
health.failures.push({
|
|
881
|
+
backend,
|
|
882
|
+
operation,
|
|
883
|
+
message: error instanceof Error ? error.message : String(error)
|
|
884
|
+
});
|
|
885
|
+
if (health.failures.length > 25) {
|
|
886
|
+
health.failures.shift();
|
|
425
887
|
}
|
|
426
|
-
return value;
|
|
427
888
|
}
|
|
428
|
-
function
|
|
429
|
-
const
|
|
430
|
-
return
|
|
889
|
+
function createMemoryPersistence(health = createPersistenceHealth("memory")) {
|
|
890
|
+
const memory = {};
|
|
891
|
+
return {
|
|
892
|
+
getItem(key) {
|
|
893
|
+
return Object.prototype.hasOwnProperty.call(memory, key) ? memory[key] ?? null : null;
|
|
894
|
+
},
|
|
895
|
+
setItem(key, value) {
|
|
896
|
+
memory[key] = value;
|
|
897
|
+
return true;
|
|
898
|
+
},
|
|
899
|
+
removeItem(key) {
|
|
900
|
+
delete memory[key];
|
|
901
|
+
},
|
|
902
|
+
getHealth() {
|
|
903
|
+
return {
|
|
904
|
+
...health,
|
|
905
|
+
failures: health.failures.map((failure) => ({ ...failure }))
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
function getCookie2(name) {
|
|
911
|
+
const browserWindow = getBrowserWindow();
|
|
912
|
+
const cookie = browserWindow?.document?.cookie;
|
|
913
|
+
if (!cookie) return null;
|
|
914
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
915
|
+
const match = new RegExp(`(?:^|; )${escapedName}=([^;]*)`).exec(cookie);
|
|
916
|
+
return match ? decodeURIComponent(match[1] ?? "") : null;
|
|
917
|
+
}
|
|
918
|
+
function setCookie(name, value) {
|
|
919
|
+
const browserWindow = getBrowserWindow();
|
|
920
|
+
if (!browserWindow?.document) return false;
|
|
921
|
+
try {
|
|
922
|
+
const expires = new Date(Date.now() + 365 * 24 * 60 * 60 * 1e3).toUTCString();
|
|
923
|
+
const secure = browserWindow.location?.protocol === "https:" ? "; Secure" : "";
|
|
924
|
+
browserWindow.document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/${secure}; SameSite=Lax`;
|
|
925
|
+
return true;
|
|
926
|
+
} catch {
|
|
927
|
+
return false;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
function removeCookie(name) {
|
|
931
|
+
const browserWindow = getBrowserWindow();
|
|
932
|
+
if (!browserWindow?.document) return;
|
|
933
|
+
browserWindow.document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
|
|
934
|
+
}
|
|
935
|
+
function getLocalStorage() {
|
|
936
|
+
const browserWindow = getBrowserWindow();
|
|
937
|
+
try {
|
|
938
|
+
return browserWindow?.localStorage;
|
|
939
|
+
} catch {
|
|
940
|
+
return void 0;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
function getSessionStorage() {
|
|
944
|
+
const browserWindow = getBrowserWindow();
|
|
945
|
+
try {
|
|
946
|
+
return browserWindow?.sessionStorage;
|
|
947
|
+
} catch {
|
|
948
|
+
return void 0;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
function createBrowserPersistence(mode = "localStorage+cookie") {
|
|
952
|
+
const health = createPersistenceHealth(mode);
|
|
953
|
+
const memory = createMemoryPersistence(health);
|
|
954
|
+
if (mode === "none") {
|
|
955
|
+
return {
|
|
956
|
+
getItem() {
|
|
957
|
+
return null;
|
|
958
|
+
},
|
|
959
|
+
setItem() {
|
|
960
|
+
return false;
|
|
961
|
+
},
|
|
962
|
+
removeItem() {
|
|
963
|
+
},
|
|
964
|
+
getHealth() {
|
|
965
|
+
return {
|
|
966
|
+
...health,
|
|
967
|
+
failures: health.failures.map((failure) => ({ ...failure }))
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
if (mode === "memory") return memory;
|
|
973
|
+
const primary = mode === "sessionStorage" ? getSessionStorage() : getLocalStorage();
|
|
974
|
+
const useCookie = mode === "cookie" || mode === "localStorage+cookie" || !primary;
|
|
975
|
+
return {
|
|
976
|
+
getItem(key) {
|
|
977
|
+
try {
|
|
978
|
+
const stored = primary?.getItem(key);
|
|
979
|
+
if (stored) return stored;
|
|
980
|
+
} catch (error) {
|
|
981
|
+
recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "get", error);
|
|
982
|
+
}
|
|
983
|
+
if (useCookie) {
|
|
984
|
+
const cookie = getCookie2(key);
|
|
985
|
+
if (cookie) {
|
|
986
|
+
health.cookieFallbackUsed = true;
|
|
987
|
+
return cookie;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
const value = memory.getItem(key);
|
|
991
|
+
if (value !== null) health.memoryFallbackUsed = true;
|
|
992
|
+
return value;
|
|
993
|
+
},
|
|
994
|
+
setItem(key, value) {
|
|
995
|
+
let wrote = false;
|
|
996
|
+
try {
|
|
997
|
+
primary?.setItem(key, value);
|
|
998
|
+
wrote = primary !== void 0;
|
|
999
|
+
} catch (error) {
|
|
1000
|
+
recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "set", error);
|
|
1001
|
+
wrote = false;
|
|
1002
|
+
}
|
|
1003
|
+
if (useCookie) {
|
|
1004
|
+
const cookieWrote = setCookie(key, value);
|
|
1005
|
+
if (cookieWrote) health.cookieFallbackUsed = true;
|
|
1006
|
+
wrote = cookieWrote || wrote;
|
|
1007
|
+
}
|
|
1008
|
+
if (!wrote) {
|
|
1009
|
+
health.memoryFallbackUsed = true;
|
|
1010
|
+
return memory.setItem(key, value);
|
|
1011
|
+
}
|
|
1012
|
+
if (mode === "localStorage+cookie") {
|
|
1013
|
+
memory.setItem(key, value);
|
|
1014
|
+
}
|
|
1015
|
+
return true;
|
|
1016
|
+
},
|
|
1017
|
+
removeItem(key) {
|
|
1018
|
+
try {
|
|
1019
|
+
primary?.removeItem(key);
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "remove", error);
|
|
1022
|
+
}
|
|
1023
|
+
if (useCookie) removeCookie(key);
|
|
1024
|
+
memory.removeItem(key);
|
|
1025
|
+
},
|
|
1026
|
+
getHealth() {
|
|
1027
|
+
return {
|
|
1028
|
+
...health,
|
|
1029
|
+
failures: health.failures.map((failure) => ({ ...failure }))
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
431
1033
|
}
|
|
432
1034
|
|
|
433
|
-
// src/browser/core/
|
|
434
|
-
var
|
|
435
|
-
function
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
1035
|
+
// src/browser/core/session.ts
|
|
1036
|
+
var SESSION_STORAGE_KEY = "sessionData";
|
|
1037
|
+
function getUrlParam2(name) {
|
|
1038
|
+
const href = getBrowserWindow()?.location?.href;
|
|
1039
|
+
if (!href) return null;
|
|
1040
|
+
try {
|
|
1041
|
+
return new URL(href).searchParams.get(name);
|
|
1042
|
+
} catch {
|
|
1043
|
+
return null;
|
|
441
1044
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
1045
|
+
}
|
|
1046
|
+
function readStoredSession(persistence) {
|
|
1047
|
+
const rawSession = persistence.getItem(SESSION_STORAGE_KEY);
|
|
1048
|
+
if (!rawSession) return void 0;
|
|
1049
|
+
try {
|
|
1050
|
+
const parsed = JSON.parse(rawSession);
|
|
1051
|
+
if (isUuid(parsed.sessionId) && typeof parsed.timestamp === "number") {
|
|
1052
|
+
return {
|
|
1053
|
+
sessionId: parsed.sessionId,
|
|
1054
|
+
timestamp: parsed.timestamp
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
} catch {
|
|
1058
|
+
persistence.removeItem(SESSION_STORAGE_KEY);
|
|
446
1059
|
}
|
|
447
|
-
return
|
|
1060
|
+
return void 0;
|
|
1061
|
+
}
|
|
1062
|
+
function writeStoredSession(persistence, sessionId, timestamp) {
|
|
1063
|
+
persistence.setItem(SESSION_STORAGE_KEY, JSON.stringify({ sessionId, timestamp }));
|
|
1064
|
+
}
|
|
1065
|
+
function createSessionManager(persistence, idleTimeoutSeconds = 30 * 60) {
|
|
1066
|
+
function resolveSessionId() {
|
|
1067
|
+
const now = Date.now();
|
|
1068
|
+
const urlSessionId = getUrlParam2("sid") ?? getUrlParam2("session_id");
|
|
1069
|
+
if (isUuid(urlSessionId)) {
|
|
1070
|
+
writeStoredSession(persistence, urlSessionId, now);
|
|
1071
|
+
return urlSessionId;
|
|
1072
|
+
}
|
|
1073
|
+
const storedSession = readStoredSession(persistence);
|
|
1074
|
+
if (storedSession && now - storedSession.timestamp <= idleTimeoutSeconds * 1e3) {
|
|
1075
|
+
writeStoredSession(persistence, storedSession.sessionId, now);
|
|
1076
|
+
return storedSession.sessionId;
|
|
1077
|
+
}
|
|
1078
|
+
const sessionId = createUuid();
|
|
1079
|
+
writeStoredSession(persistence, sessionId, now);
|
|
1080
|
+
return sessionId;
|
|
1081
|
+
}
|
|
1082
|
+
return {
|
|
1083
|
+
getSessionId() {
|
|
1084
|
+
return resolveSessionId();
|
|
1085
|
+
},
|
|
1086
|
+
reset() {
|
|
1087
|
+
const sessionId = createUuid();
|
|
1088
|
+
writeStoredSession(persistence, sessionId, Date.now());
|
|
1089
|
+
return sessionId;
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
448
1092
|
}
|
|
449
1093
|
|
|
450
1094
|
// src/browser/core/DriveMetaDataSDK.ts
|
|
451
|
-
function createId2(prefix) {
|
|
452
|
-
return `${prefix}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
|
|
453
|
-
}
|
|
454
1095
|
function endpointFromConfig(config) {
|
|
455
1096
|
const host = config.apiHost ?? "https://sdk.drivemetadata.com/v2";
|
|
456
1097
|
return `${host.replace(/\/$/, "")}/data-collector`;
|
|
@@ -461,14 +1102,6 @@ function requireConfigString(value, field) {
|
|
|
461
1102
|
}
|
|
462
1103
|
return value;
|
|
463
1104
|
}
|
|
464
|
-
function getBrowserStorage() {
|
|
465
|
-
const browserWindow = getBrowserWindow();
|
|
466
|
-
try {
|
|
467
|
-
return browserWindow?.localStorage;
|
|
468
|
-
} catch {
|
|
469
|
-
return void 0;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
1105
|
var DriveMetaDataSDK = class {
|
|
473
1106
|
constructor(config) {
|
|
474
1107
|
this.initialized = true;
|
|
@@ -481,30 +1114,35 @@ var DriveMetaDataSDK = class {
|
|
|
481
1114
|
requireConfigString(config.writeKey || config.token, "writeKey or token");
|
|
482
1115
|
this.config = config;
|
|
483
1116
|
this.endpoint = endpointFromConfig(config);
|
|
484
|
-
|
|
1117
|
+
this.persistence = createBrowserPersistence(config.persistence);
|
|
1118
|
+
this.session = createSessionManager(this.persistence, config.sessionIdleTimeoutSeconds);
|
|
485
1119
|
const deliveryConfig = {
|
|
486
|
-
endpoint: this.endpoint
|
|
1120
|
+
endpoint: this.endpoint,
|
|
1121
|
+
storage: this.persistence
|
|
487
1122
|
};
|
|
488
1123
|
if (config.delivery?.maxQueueSize !== void 0) deliveryConfig.maxQueueSize = config.delivery.maxQueueSize;
|
|
489
1124
|
if (config.delivery?.queueTtlMs !== void 0) deliveryConfig.queueTtlMs = config.delivery.queueTtlMs;
|
|
490
1125
|
if (config.delivery?.retryDelayMs !== void 0) deliveryConfig.retryDelayMs = config.delivery.retryDelayMs;
|
|
491
1126
|
if (config.delivery?.maxRetryDelayMs !== void 0) deliveryConfig.maxRetryDelayMs = config.delivery.maxRetryDelayMs;
|
|
492
1127
|
if (config.delivery?.maxPayloadBytes !== void 0) deliveryConfig.maxPayloadBytes = config.delivery.maxPayloadBytes;
|
|
1128
|
+
if (config.delivery?.payloadSizePolicy !== void 0) deliveryConfig.payloadSizePolicy = config.delivery.payloadSizePolicy;
|
|
1129
|
+
if (config.delivery?.payloadTruncateStringLength !== void 0) {
|
|
1130
|
+
deliveryConfig.payloadTruncateStringLength = config.delivery.payloadTruncateStringLength;
|
|
1131
|
+
}
|
|
1132
|
+
if (config.delivery?.useBeacon !== void 0) deliveryConfig.useBeacon = config.delivery.useBeacon;
|
|
493
1133
|
if (config.delivery?.batchSize !== void 0) deliveryConfig.batchSize = config.delivery.batchSize;
|
|
494
1134
|
if (config.onDrop !== void 0) deliveryConfig.onDrop = config.onDrop;
|
|
495
1135
|
if (config.onError !== void 0) deliveryConfig.onError = config.onError;
|
|
496
|
-
this.delivery = createDeliveryManager(
|
|
497
|
-
...deliveryConfig,
|
|
498
|
-
storage
|
|
499
|
-
} : deliveryConfig);
|
|
1136
|
+
this.delivery = createDeliveryManager(deliveryConfig);
|
|
500
1137
|
this.initialRetryDelayMs = config.delivery?.retryDelayMs ?? 1e3;
|
|
501
1138
|
this.retryDelayMs = this.initialRetryDelayMs;
|
|
502
1139
|
this.maxRetryDelayMs = config.delivery?.maxRetryDelayMs ?? 3e4;
|
|
503
1140
|
this.writeKey = config.writeKey || config.token || "";
|
|
504
|
-
this.identity = { anonymousId:
|
|
505
|
-
this.
|
|
1141
|
+
this.identity = { anonymousId: resolveAnonymousId(this.persistence) };
|
|
1142
|
+
captureAttributionFromUrl(this.persistence);
|
|
506
1143
|
this.consentState = normalizeConsent(config.gdprConsent ?? config.consent);
|
|
507
1144
|
this.gdprConsent = this.consentState.analytics;
|
|
1145
|
+
this.installAutocapture();
|
|
508
1146
|
if (!config.delivery?.disableLifecycleFlush) {
|
|
509
1147
|
this.installLifecycleFlush();
|
|
510
1148
|
}
|
|
@@ -544,7 +1182,8 @@ var DriveMetaDataSDK = class {
|
|
|
544
1182
|
}
|
|
545
1183
|
}
|
|
546
1184
|
reset() {
|
|
547
|
-
this.identity = { anonymousId:
|
|
1185
|
+
this.identity = { anonymousId: resetAnonymousId(this.persistence) };
|
|
1186
|
+
this.session.reset();
|
|
548
1187
|
this.queue = [];
|
|
549
1188
|
this.offline = false;
|
|
550
1189
|
if (this.retryTimer !== void 0) {
|
|
@@ -553,8 +1192,20 @@ var DriveMetaDataSDK = class {
|
|
|
553
1192
|
}
|
|
554
1193
|
this.lifecycleCleanup?.();
|
|
555
1194
|
this.lifecycleCleanup = void 0;
|
|
1195
|
+
this.autocaptureCleanup?.();
|
|
1196
|
+
this.autocaptureCleanup = void 0;
|
|
556
1197
|
this.delivery.clearQueue("manual_clear");
|
|
557
1198
|
}
|
|
1199
|
+
disposeForTests() {
|
|
1200
|
+
if (this.retryTimer !== void 0) {
|
|
1201
|
+
clearTimeout(this.retryTimer);
|
|
1202
|
+
this.retryTimer = void 0;
|
|
1203
|
+
}
|
|
1204
|
+
this.lifecycleCleanup?.();
|
|
1205
|
+
this.lifecycleCleanup = void 0;
|
|
1206
|
+
this.autocaptureCleanup?.();
|
|
1207
|
+
this.autocaptureCleanup = void 0;
|
|
1208
|
+
}
|
|
558
1209
|
setConsent(consent) {
|
|
559
1210
|
this.consentState = typeof consent === "object" ? mergeConsent(this.consentState, consent) : normalizeConsent(consent);
|
|
560
1211
|
this.gdprConsent = this.consentState.analytics;
|
|
@@ -569,6 +1220,7 @@ var DriveMetaDataSDK = class {
|
|
|
569
1220
|
initialized: this.initialized,
|
|
570
1221
|
consent: this.gdprConsent,
|
|
571
1222
|
consentPurposes: this.consentState,
|
|
1223
|
+
persistence: this.persistence.getHealth(),
|
|
572
1224
|
queueSize: deliveryDiagnostics.queued,
|
|
573
1225
|
offline: this.offline || deliveryDiagnostics.queued > 0,
|
|
574
1226
|
droppedEvents: this.droppedEvents + deliveryDiagnostics.dropped.length,
|
|
@@ -583,7 +1235,9 @@ var DriveMetaDataSDK = class {
|
|
|
583
1235
|
void this.delivery.send(payload).then(() => {
|
|
584
1236
|
const diagnostics = this.delivery.getDiagnostics();
|
|
585
1237
|
this.offline = diagnostics.queued > 0;
|
|
586
|
-
|
|
1238
|
+
if (diagnostics.lastError !== void 0) {
|
|
1239
|
+
this.lastError = diagnostics.lastError;
|
|
1240
|
+
}
|
|
587
1241
|
if (diagnostics.queued > 0) {
|
|
588
1242
|
this.scheduleRetryFlush();
|
|
589
1243
|
}
|
|
@@ -615,6 +1269,28 @@ var DriveMetaDataSDK = class {
|
|
|
615
1269
|
}
|
|
616
1270
|
};
|
|
617
1271
|
}
|
|
1272
|
+
installAutocapture() {
|
|
1273
|
+
const browserWindow = getBrowserWindow();
|
|
1274
|
+
if (!browserWindow) return;
|
|
1275
|
+
if (this.config.autocapture === false) return;
|
|
1276
|
+
const capturePageview = this.config.capturePageview !== false;
|
|
1277
|
+
const capturePageleave = this.config.capturePageleave !== false;
|
|
1278
|
+
const controller = installAutocapture({
|
|
1279
|
+
browserWindow,
|
|
1280
|
+
capturePageview,
|
|
1281
|
+
capturePageleave,
|
|
1282
|
+
onPageView: () => {
|
|
1283
|
+
this.page();
|
|
1284
|
+
},
|
|
1285
|
+
onPageLeave: () => {
|
|
1286
|
+
this.trackEvent("page_leave", { url: browserWindow.location.href });
|
|
1287
|
+
},
|
|
1288
|
+
onRouteChange: () => {
|
|
1289
|
+
captureAttributionFromUrl(this.persistence);
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
this.autocaptureCleanup = controller.cleanup;
|
|
1293
|
+
}
|
|
618
1294
|
scheduleRetryFlush() {
|
|
619
1295
|
if (this.retryTimer !== void 0) return;
|
|
620
1296
|
const delay = Math.min(this.retryDelayMs, this.maxRetryDelayMs);
|
|
@@ -640,7 +1316,7 @@ var DriveMetaDataSDK = class {
|
|
|
640
1316
|
type,
|
|
641
1317
|
event,
|
|
642
1318
|
properties: sanitizeProperties(properties),
|
|
643
|
-
messageId: options.messageId
|
|
1319
|
+
messageId: ensureUuid(options.messageId),
|
|
644
1320
|
timestamp: options.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
645
1321
|
context: options.context ?? {},
|
|
646
1322
|
anonymousId: this.identity.anonymousId,
|
|
@@ -649,7 +1325,7 @@ var DriveMetaDataSDK = class {
|
|
|
649
1325
|
appId: this.config.appId,
|
|
650
1326
|
writeKey: this.writeKey,
|
|
651
1327
|
consent: this.consentState,
|
|
652
|
-
sessionId: this.
|
|
1328
|
+
sessionId: this.session.getSessionId()
|
|
653
1329
|
};
|
|
654
1330
|
if (this.identity.userId !== void 0) prepared.userId = this.identity.userId;
|
|
655
1331
|
if (this.identity.groupId !== void 0) prepared.groupId = this.identity.groupId;
|
|
@@ -673,7 +1349,17 @@ var DriveMetaDataSDK = class {
|
|
|
673
1349
|
if (!validation.ok) {
|
|
674
1350
|
this.lastError = `DMD SDK schema warning: ${validation.errors.join(", ")}`;
|
|
675
1351
|
}
|
|
676
|
-
this.
|
|
1352
|
+
const collectorPayload = this.toCollectorPayload(prepared);
|
|
1353
|
+
const backendValidation = validateBackendCollectorPayload(collectorPayload);
|
|
1354
|
+
if (!backendValidation.ok && this.config.schemaValidation === "strict") {
|
|
1355
|
+
this.lastError = `DMD SDK backend schema warning: ${backendValidation.errors.join(", ")}`;
|
|
1356
|
+
this.recordDrop(type, "invalid_payload", event);
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
if (!backendValidation.ok) {
|
|
1360
|
+
this.lastError = `DMD SDK backend schema warning: ${backendValidation.errors.join(", ")}`;
|
|
1361
|
+
}
|
|
1362
|
+
this.sendEvent(collectorPayload);
|
|
677
1363
|
}
|
|
678
1364
|
toCollectorPayload(prepared) {
|
|
679
1365
|
const browserWindow = getBrowserWindow();
|
|
@@ -689,14 +1375,14 @@ var DriveMetaDataSDK = class {
|
|
|
689
1375
|
ua: browserWindow?.navigator?.userAgent,
|
|
690
1376
|
appId: prepared.appId,
|
|
691
1377
|
pageUrl: browserWindow?.location?.href,
|
|
692
|
-
eventData: {
|
|
1378
|
+
eventData: mergeStoredAttribution({
|
|
693
1379
|
...prepared.properties,
|
|
694
1380
|
anonymousId: prepared.anonymousId,
|
|
695
1381
|
sessionId: prepared.sessionId,
|
|
696
1382
|
timestamp: prepared.timestamp,
|
|
697
1383
|
requestSentAt: prepared.timestamp,
|
|
698
1384
|
requestReceivedAt: prepared.timestamp
|
|
699
|
-
}
|
|
1385
|
+
}, this.persistence)
|
|
700
1386
|
});
|
|
701
1387
|
}
|
|
702
1388
|
recordDrop(type, reason, event) {
|
|
@@ -772,7 +1458,7 @@ function initDmdSDK(config) {
|
|
|
772
1458
|
return publicSingleton;
|
|
773
1459
|
}
|
|
774
1460
|
try {
|
|
775
|
-
const instance = new DriveMetaDataSDK(config);
|
|
1461
|
+
const instance = new DriveMetaDataSDK(normalizeBrowserConfig(config));
|
|
776
1462
|
return setSingleton(instance);
|
|
777
1463
|
} catch (error) {
|
|
778
1464
|
lastError = error instanceof Error ? error.message : String(error);
|