@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.cjs
CHANGED
|
@@ -38,12 +38,62 @@ module.exports = __toCommonJS(angular_exports);
|
|
|
38
38
|
// src/angular/dmd-analytics.service.ts
|
|
39
39
|
var import_core2 = require("@angular/core");
|
|
40
40
|
|
|
41
|
+
// src/browser/core/browser-config.ts
|
|
42
|
+
function setIfDefined(target, key, value) {
|
|
43
|
+
if (value !== void 0) {
|
|
44
|
+
target[key] = value;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function normalizeBrowserConfig(input) {
|
|
48
|
+
const normalized = {
|
|
49
|
+
...input,
|
|
50
|
+
clientId: input.clientId ?? "",
|
|
51
|
+
workspaceId: input.workspaceId ?? "",
|
|
52
|
+
appId: input.appId ?? ""
|
|
53
|
+
};
|
|
54
|
+
setIfDefined(normalized, "apiHost", input.apiHost);
|
|
55
|
+
setIfDefined(normalized, "capturePageview", input.capturePageview);
|
|
56
|
+
setIfDefined(normalized, "capturePageleave", input.capturePageleave);
|
|
57
|
+
setIfDefined(normalized, "captureDeadClicks", input.captureDeadClicks);
|
|
58
|
+
setIfDefined(normalized, "crossSubdomainCookie", input.crossSubdomainCookie);
|
|
59
|
+
setIfDefined(normalized, "sessionIdleTimeoutSeconds", input.sessionIdleTimeoutSeconds);
|
|
60
|
+
setIfDefined(normalized, "schemaValidation", input.schemaValidation);
|
|
61
|
+
setIfDefined(normalized, "beforeSend", input.beforeSend);
|
|
62
|
+
setIfDefined(normalized, "persistence", input.disablePersistence === true ? "none" : input.persistence);
|
|
63
|
+
return normalized;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/core/uuid.ts
|
|
67
|
+
function createUuid() {
|
|
68
|
+
const cryptoApi = globalThis.crypto;
|
|
69
|
+
if (typeof cryptoApi?.randomUUID === "function") {
|
|
70
|
+
return cryptoApi.randomUUID();
|
|
71
|
+
}
|
|
72
|
+
const bytes = new Uint8Array(16);
|
|
73
|
+
if (typeof cryptoApi?.getRandomValues === "function") {
|
|
74
|
+
cryptoApi.getRandomValues(bytes);
|
|
75
|
+
} else {
|
|
76
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
77
|
+
bytes[index] = Math.floor(Math.random() * 256);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
bytes[6] = (bytes[6] ?? 0) & 15 | 64;
|
|
81
|
+
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
82
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
83
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
84
|
+
}
|
|
85
|
+
function isUuid(value) {
|
|
86
|
+
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);
|
|
87
|
+
}
|
|
88
|
+
function ensureUuid(value) {
|
|
89
|
+
return isUuid(value) ? value : createUuid();
|
|
90
|
+
}
|
|
91
|
+
|
|
41
92
|
// src/core/backend-payload.ts
|
|
42
93
|
function formatUtcTimestamp(value) {
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
return date.toISOString().replace("T", " ").slice(0, 19);
|
|
94
|
+
const date = typeof value === "string" || typeof value === "number" || value instanceof Date ? new Date(value) : /* @__PURE__ */ new Date();
|
|
95
|
+
const safeDate = Number.isNaN(date.getTime()) ? /* @__PURE__ */ new Date() : date;
|
|
96
|
+
return safeDate.toISOString().replace("T", " ").slice(0, 19);
|
|
47
97
|
}
|
|
48
98
|
function cleanObject(value) {
|
|
49
99
|
if (Array.isArray(value)) {
|
|
@@ -74,15 +124,19 @@ function createBackendCollectorPayload(input) {
|
|
|
74
124
|
};
|
|
75
125
|
const metaData = {
|
|
76
126
|
...normalizedEventData,
|
|
77
|
-
requestId: input.requestId,
|
|
127
|
+
requestId: ensureUuid(typeof input.requestId === "string" ? input.requestId : void 0),
|
|
78
128
|
timestamp,
|
|
79
129
|
eventType: input.eventType,
|
|
80
130
|
requestFrom: input.requestFrom ?? "3",
|
|
81
131
|
clientId: input.clientId,
|
|
82
132
|
workspaceId: input.workspaceId,
|
|
83
133
|
token: input.token,
|
|
84
|
-
anonymousId:
|
|
85
|
-
|
|
134
|
+
anonymousId: ensureUuid(
|
|
135
|
+
typeof eventData.anonymousId === "string" ? eventData.anonymousId : typeof input.anonymousId === "string" ? input.anonymousId : void 0
|
|
136
|
+
),
|
|
137
|
+
sessionId: ensureUuid(
|
|
138
|
+
typeof eventData.sessionId === "string" ? eventData.sessionId : typeof input.sessionId === "string" ? input.sessionId : void 0
|
|
139
|
+
),
|
|
86
140
|
ua: input.ua,
|
|
87
141
|
appDetails: { app_id: input.appId },
|
|
88
142
|
page: { ...page2, url: page2.url ?? input.pageUrl },
|
|
@@ -93,12 +147,45 @@ function createBackendCollectorPayload(input) {
|
|
|
93
147
|
return cleanObject(payload);
|
|
94
148
|
}
|
|
95
149
|
|
|
96
|
-
// src/core/
|
|
97
|
-
|
|
98
|
-
|
|
150
|
+
// src/core/backend-schema.ts
|
|
151
|
+
var requiredMetaDataFields = [
|
|
152
|
+
"requestId",
|
|
153
|
+
"timestamp",
|
|
154
|
+
"eventType",
|
|
155
|
+
"requestFrom",
|
|
156
|
+
"clientId",
|
|
157
|
+
"workspaceId",
|
|
158
|
+
"anonymousId",
|
|
159
|
+
"sessionId"
|
|
160
|
+
];
|
|
161
|
+
function isPresent(value) {
|
|
162
|
+
return value !== null && value !== void 0 && value !== "";
|
|
163
|
+
}
|
|
164
|
+
function validateBackendCollectorPayload(payload) {
|
|
165
|
+
const errors = [];
|
|
166
|
+
const metaData = payload.metaData;
|
|
167
|
+
if (!metaData || typeof metaData !== "object" || Array.isArray(metaData)) {
|
|
168
|
+
return {
|
|
169
|
+
ok: false,
|
|
170
|
+
errors: ["metaData is required"]
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const metadataRecord = metaData;
|
|
174
|
+
for (const field of requiredMetaDataFields) {
|
|
175
|
+
if (!isPresent(metadataRecord[field])) {
|
|
176
|
+
errors.push(`metaData.${field} is required`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (isPresent(metadataRecord.eventType) && typeof metadataRecord.eventType !== "string") {
|
|
180
|
+
errors.push("metaData.eventType must be a string");
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
ok: errors.length === 0,
|
|
184
|
+
errors
|
|
185
|
+
};
|
|
99
186
|
}
|
|
100
187
|
|
|
101
|
-
// src/
|
|
188
|
+
// src/core/consent.ts
|
|
102
189
|
var purposes = [
|
|
103
190
|
"analytics",
|
|
104
191
|
"advertising",
|
|
@@ -138,6 +225,335 @@ function canCollectPurpose(consent, purpose) {
|
|
|
138
225
|
return consent[purpose] === "granted";
|
|
139
226
|
}
|
|
140
227
|
|
|
228
|
+
// src/core/event-schema.ts
|
|
229
|
+
var reservedKeys = /* @__PURE__ */ new Set(["messageId", "timestamp", "type", "event", "anonymousId", "userId", "context"]);
|
|
230
|
+
function validateEventEnvelope(envelope, options = {}) {
|
|
231
|
+
if (options.mode === "off") return { ok: true, errors: [] };
|
|
232
|
+
const errors = [];
|
|
233
|
+
if (typeof envelope.type !== "string") errors.push("type must be a string");
|
|
234
|
+
if (envelope.type === "track" && typeof envelope.event !== "string") {
|
|
235
|
+
errors.push("track event must include event name");
|
|
236
|
+
}
|
|
237
|
+
if (typeof envelope.messageId !== "string") errors.push("messageId must be a string");
|
|
238
|
+
if (typeof envelope.timestamp !== "string") errors.push("timestamp must be an ISO string");
|
|
239
|
+
for (const key of Object.keys(envelope.properties ?? {})) {
|
|
240
|
+
if (reservedKeys.has(key)) errors.push(`properties.${key} is reserved`);
|
|
241
|
+
}
|
|
242
|
+
return { ok: errors.length === 0, errors };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/core/environment.ts
|
|
246
|
+
function getBrowserWindow() {
|
|
247
|
+
return typeof window === "undefined" ? void 0 : window;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/core/privacy.ts
|
|
251
|
+
var sensitiveKeys = /* @__PURE__ */ new Set([
|
|
252
|
+
"email",
|
|
253
|
+
"phone",
|
|
254
|
+
"mobile",
|
|
255
|
+
"address",
|
|
256
|
+
"address1",
|
|
257
|
+
"address2",
|
|
258
|
+
"first_name",
|
|
259
|
+
"last_name",
|
|
260
|
+
"name",
|
|
261
|
+
"token",
|
|
262
|
+
"secret",
|
|
263
|
+
"password",
|
|
264
|
+
"session",
|
|
265
|
+
"cookie"
|
|
266
|
+
]);
|
|
267
|
+
function sanitizeValue(value, allow, path = []) {
|
|
268
|
+
if (Array.isArray(value)) {
|
|
269
|
+
return value.map((item) => sanitizeValue(item, allow, path));
|
|
270
|
+
}
|
|
271
|
+
if (value && typeof value === "object") {
|
|
272
|
+
return Object.fromEntries(
|
|
273
|
+
Object.entries(value).filter(([key]) => {
|
|
274
|
+
const lowerKey = key.toLowerCase();
|
|
275
|
+
const lowerPath = path.map((item) => item.toLowerCase());
|
|
276
|
+
const isEcommerceItemName = lowerKey === "name" && lowerPath.includes("ecommerce") && lowerPath.includes("items");
|
|
277
|
+
return isEcommerceItemName || !sensitiveKeys.has(lowerKey) || allow.has(lowerKey);
|
|
278
|
+
}).map(([key, nestedValue]) => [key, sanitizeValue(nestedValue, allow, [...path, key])])
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
return value;
|
|
282
|
+
}
|
|
283
|
+
function sanitizeProperties(input, allowRawKeys = []) {
|
|
284
|
+
const allow = new Set(allowRawKeys.map((key) => key.toLowerCase()));
|
|
285
|
+
return sanitizeValue(input, allow);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// src/core/attribution.ts
|
|
289
|
+
var dmdUtmKeys = [
|
|
290
|
+
"utm_source",
|
|
291
|
+
"utm_medium",
|
|
292
|
+
"utm_campaign",
|
|
293
|
+
"utm_term",
|
|
294
|
+
"utm_content",
|
|
295
|
+
"utm_id"
|
|
296
|
+
];
|
|
297
|
+
var dmdCampaignKeys = ["campaign_id", "ad_id"];
|
|
298
|
+
var dmdClickIdSources = [
|
|
299
|
+
["gclid", 2],
|
|
300
|
+
["fbclid", 3],
|
|
301
|
+
["ScCid", 1],
|
|
302
|
+
["li_fat_id", 4]
|
|
303
|
+
];
|
|
304
|
+
function cleanAttributionRecord(record) {
|
|
305
|
+
const cleaned = Object.fromEntries(
|
|
306
|
+
Object.entries(record).filter(([, value]) => value !== null && value !== void 0 && value !== "")
|
|
307
|
+
);
|
|
308
|
+
return Object.keys(cleaned).length > 0 ? cleaned : void 0;
|
|
309
|
+
}
|
|
310
|
+
function objectValue(value) {
|
|
311
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
312
|
+
}
|
|
313
|
+
function mergeAttributionRecords(explicitProperties, stored) {
|
|
314
|
+
const attributionData = objectValue(explicitProperties.attributionData);
|
|
315
|
+
const utmParameter = objectValue(explicitProperties.utmParameter);
|
|
316
|
+
return {
|
|
317
|
+
...explicitProperties,
|
|
318
|
+
...stored.attributionData || attributionData ? { attributionData: { ...stored.attributionData ?? {}, ...attributionData ?? {} } } : {},
|
|
319
|
+
...stored.utmParameter || utmParameter ? { utmParameter: { ...stored.utmParameter ?? {}, ...utmParameter ?? {} } } : {}
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/browser/core/attribution.ts
|
|
324
|
+
function getCurrentUrl() {
|
|
325
|
+
return getBrowserWindow()?.location?.href;
|
|
326
|
+
}
|
|
327
|
+
function getCookie(name) {
|
|
328
|
+
const cookie = getBrowserWindow()?.document?.cookie;
|
|
329
|
+
if (!cookie) return void 0;
|
|
330
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
331
|
+
const match = new RegExp(`(?:^|; )${escapedName}=([^;]*)`).exec(cookie);
|
|
332
|
+
return match ? decodeURIComponent(match[1] ?? "") : void 0;
|
|
333
|
+
}
|
|
334
|
+
function getParams(url) {
|
|
335
|
+
return new URL(url, "https://placeholder.local").searchParams;
|
|
336
|
+
}
|
|
337
|
+
function setIfPresent(persistence, key, value) {
|
|
338
|
+
if (value !== null && value !== "") {
|
|
339
|
+
persistence.setItem(key, value);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function getStoredString(persistence, key) {
|
|
343
|
+
const value = persistence.getItem(key);
|
|
344
|
+
return value === null || value === "" ? void 0 : value;
|
|
345
|
+
}
|
|
346
|
+
function getStoredNumberOrString(persistence, key) {
|
|
347
|
+
const value = getStoredString(persistence, key);
|
|
348
|
+
if (value === void 0) return void 0;
|
|
349
|
+
const parsed = Number.parseInt(value, 10);
|
|
350
|
+
return Number.isNaN(parsed) ? value : parsed;
|
|
351
|
+
}
|
|
352
|
+
function captureAttributionFromUrl(persistence, url = getCurrentUrl()) {
|
|
353
|
+
if (!url) return;
|
|
354
|
+
const params = getParams(url);
|
|
355
|
+
for (const key of dmdUtmKeys) {
|
|
356
|
+
setIfPresent(persistence, key, params.get(key));
|
|
357
|
+
}
|
|
358
|
+
for (const key of dmdCampaignKeys) {
|
|
359
|
+
setIfPresent(persistence, key, params.get(key));
|
|
360
|
+
}
|
|
361
|
+
for (const [param, sdkPubId] of dmdClickIdSources) {
|
|
362
|
+
const value = params.get(param);
|
|
363
|
+
if (value) {
|
|
364
|
+
persistence.setItem("unique_id", value);
|
|
365
|
+
persistence.setItem("sdk_pub_id", String(sdkPubId));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function getStoredAttributionData(persistence) {
|
|
370
|
+
return cleanAttributionRecord({
|
|
371
|
+
unique_id: getStoredString(persistence, "unique_id"),
|
|
372
|
+
sdk_pub_id: getStoredNumberOrString(persistence, "sdk_pub_id"),
|
|
373
|
+
fbc: getStoredString(persistence, "fbc") ?? getCookie("_fbc"),
|
|
374
|
+
fbp: getStoredString(persistence, "fbp") ?? getCookie("_fbp"),
|
|
375
|
+
campaign_id: getStoredString(persistence, "campaign_id"),
|
|
376
|
+
ad_id: getStoredString(persistence, "ad_id")
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
function getStoredUtmParameter(persistence) {
|
|
380
|
+
return cleanAttributionRecord(Object.fromEntries(dmdUtmKeys.map((key) => [key, getStoredString(persistence, key)])));
|
|
381
|
+
}
|
|
382
|
+
function mergeStoredAttribution(properties, persistence) {
|
|
383
|
+
const stored = {};
|
|
384
|
+
const attributionData = getStoredAttributionData(persistence);
|
|
385
|
+
const utmParameter = getStoredUtmParameter(persistence);
|
|
386
|
+
if (attributionData !== void 0) stored.attributionData = attributionData;
|
|
387
|
+
if (utmParameter !== void 0) stored.utmParameter = utmParameter;
|
|
388
|
+
return mergeAttributionRecords(properties, stored);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// src/browser/core/autocapture.ts
|
|
392
|
+
function installAutocapture(config) {
|
|
393
|
+
const trackedPageUrls = /* @__PURE__ */ new Set();
|
|
394
|
+
const timers = /* @__PURE__ */ new Set();
|
|
395
|
+
const pageviewDelayMs = config.pageviewDelayMs ?? 500;
|
|
396
|
+
const history = config.browserWindow.history;
|
|
397
|
+
const originalPushState = history?.pushState;
|
|
398
|
+
const originalReplaceState = history?.replaceState;
|
|
399
|
+
function clearTimer(timer) {
|
|
400
|
+
timers.delete(timer);
|
|
401
|
+
clearTimeout(timer);
|
|
402
|
+
}
|
|
403
|
+
function canonicalUrl() {
|
|
404
|
+
return config.browserWindow.location.href;
|
|
405
|
+
}
|
|
406
|
+
function trackCurrentPageview() {
|
|
407
|
+
if (!config.capturePageview) return;
|
|
408
|
+
const url = canonicalUrl();
|
|
409
|
+
if (trackedPageUrls.has(url)) return;
|
|
410
|
+
trackedPageUrls.add(url);
|
|
411
|
+
config.onPageView();
|
|
412
|
+
}
|
|
413
|
+
function schedulePageview(delayMs) {
|
|
414
|
+
if (!config.capturePageview) return;
|
|
415
|
+
const timer = setTimeout(() => {
|
|
416
|
+
timers.delete(timer);
|
|
417
|
+
trackCurrentPageview();
|
|
418
|
+
}, delayMs);
|
|
419
|
+
timers.add(timer);
|
|
420
|
+
}
|
|
421
|
+
function handleRouteChange() {
|
|
422
|
+
config.onRouteChange?.();
|
|
423
|
+
trackCurrentPageview();
|
|
424
|
+
}
|
|
425
|
+
function wrapHistoryMethod(method) {
|
|
426
|
+
if (!history) return;
|
|
427
|
+
const original = history[method];
|
|
428
|
+
if (typeof original !== "function") return;
|
|
429
|
+
history[method] = function wrappedHistoryMethod(...args) {
|
|
430
|
+
const result = original.apply(this, args);
|
|
431
|
+
const timer = setTimeout(() => {
|
|
432
|
+
timers.delete(timer);
|
|
433
|
+
handleRouteChange();
|
|
434
|
+
}, 0);
|
|
435
|
+
timers.add(timer);
|
|
436
|
+
return result;
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
function handlePopOrHashChange() {
|
|
440
|
+
handleRouteChange();
|
|
441
|
+
}
|
|
442
|
+
function handlePageLeave() {
|
|
443
|
+
if (config.capturePageleave) {
|
|
444
|
+
config.onPageLeave();
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
schedulePageview(pageviewDelayMs);
|
|
448
|
+
wrapHistoryMethod("pushState");
|
|
449
|
+
wrapHistoryMethod("replaceState");
|
|
450
|
+
const canListen = typeof config.browserWindow.addEventListener === "function" && typeof config.browserWindow.removeEventListener === "function";
|
|
451
|
+
if (canListen) {
|
|
452
|
+
config.browserWindow.addEventListener("popstate", handlePopOrHashChange);
|
|
453
|
+
config.browserWindow.addEventListener("hashchange", handlePopOrHashChange);
|
|
454
|
+
config.browserWindow.addEventListener("beforeunload", handlePageLeave);
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
cleanup() {
|
|
458
|
+
for (const timer of Array.from(timers)) {
|
|
459
|
+
clearTimer(timer);
|
|
460
|
+
}
|
|
461
|
+
if (history && originalPushState) history.pushState = originalPushState;
|
|
462
|
+
if (history && originalReplaceState) history.replaceState = originalReplaceState;
|
|
463
|
+
if (canListen) {
|
|
464
|
+
config.browserWindow.removeEventListener("popstate", handlePopOrHashChange);
|
|
465
|
+
config.browserWindow.removeEventListener("hashchange", handlePopOrHashChange);
|
|
466
|
+
config.browserWindow.removeEventListener("beforeunload", handlePageLeave);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/core/payload-size.ts
|
|
473
|
+
var preserveStringKeys = /* @__PURE__ */ new Set([
|
|
474
|
+
"requestId",
|
|
475
|
+
"eventType",
|
|
476
|
+
"requestFrom",
|
|
477
|
+
"clientId",
|
|
478
|
+
"workspaceId",
|
|
479
|
+
"anonymousId",
|
|
480
|
+
"sessionId",
|
|
481
|
+
"token"
|
|
482
|
+
]);
|
|
483
|
+
function payloadByteLength(payload) {
|
|
484
|
+
const serialized = JSON.stringify(payload);
|
|
485
|
+
if (typeof TextEncoder !== "undefined") {
|
|
486
|
+
return new TextEncoder().encode(serialized).length;
|
|
487
|
+
}
|
|
488
|
+
return serialized.length;
|
|
489
|
+
}
|
|
490
|
+
function clonePayload(payload) {
|
|
491
|
+
try {
|
|
492
|
+
return JSON.parse(JSON.stringify(payload));
|
|
493
|
+
} catch {
|
|
494
|
+
return void 0;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function truncateValue(value, truncateStringLength, key) {
|
|
498
|
+
if (typeof value === "string") {
|
|
499
|
+
if (key && preserveStringKeys.has(key)) return value;
|
|
500
|
+
if (value.length <= truncateStringLength) return value;
|
|
501
|
+
return `${value.slice(0, truncateStringLength)}...[TRUNCATED]`;
|
|
502
|
+
}
|
|
503
|
+
if (Array.isArray(value)) {
|
|
504
|
+
return value.map((item) => truncateValue(item, truncateStringLength));
|
|
505
|
+
}
|
|
506
|
+
if (value && typeof value === "object") {
|
|
507
|
+
return Object.fromEntries(
|
|
508
|
+
Object.entries(value).map(([childKey, childValue]) => [
|
|
509
|
+
childKey,
|
|
510
|
+
truncateValue(childValue, truncateStringLength, childKey)
|
|
511
|
+
])
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
return value;
|
|
515
|
+
}
|
|
516
|
+
function markPayloadTruncated(payload) {
|
|
517
|
+
if (payload.metaData && typeof payload.metaData === "object" && !Array.isArray(payload.metaData)) {
|
|
518
|
+
return {
|
|
519
|
+
...payload,
|
|
520
|
+
metaData: {
|
|
521
|
+
...payload.metaData,
|
|
522
|
+
payloadTruncated: true
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
return {
|
|
527
|
+
...payload,
|
|
528
|
+
payloadTruncated: true
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
function truncatePayload(payload, truncateStringLength) {
|
|
532
|
+
const cloned = clonePayload(payload);
|
|
533
|
+
if (!cloned) return void 0;
|
|
534
|
+
return markPayloadTruncated(truncateValue(cloned, truncateStringLength));
|
|
535
|
+
}
|
|
536
|
+
function getPayloadMessageId(payload) {
|
|
537
|
+
const value = payload.metaData?.requestId ?? payload.messageId;
|
|
538
|
+
return value === void 0 ? void 0 : String(value);
|
|
539
|
+
}
|
|
540
|
+
function preparePayloadForSizePolicy(payload, options = {}) {
|
|
541
|
+
const maxPayloadBytes = options.maxPayloadBytes ?? 64e3;
|
|
542
|
+
const payloadSizePolicy = options.payloadSizePolicy ?? "drop";
|
|
543
|
+
const payloadTruncateStringLength = options.payloadTruncateStringLength ?? 1024;
|
|
544
|
+
if (payloadByteLength(payload) <= maxPayloadBytes) {
|
|
545
|
+
return { ok: true, payload, truncated: false };
|
|
546
|
+
}
|
|
547
|
+
if (payloadSizePolicy === "truncate") {
|
|
548
|
+
const truncatedPayload = truncatePayload(payload, payloadTruncateStringLength);
|
|
549
|
+
if (truncatedPayload && payloadByteLength(truncatedPayload) <= maxPayloadBytes) {
|
|
550
|
+
return { ok: true, payload: truncatedPayload, truncated: true };
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
const messageId = getPayloadMessageId(payload);
|
|
554
|
+
return messageId === void 0 ? { ok: false, reason: "payload_too_large" } : { ok: false, reason: "payload_too_large", messageId };
|
|
555
|
+
}
|
|
556
|
+
|
|
141
557
|
// src/browser/core/delivery.ts
|
|
142
558
|
function createId(prefix) {
|
|
143
559
|
return `${prefix}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
|
|
@@ -169,7 +585,6 @@ function createDeliveryManager(config) {
|
|
|
169
585
|
const lockTtlMs = config.lockTtlMs ?? 5e3;
|
|
170
586
|
const tabId = config.tabId ?? createId("tab");
|
|
171
587
|
const batchSize = config.batchSize ?? 25;
|
|
172
|
-
const maxPayloadBytes = config.maxPayloadBytes ?? 64e3;
|
|
173
588
|
function recordDrop(event) {
|
|
174
589
|
diagnostics.dropped.push(event);
|
|
175
590
|
config.onDrop?.(event);
|
|
@@ -178,12 +593,22 @@ function createDeliveryManager(config) {
|
|
|
178
593
|
diagnostics.lastError = error.message;
|
|
179
594
|
config.onError?.(error);
|
|
180
595
|
}
|
|
181
|
-
function
|
|
182
|
-
const
|
|
183
|
-
if (
|
|
184
|
-
|
|
596
|
+
function preparePayloadForSend(payload) {
|
|
597
|
+
const sizePolicyOptions = {};
|
|
598
|
+
if (config.maxPayloadBytes !== void 0) sizePolicyOptions.maxPayloadBytes = config.maxPayloadBytes;
|
|
599
|
+
if (config.payloadSizePolicy !== void 0) sizePolicyOptions.payloadSizePolicy = config.payloadSizePolicy;
|
|
600
|
+
if (config.payloadTruncateStringLength !== void 0) {
|
|
601
|
+
sizePolicyOptions.payloadTruncateStringLength = config.payloadTruncateStringLength;
|
|
185
602
|
}
|
|
186
|
-
|
|
603
|
+
const prepared = preparePayloadForSizePolicy(payload, sizePolicyOptions);
|
|
604
|
+
if (prepared.ok) return prepared.payload;
|
|
605
|
+
const diagnostic = {
|
|
606
|
+
reason: prepared.reason,
|
|
607
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
608
|
+
};
|
|
609
|
+
if (prepared.messageId !== void 0) diagnostic.messageId = prepared.messageId;
|
|
610
|
+
recordDrop(diagnostic);
|
|
611
|
+
return void 0;
|
|
187
612
|
}
|
|
188
613
|
function recordStorageUnavailable() {
|
|
189
614
|
recordDrop({
|
|
@@ -273,7 +698,19 @@ function createDeliveryManager(config) {
|
|
|
273
698
|
idempotencyKey: String(payload.idempotencyKey ?? createIdempotencyKey(payload, messageId))
|
|
274
699
|
};
|
|
275
700
|
}
|
|
701
|
+
function tryBeacon(body) {
|
|
702
|
+
if (!config.useBeacon) return false;
|
|
703
|
+
const sendBeacon = globalThis.navigator?.sendBeacon;
|
|
704
|
+
if (typeof sendBeacon !== "function") return false;
|
|
705
|
+
try {
|
|
706
|
+
const blob = new Blob([JSON.stringify(body)], { type: "application/json" });
|
|
707
|
+
return sendBeacon.call(globalThis.navigator, config.endpoint, blob);
|
|
708
|
+
} catch {
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
276
712
|
async function deliver(body) {
|
|
713
|
+
if (tryBeacon(body)) return;
|
|
277
714
|
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
278
715
|
if (typeof fetchImpl !== "function") {
|
|
279
716
|
throw new Error("fetch_unavailable");
|
|
@@ -290,26 +727,22 @@ function createDeliveryManager(config) {
|
|
|
290
727
|
return {
|
|
291
728
|
async send(payload) {
|
|
292
729
|
const body = withEnvelope(payload);
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
messageId: String(body.metaData?.requestId ?? body.messageId),
|
|
296
|
-
reason: "payload_too_large",
|
|
297
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
298
|
-
});
|
|
730
|
+
const preparedBody = preparePayloadForSend(body);
|
|
731
|
+
if (!preparedBody) {
|
|
299
732
|
return;
|
|
300
733
|
}
|
|
301
734
|
diagnostics.inFlight += 1;
|
|
302
735
|
try {
|
|
303
|
-
await deliver(
|
|
736
|
+
await deliver(preparedBody);
|
|
304
737
|
} catch (error) {
|
|
305
738
|
const deliveryError = error instanceof Error ? error : new Error(String(error));
|
|
306
739
|
recordError(deliveryError);
|
|
307
740
|
enqueue({
|
|
308
|
-
messageId: String(body
|
|
741
|
+
messageId: String(getPayloadMessageId(body)),
|
|
309
742
|
savedAt: Date.now(),
|
|
310
743
|
attempts: 1,
|
|
311
744
|
lastError: deliveryError.message,
|
|
312
|
-
payload:
|
|
745
|
+
payload: preparedBody
|
|
313
746
|
});
|
|
314
747
|
} finally {
|
|
315
748
|
diagnostics.inFlight -= 1;
|
|
@@ -422,60 +855,268 @@ function createDeliveryManager(config) {
|
|
|
422
855
|
};
|
|
423
856
|
}
|
|
424
857
|
|
|
425
|
-
// src/browser/core/
|
|
426
|
-
var
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
"name",
|
|
436
|
-
"token",
|
|
437
|
-
"secret",
|
|
438
|
-
"password",
|
|
439
|
-
"session",
|
|
440
|
-
"cookie"
|
|
441
|
-
]);
|
|
442
|
-
function sanitizeValue(value, allow) {
|
|
443
|
-
if (Array.isArray(value)) {
|
|
444
|
-
return value.map((item) => sanitizeValue(item, allow));
|
|
858
|
+
// src/browser/core/identity.ts
|
|
859
|
+
var ANONYMOUS_ID_STORAGE_KEY = "dmd_anonymous_id";
|
|
860
|
+
var LEGACY_ANONYMOUS_ID_STORAGE_KEY = "anonymousId";
|
|
861
|
+
function getUrlParam(name) {
|
|
862
|
+
const href = getBrowserWindow()?.location?.href;
|
|
863
|
+
if (!href) return null;
|
|
864
|
+
try {
|
|
865
|
+
return new URL(href).searchParams.get(name);
|
|
866
|
+
} catch {
|
|
867
|
+
return null;
|
|
445
868
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
869
|
+
}
|
|
870
|
+
function resolveAnonymousId(persistence) {
|
|
871
|
+
const urlAnonymousId = getUrlParam("aid");
|
|
872
|
+
if (isUuid(urlAnonymousId)) {
|
|
873
|
+
persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, urlAnonymousId);
|
|
874
|
+
return urlAnonymousId;
|
|
875
|
+
}
|
|
876
|
+
const storedAnonymousId = persistence.getItem(ANONYMOUS_ID_STORAGE_KEY);
|
|
877
|
+
if (isUuid(storedAnonymousId)) {
|
|
878
|
+
return storedAnonymousId;
|
|
879
|
+
}
|
|
880
|
+
const legacyAnonymousId = persistence.getItem(LEGACY_ANONYMOUS_ID_STORAGE_KEY);
|
|
881
|
+
if (isUuid(legacyAnonymousId)) {
|
|
882
|
+
persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, legacyAnonymousId);
|
|
883
|
+
return legacyAnonymousId;
|
|
884
|
+
}
|
|
885
|
+
const anonymousId = createUuid();
|
|
886
|
+
persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, anonymousId);
|
|
887
|
+
return anonymousId;
|
|
888
|
+
}
|
|
889
|
+
function resetAnonymousId(persistence) {
|
|
890
|
+
const anonymousId = createUuid();
|
|
891
|
+
persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, anonymousId);
|
|
892
|
+
return anonymousId;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// src/browser/core/persistence.ts
|
|
896
|
+
function createPersistenceHealth(requested) {
|
|
897
|
+
return {
|
|
898
|
+
requested,
|
|
899
|
+
cookieFallbackUsed: false,
|
|
900
|
+
memoryFallbackUsed: requested === "memory",
|
|
901
|
+
failures: []
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
function recordFailure(health, backend, operation, error) {
|
|
905
|
+
health.failures.push({
|
|
906
|
+
backend,
|
|
907
|
+
operation,
|
|
908
|
+
message: error instanceof Error ? error.message : String(error)
|
|
909
|
+
});
|
|
910
|
+
if (health.failures.length > 25) {
|
|
911
|
+
health.failures.shift();
|
|
450
912
|
}
|
|
451
|
-
return value;
|
|
452
913
|
}
|
|
453
|
-
function
|
|
454
|
-
const
|
|
455
|
-
return
|
|
914
|
+
function createMemoryPersistence(health = createPersistenceHealth("memory")) {
|
|
915
|
+
const memory = {};
|
|
916
|
+
return {
|
|
917
|
+
getItem(key) {
|
|
918
|
+
return Object.prototype.hasOwnProperty.call(memory, key) ? memory[key] ?? null : null;
|
|
919
|
+
},
|
|
920
|
+
setItem(key, value) {
|
|
921
|
+
memory[key] = value;
|
|
922
|
+
return true;
|
|
923
|
+
},
|
|
924
|
+
removeItem(key) {
|
|
925
|
+
delete memory[key];
|
|
926
|
+
},
|
|
927
|
+
getHealth() {
|
|
928
|
+
return {
|
|
929
|
+
...health,
|
|
930
|
+
failures: health.failures.map((failure) => ({ ...failure }))
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
function getCookie2(name) {
|
|
936
|
+
const browserWindow = getBrowserWindow();
|
|
937
|
+
const cookie = browserWindow?.document?.cookie;
|
|
938
|
+
if (!cookie) return null;
|
|
939
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
940
|
+
const match = new RegExp(`(?:^|; )${escapedName}=([^;]*)`).exec(cookie);
|
|
941
|
+
return match ? decodeURIComponent(match[1] ?? "") : null;
|
|
942
|
+
}
|
|
943
|
+
function setCookie(name, value) {
|
|
944
|
+
const browserWindow = getBrowserWindow();
|
|
945
|
+
if (!browserWindow?.document) return false;
|
|
946
|
+
try {
|
|
947
|
+
const expires = new Date(Date.now() + 365 * 24 * 60 * 60 * 1e3).toUTCString();
|
|
948
|
+
const secure = browserWindow.location?.protocol === "https:" ? "; Secure" : "";
|
|
949
|
+
browserWindow.document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/${secure}; SameSite=Lax`;
|
|
950
|
+
return true;
|
|
951
|
+
} catch {
|
|
952
|
+
return false;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
function removeCookie(name) {
|
|
956
|
+
const browserWindow = getBrowserWindow();
|
|
957
|
+
if (!browserWindow?.document) return;
|
|
958
|
+
browserWindow.document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
|
|
959
|
+
}
|
|
960
|
+
function getLocalStorage() {
|
|
961
|
+
const browserWindow = getBrowserWindow();
|
|
962
|
+
try {
|
|
963
|
+
return browserWindow?.localStorage;
|
|
964
|
+
} catch {
|
|
965
|
+
return void 0;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
function getSessionStorage() {
|
|
969
|
+
const browserWindow = getBrowserWindow();
|
|
970
|
+
try {
|
|
971
|
+
return browserWindow?.sessionStorage;
|
|
972
|
+
} catch {
|
|
973
|
+
return void 0;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
function createBrowserPersistence(mode = "localStorage+cookie") {
|
|
977
|
+
const health = createPersistenceHealth(mode);
|
|
978
|
+
const memory = createMemoryPersistence(health);
|
|
979
|
+
if (mode === "none") {
|
|
980
|
+
return {
|
|
981
|
+
getItem() {
|
|
982
|
+
return null;
|
|
983
|
+
},
|
|
984
|
+
setItem() {
|
|
985
|
+
return false;
|
|
986
|
+
},
|
|
987
|
+
removeItem() {
|
|
988
|
+
},
|
|
989
|
+
getHealth() {
|
|
990
|
+
return {
|
|
991
|
+
...health,
|
|
992
|
+
failures: health.failures.map((failure) => ({ ...failure }))
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
if (mode === "memory") return memory;
|
|
998
|
+
const primary = mode === "sessionStorage" ? getSessionStorage() : getLocalStorage();
|
|
999
|
+
const useCookie = mode === "cookie" || mode === "localStorage+cookie" || !primary;
|
|
1000
|
+
return {
|
|
1001
|
+
getItem(key) {
|
|
1002
|
+
try {
|
|
1003
|
+
const stored = primary?.getItem(key);
|
|
1004
|
+
if (stored) return stored;
|
|
1005
|
+
} catch (error) {
|
|
1006
|
+
recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "get", error);
|
|
1007
|
+
}
|
|
1008
|
+
if (useCookie) {
|
|
1009
|
+
const cookie = getCookie2(key);
|
|
1010
|
+
if (cookie) {
|
|
1011
|
+
health.cookieFallbackUsed = true;
|
|
1012
|
+
return cookie;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
const value = memory.getItem(key);
|
|
1016
|
+
if (value !== null) health.memoryFallbackUsed = true;
|
|
1017
|
+
return value;
|
|
1018
|
+
},
|
|
1019
|
+
setItem(key, value) {
|
|
1020
|
+
let wrote = false;
|
|
1021
|
+
try {
|
|
1022
|
+
primary?.setItem(key, value);
|
|
1023
|
+
wrote = primary !== void 0;
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "set", error);
|
|
1026
|
+
wrote = false;
|
|
1027
|
+
}
|
|
1028
|
+
if (useCookie) {
|
|
1029
|
+
const cookieWrote = setCookie(key, value);
|
|
1030
|
+
if (cookieWrote) health.cookieFallbackUsed = true;
|
|
1031
|
+
wrote = cookieWrote || wrote;
|
|
1032
|
+
}
|
|
1033
|
+
if (!wrote) {
|
|
1034
|
+
health.memoryFallbackUsed = true;
|
|
1035
|
+
return memory.setItem(key, value);
|
|
1036
|
+
}
|
|
1037
|
+
if (mode === "localStorage+cookie") {
|
|
1038
|
+
memory.setItem(key, value);
|
|
1039
|
+
}
|
|
1040
|
+
return true;
|
|
1041
|
+
},
|
|
1042
|
+
removeItem(key) {
|
|
1043
|
+
try {
|
|
1044
|
+
primary?.removeItem(key);
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "remove", error);
|
|
1047
|
+
}
|
|
1048
|
+
if (useCookie) removeCookie(key);
|
|
1049
|
+
memory.removeItem(key);
|
|
1050
|
+
},
|
|
1051
|
+
getHealth() {
|
|
1052
|
+
return {
|
|
1053
|
+
...health,
|
|
1054
|
+
failures: health.failures.map((failure) => ({ ...failure }))
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
456
1058
|
}
|
|
457
1059
|
|
|
458
|
-
// src/browser/core/
|
|
459
|
-
var
|
|
460
|
-
function
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
1060
|
+
// src/browser/core/session.ts
|
|
1061
|
+
var SESSION_STORAGE_KEY = "sessionData";
|
|
1062
|
+
function getUrlParam2(name) {
|
|
1063
|
+
const href = getBrowserWindow()?.location?.href;
|
|
1064
|
+
if (!href) return null;
|
|
1065
|
+
try {
|
|
1066
|
+
return new URL(href).searchParams.get(name);
|
|
1067
|
+
} catch {
|
|
1068
|
+
return null;
|
|
466
1069
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
1070
|
+
}
|
|
1071
|
+
function readStoredSession(persistence) {
|
|
1072
|
+
const rawSession = persistence.getItem(SESSION_STORAGE_KEY);
|
|
1073
|
+
if (!rawSession) return void 0;
|
|
1074
|
+
try {
|
|
1075
|
+
const parsed = JSON.parse(rawSession);
|
|
1076
|
+
if (isUuid(parsed.sessionId) && typeof parsed.timestamp === "number") {
|
|
1077
|
+
return {
|
|
1078
|
+
sessionId: parsed.sessionId,
|
|
1079
|
+
timestamp: parsed.timestamp
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
} catch {
|
|
1083
|
+
persistence.removeItem(SESSION_STORAGE_KEY);
|
|
471
1084
|
}
|
|
472
|
-
return
|
|
1085
|
+
return void 0;
|
|
1086
|
+
}
|
|
1087
|
+
function writeStoredSession(persistence, sessionId, timestamp) {
|
|
1088
|
+
persistence.setItem(SESSION_STORAGE_KEY, JSON.stringify({ sessionId, timestamp }));
|
|
1089
|
+
}
|
|
1090
|
+
function createSessionManager(persistence, idleTimeoutSeconds = 30 * 60) {
|
|
1091
|
+
function resolveSessionId() {
|
|
1092
|
+
const now = Date.now();
|
|
1093
|
+
const urlSessionId = getUrlParam2("sid") ?? getUrlParam2("session_id");
|
|
1094
|
+
if (isUuid(urlSessionId)) {
|
|
1095
|
+
writeStoredSession(persistence, urlSessionId, now);
|
|
1096
|
+
return urlSessionId;
|
|
1097
|
+
}
|
|
1098
|
+
const storedSession = readStoredSession(persistence);
|
|
1099
|
+
if (storedSession && now - storedSession.timestamp <= idleTimeoutSeconds * 1e3) {
|
|
1100
|
+
writeStoredSession(persistence, storedSession.sessionId, now);
|
|
1101
|
+
return storedSession.sessionId;
|
|
1102
|
+
}
|
|
1103
|
+
const sessionId = createUuid();
|
|
1104
|
+
writeStoredSession(persistence, sessionId, now);
|
|
1105
|
+
return sessionId;
|
|
1106
|
+
}
|
|
1107
|
+
return {
|
|
1108
|
+
getSessionId() {
|
|
1109
|
+
return resolveSessionId();
|
|
1110
|
+
},
|
|
1111
|
+
reset() {
|
|
1112
|
+
const sessionId = createUuid();
|
|
1113
|
+
writeStoredSession(persistence, sessionId, Date.now());
|
|
1114
|
+
return sessionId;
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
473
1117
|
}
|
|
474
1118
|
|
|
475
1119
|
// src/browser/core/DriveMetaDataSDK.ts
|
|
476
|
-
function createId2(prefix) {
|
|
477
|
-
return `${prefix}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
|
|
478
|
-
}
|
|
479
1120
|
function endpointFromConfig(config) {
|
|
480
1121
|
const host = config.apiHost ?? "https://sdk.drivemetadata.com/v2";
|
|
481
1122
|
return `${host.replace(/\/$/, "")}/data-collector`;
|
|
@@ -486,14 +1127,6 @@ function requireConfigString(value, field) {
|
|
|
486
1127
|
}
|
|
487
1128
|
return value;
|
|
488
1129
|
}
|
|
489
|
-
function getBrowserStorage() {
|
|
490
|
-
const browserWindow = getBrowserWindow();
|
|
491
|
-
try {
|
|
492
|
-
return browserWindow?.localStorage;
|
|
493
|
-
} catch {
|
|
494
|
-
return void 0;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
1130
|
var DriveMetaDataSDK = class {
|
|
498
1131
|
constructor(config) {
|
|
499
1132
|
this.initialized = true;
|
|
@@ -506,30 +1139,35 @@ var DriveMetaDataSDK = class {
|
|
|
506
1139
|
requireConfigString(config.writeKey || config.token, "writeKey or token");
|
|
507
1140
|
this.config = config;
|
|
508
1141
|
this.endpoint = endpointFromConfig(config);
|
|
509
|
-
|
|
1142
|
+
this.persistence = createBrowserPersistence(config.persistence);
|
|
1143
|
+
this.session = createSessionManager(this.persistence, config.sessionIdleTimeoutSeconds);
|
|
510
1144
|
const deliveryConfig = {
|
|
511
|
-
endpoint: this.endpoint
|
|
1145
|
+
endpoint: this.endpoint,
|
|
1146
|
+
storage: this.persistence
|
|
512
1147
|
};
|
|
513
1148
|
if (config.delivery?.maxQueueSize !== void 0) deliveryConfig.maxQueueSize = config.delivery.maxQueueSize;
|
|
514
1149
|
if (config.delivery?.queueTtlMs !== void 0) deliveryConfig.queueTtlMs = config.delivery.queueTtlMs;
|
|
515
1150
|
if (config.delivery?.retryDelayMs !== void 0) deliveryConfig.retryDelayMs = config.delivery.retryDelayMs;
|
|
516
1151
|
if (config.delivery?.maxRetryDelayMs !== void 0) deliveryConfig.maxRetryDelayMs = config.delivery.maxRetryDelayMs;
|
|
517
1152
|
if (config.delivery?.maxPayloadBytes !== void 0) deliveryConfig.maxPayloadBytes = config.delivery.maxPayloadBytes;
|
|
1153
|
+
if (config.delivery?.payloadSizePolicy !== void 0) deliveryConfig.payloadSizePolicy = config.delivery.payloadSizePolicy;
|
|
1154
|
+
if (config.delivery?.payloadTruncateStringLength !== void 0) {
|
|
1155
|
+
deliveryConfig.payloadTruncateStringLength = config.delivery.payloadTruncateStringLength;
|
|
1156
|
+
}
|
|
1157
|
+
if (config.delivery?.useBeacon !== void 0) deliveryConfig.useBeacon = config.delivery.useBeacon;
|
|
518
1158
|
if (config.delivery?.batchSize !== void 0) deliveryConfig.batchSize = config.delivery.batchSize;
|
|
519
1159
|
if (config.onDrop !== void 0) deliveryConfig.onDrop = config.onDrop;
|
|
520
1160
|
if (config.onError !== void 0) deliveryConfig.onError = config.onError;
|
|
521
|
-
this.delivery = createDeliveryManager(
|
|
522
|
-
...deliveryConfig,
|
|
523
|
-
storage
|
|
524
|
-
} : deliveryConfig);
|
|
1161
|
+
this.delivery = createDeliveryManager(deliveryConfig);
|
|
525
1162
|
this.initialRetryDelayMs = config.delivery?.retryDelayMs ?? 1e3;
|
|
526
1163
|
this.retryDelayMs = this.initialRetryDelayMs;
|
|
527
1164
|
this.maxRetryDelayMs = config.delivery?.maxRetryDelayMs ?? 3e4;
|
|
528
1165
|
this.writeKey = config.writeKey || config.token || "";
|
|
529
|
-
this.identity = { anonymousId:
|
|
530
|
-
this.
|
|
1166
|
+
this.identity = { anonymousId: resolveAnonymousId(this.persistence) };
|
|
1167
|
+
captureAttributionFromUrl(this.persistence);
|
|
531
1168
|
this.consentState = normalizeConsent(config.gdprConsent ?? config.consent);
|
|
532
1169
|
this.gdprConsent = this.consentState.analytics;
|
|
1170
|
+
this.installAutocapture();
|
|
533
1171
|
if (!config.delivery?.disableLifecycleFlush) {
|
|
534
1172
|
this.installLifecycleFlush();
|
|
535
1173
|
}
|
|
@@ -569,7 +1207,8 @@ var DriveMetaDataSDK = class {
|
|
|
569
1207
|
}
|
|
570
1208
|
}
|
|
571
1209
|
reset() {
|
|
572
|
-
this.identity = { anonymousId:
|
|
1210
|
+
this.identity = { anonymousId: resetAnonymousId(this.persistence) };
|
|
1211
|
+
this.session.reset();
|
|
573
1212
|
this.queue = [];
|
|
574
1213
|
this.offline = false;
|
|
575
1214
|
if (this.retryTimer !== void 0) {
|
|
@@ -578,8 +1217,20 @@ var DriveMetaDataSDK = class {
|
|
|
578
1217
|
}
|
|
579
1218
|
this.lifecycleCleanup?.();
|
|
580
1219
|
this.lifecycleCleanup = void 0;
|
|
1220
|
+
this.autocaptureCleanup?.();
|
|
1221
|
+
this.autocaptureCleanup = void 0;
|
|
581
1222
|
this.delivery.clearQueue("manual_clear");
|
|
582
1223
|
}
|
|
1224
|
+
disposeForTests() {
|
|
1225
|
+
if (this.retryTimer !== void 0) {
|
|
1226
|
+
clearTimeout(this.retryTimer);
|
|
1227
|
+
this.retryTimer = void 0;
|
|
1228
|
+
}
|
|
1229
|
+
this.lifecycleCleanup?.();
|
|
1230
|
+
this.lifecycleCleanup = void 0;
|
|
1231
|
+
this.autocaptureCleanup?.();
|
|
1232
|
+
this.autocaptureCleanup = void 0;
|
|
1233
|
+
}
|
|
583
1234
|
setConsent(consent) {
|
|
584
1235
|
this.consentState = typeof consent === "object" ? mergeConsent(this.consentState, consent) : normalizeConsent(consent);
|
|
585
1236
|
this.gdprConsent = this.consentState.analytics;
|
|
@@ -594,6 +1245,7 @@ var DriveMetaDataSDK = class {
|
|
|
594
1245
|
initialized: this.initialized,
|
|
595
1246
|
consent: this.gdprConsent,
|
|
596
1247
|
consentPurposes: this.consentState,
|
|
1248
|
+
persistence: this.persistence.getHealth(),
|
|
597
1249
|
queueSize: deliveryDiagnostics.queued,
|
|
598
1250
|
offline: this.offline || deliveryDiagnostics.queued > 0,
|
|
599
1251
|
droppedEvents: this.droppedEvents + deliveryDiagnostics.dropped.length,
|
|
@@ -608,7 +1260,9 @@ var DriveMetaDataSDK = class {
|
|
|
608
1260
|
void this.delivery.send(payload).then(() => {
|
|
609
1261
|
const diagnostics = this.delivery.getDiagnostics();
|
|
610
1262
|
this.offline = diagnostics.queued > 0;
|
|
611
|
-
|
|
1263
|
+
if (diagnostics.lastError !== void 0) {
|
|
1264
|
+
this.lastError = diagnostics.lastError;
|
|
1265
|
+
}
|
|
612
1266
|
if (diagnostics.queued > 0) {
|
|
613
1267
|
this.scheduleRetryFlush();
|
|
614
1268
|
}
|
|
@@ -640,6 +1294,28 @@ var DriveMetaDataSDK = class {
|
|
|
640
1294
|
}
|
|
641
1295
|
};
|
|
642
1296
|
}
|
|
1297
|
+
installAutocapture() {
|
|
1298
|
+
const browserWindow = getBrowserWindow();
|
|
1299
|
+
if (!browserWindow) return;
|
|
1300
|
+
if (this.config.autocapture === false) return;
|
|
1301
|
+
const capturePageview = this.config.capturePageview !== false;
|
|
1302
|
+
const capturePageleave = this.config.capturePageleave !== false;
|
|
1303
|
+
const controller = installAutocapture({
|
|
1304
|
+
browserWindow,
|
|
1305
|
+
capturePageview,
|
|
1306
|
+
capturePageleave,
|
|
1307
|
+
onPageView: () => {
|
|
1308
|
+
this.page();
|
|
1309
|
+
},
|
|
1310
|
+
onPageLeave: () => {
|
|
1311
|
+
this.trackEvent("page_leave", { url: browserWindow.location.href });
|
|
1312
|
+
},
|
|
1313
|
+
onRouteChange: () => {
|
|
1314
|
+
captureAttributionFromUrl(this.persistence);
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
this.autocaptureCleanup = controller.cleanup;
|
|
1318
|
+
}
|
|
643
1319
|
scheduleRetryFlush() {
|
|
644
1320
|
if (this.retryTimer !== void 0) return;
|
|
645
1321
|
const delay = Math.min(this.retryDelayMs, this.maxRetryDelayMs);
|
|
@@ -665,7 +1341,7 @@ var DriveMetaDataSDK = class {
|
|
|
665
1341
|
type,
|
|
666
1342
|
event,
|
|
667
1343
|
properties: sanitizeProperties(properties),
|
|
668
|
-
messageId: options.messageId
|
|
1344
|
+
messageId: ensureUuid(options.messageId),
|
|
669
1345
|
timestamp: options.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
670
1346
|
context: options.context ?? {},
|
|
671
1347
|
anonymousId: this.identity.anonymousId,
|
|
@@ -674,7 +1350,7 @@ var DriveMetaDataSDK = class {
|
|
|
674
1350
|
appId: this.config.appId,
|
|
675
1351
|
writeKey: this.writeKey,
|
|
676
1352
|
consent: this.consentState,
|
|
677
|
-
sessionId: this.
|
|
1353
|
+
sessionId: this.session.getSessionId()
|
|
678
1354
|
};
|
|
679
1355
|
if (this.identity.userId !== void 0) prepared.userId = this.identity.userId;
|
|
680
1356
|
if (this.identity.groupId !== void 0) prepared.groupId = this.identity.groupId;
|
|
@@ -698,7 +1374,17 @@ var DriveMetaDataSDK = class {
|
|
|
698
1374
|
if (!validation.ok) {
|
|
699
1375
|
this.lastError = `DMD SDK schema warning: ${validation.errors.join(", ")}`;
|
|
700
1376
|
}
|
|
701
|
-
this.
|
|
1377
|
+
const collectorPayload = this.toCollectorPayload(prepared);
|
|
1378
|
+
const backendValidation = validateBackendCollectorPayload(collectorPayload);
|
|
1379
|
+
if (!backendValidation.ok && this.config.schemaValidation === "strict") {
|
|
1380
|
+
this.lastError = `DMD SDK backend schema warning: ${backendValidation.errors.join(", ")}`;
|
|
1381
|
+
this.recordDrop(type, "invalid_payload", event);
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
if (!backendValidation.ok) {
|
|
1385
|
+
this.lastError = `DMD SDK backend schema warning: ${backendValidation.errors.join(", ")}`;
|
|
1386
|
+
}
|
|
1387
|
+
this.sendEvent(collectorPayload);
|
|
702
1388
|
}
|
|
703
1389
|
toCollectorPayload(prepared) {
|
|
704
1390
|
const browserWindow = getBrowserWindow();
|
|
@@ -714,14 +1400,14 @@ var DriveMetaDataSDK = class {
|
|
|
714
1400
|
ua: browserWindow?.navigator?.userAgent,
|
|
715
1401
|
appId: prepared.appId,
|
|
716
1402
|
pageUrl: browserWindow?.location?.href,
|
|
717
|
-
eventData: {
|
|
1403
|
+
eventData: mergeStoredAttribution({
|
|
718
1404
|
...prepared.properties,
|
|
719
1405
|
anonymousId: prepared.anonymousId,
|
|
720
1406
|
sessionId: prepared.sessionId,
|
|
721
1407
|
timestamp: prepared.timestamp,
|
|
722
1408
|
requestSentAt: prepared.timestamp,
|
|
723
1409
|
requestReceivedAt: prepared.timestamp
|
|
724
|
-
}
|
|
1410
|
+
}, this.persistence)
|
|
725
1411
|
});
|
|
726
1412
|
}
|
|
727
1413
|
recordDrop(type, reason, event) {
|
|
@@ -797,7 +1483,7 @@ function initDmdSDK(config) {
|
|
|
797
1483
|
return publicSingleton;
|
|
798
1484
|
}
|
|
799
1485
|
try {
|
|
800
|
-
const instance = new DriveMetaDataSDK(config);
|
|
1486
|
+
const instance = new DriveMetaDataSDK(normalizeBrowserConfig(config));
|
|
801
1487
|
return setSingleton(instance);
|
|
802
1488
|
} catch (error) {
|
|
803
1489
|
lastError = error instanceof Error ? error.message : String(error);
|