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