@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
@@ -38,58 +38,154 @@ 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/core/config.ts
42
- function requireString(value, field) {
43
- if (typeof value !== "string" || value.trim() === "") {
44
- throw new Error(`DMD SDK config ${field} is required`);
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
+
92
+ // src/core/backend-payload.ts
93
+ function formatUtcTimestamp(value) {
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);
97
+ }
98
+ function cleanObject(value) {
99
+ if (Array.isArray(value)) {
100
+ const cleaned = value.map((item) => cleanObject(item)).filter((item) => item !== null && item !== void 0 && item !== "");
101
+ return cleaned.length > 0 ? cleaned : void 0;
102
+ }
103
+ if (value && typeof value === "object") {
104
+ const cleaned = Object.fromEntries(
105
+ Object.entries(value).map(([key, item]) => [key, cleanObject(item)]).filter(([, item]) => {
106
+ if (item === null || item === void 0 || item === "") return false;
107
+ if (typeof item === "object" && !Array.isArray(item) && Object.keys(item).length === 0) return false;
108
+ return true;
109
+ })
110
+ );
111
+ return Object.keys(cleaned).length > 0 ? cleaned : void 0;
45
112
  }
46
113
  return value;
47
114
  }
48
- function normalizeBrowserConfig(config) {
49
- const writeKey = config.writeKey || config.token;
50
- const legacyConfig = {
51
- client_id: requireString(config.clientId, "clientId"),
52
- workspace_id: requireString(config.workspaceId, "workspaceId"),
53
- app_id: requireString(config.appId, "appId"),
54
- token: requireString(writeKey, "writeKey")
115
+ function createBackendCollectorPayload(input) {
116
+ const eventData = input.eventData && typeof input.eventData === "object" ? input.eventData : {};
117
+ const page2 = eventData.page && typeof eventData.page === "object" ? eventData.page : {};
118
+ const timestamp = formatUtcTimestamp(eventData.timestamp ?? input.timestamp);
119
+ const normalizedEventData = {
120
+ ...eventData,
121
+ timestamp,
122
+ requestSentAt: formatUtcTimestamp(eventData.requestSentAt ?? input.requestSentAt ?? timestamp),
123
+ requestReceivedAt: formatUtcTimestamp(eventData.requestReceivedAt ?? input.requestReceivedAt ?? timestamp)
124
+ };
125
+ const metaData = {
126
+ ...normalizedEventData,
127
+ requestId: ensureUuid(typeof input.requestId === "string" ? input.requestId : void 0),
128
+ timestamp,
129
+ eventType: input.eventType,
130
+ requestFrom: input.requestFrom ?? "3",
131
+ clientId: input.clientId,
132
+ workspaceId: input.workspaceId,
133
+ token: input.token,
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
+ ),
140
+ ua: input.ua,
141
+ appDetails: { app_id: input.appId },
142
+ page: { ...page2, url: page2.url ?? input.pageUrl },
143
+ requestSentAt: normalizedEventData.requestSentAt,
144
+ requestReceivedAt: normalizedEventData.requestReceivedAt
55
145
  };
56
- if (config.apiHost !== void 0) legacyConfig.api_host = config.apiHost;
57
- if (config.uiHost !== void 0) legacyConfig.ui_host = config.uiHost;
58
- if (config.deeplink !== void 0) legacyConfig.deeplink = config.deeplink;
59
- if (config.debug !== void 0) legacyConfig.debug = config.debug;
60
- if (config.consent !== void 0) legacyConfig.consent = config.consent;
61
- if (config.gdprConsent !== void 0) legacyConfig.gdprConsent = config.gdprConsent;
62
- if (config.autocapture !== void 0) legacyConfig.autocapture = config.autocapture;
63
- if (config.capturePageview !== void 0) legacyConfig.capture_pageview = config.capturePageview;
64
- if (config.capturePageleave !== void 0) legacyConfig.capture_pageleave = config.capturePageleave;
65
- if (config.captureDeadClicks !== void 0) legacyConfig.capture_dead_clicks = config.captureDeadClicks;
66
- if (config.crossSubdomainCookie !== void 0) legacyConfig.cross_subdomain_cookie = config.crossSubdomainCookie;
67
- if (config.disablePersistence !== void 0) legacyConfig.disable_persistence = config.disablePersistence;
68
- if (config.disableSurveys !== void 0) legacyConfig.disable_surveys = config.disableSurveys;
69
- if (config.disableSessionRecording !== void 0) legacyConfig.disable_session_recording = config.disableSessionRecording;
70
- if (config.enableHeatmaps !== void 0) legacyConfig.enable_heatmaps = config.enableHeatmaps;
71
- if (config.maskAllText !== void 0) legacyConfig.mask_all_text = config.maskAllText;
72
- if (config.maskAllElementAttributes !== void 0) {
73
- legacyConfig.mask_all_element_attributes = config.maskAllElementAttributes;
74
- }
75
- if (config.persistence !== void 0) legacyConfig.persistence = config.persistence;
76
- if (config.propertyDenylist !== void 0) legacyConfig.property_denylist = config.propertyDenylist;
77
- if (config.sessionIdleTimeoutSeconds !== void 0) {
78
- legacyConfig.session_idle_timeout_seconds = config.sessionIdleTimeoutSeconds;
79
- }
80
- if (config.beforeSend !== void 0) legacyConfig.before_send = config.beforeSend;
81
- return legacyConfig;
146
+ const payload = { metaData };
147
+ return cleanObject(payload);
82
148
  }
83
149
 
84
- // src/core/environment.ts
85
- function isBrowserRuntime() {
86
- return typeof window !== "undefined" && typeof document !== "undefined";
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 !== "";
87
163
  }
88
- function getBrowserWindow() {
89
- return typeof window === "undefined" ? void 0 : window;
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
+ };
90
186
  }
91
187
 
92
- // src/browser/core/consent.ts
188
+ // src/core/consent.ts
93
189
  var purposes = [
94
190
  "analytics",
95
191
  "advertising",
@@ -129,6 +225,335 @@ function canCollectPurpose(consent, purpose) {
129
225
  return consent[purpose] === "granted";
130
226
  }
131
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
+
132
557
  // src/browser/core/delivery.ts
133
558
  function createId(prefix) {
134
559
  return `${prefix}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
@@ -144,8 +569,9 @@ function stableStringify(value) {
144
569
  );
145
570
  }
146
571
  function createIdempotencyKey(payload, messageId) {
147
- const event = String(payload.event ?? payload.type ?? "event");
148
- const properties = payload.properties;
572
+ const metaData = payload.metaData;
573
+ const event = String(metaData?.eventType ?? payload.event ?? payload.type ?? "event");
574
+ const properties = metaData ?? payload.properties;
149
575
  const orderId = properties?.orderId ?? properties?.order_id ?? properties?.transaction_id;
150
576
  if (orderId !== void 0) return `${event}:${String(orderId)}`;
151
577
  return `${event}:${stableStringify(properties ?? {}) || messageId}`;
@@ -159,7 +585,6 @@ function createDeliveryManager(config) {
159
585
  const lockTtlMs = config.lockTtlMs ?? 5e3;
160
586
  const tabId = config.tabId ?? createId("tab");
161
587
  const batchSize = config.batchSize ?? 25;
162
- const maxPayloadBytes = config.maxPayloadBytes ?? 64e3;
163
588
  function recordDrop(event) {
164
589
  diagnostics.dropped.push(event);
165
590
  config.onDrop?.(event);
@@ -168,12 +593,22 @@ function createDeliveryManager(config) {
168
593
  diagnostics.lastError = error.message;
169
594
  config.onError?.(error);
170
595
  }
171
- function payloadByteLength(payload) {
172
- const serialized = JSON.stringify(payload);
173
- if (typeof TextEncoder !== "undefined") {
174
- return new TextEncoder().encode(serialized).length;
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;
175
602
  }
176
- return serialized.length;
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;
177
612
  }
178
613
  function recordStorageUnavailable() {
179
614
  recordDrop({
@@ -246,14 +681,36 @@ function createDeliveryManager(config) {
246
681
  persistQueue();
247
682
  }
248
683
  function withEnvelope(payload) {
249
- const messageId = String(payload.messageId ?? createId("msg"));
684
+ const metaData = payload.metaData;
685
+ const messageId = String(metaData?.requestId ?? payload.messageId ?? createId("msg"));
686
+ if (metaData) {
687
+ return {
688
+ ...payload,
689
+ metaData: {
690
+ ...metaData,
691
+ requestId: messageId
692
+ }
693
+ };
694
+ }
250
695
  return {
251
696
  ...payload,
252
697
  messageId,
253
698
  idempotencyKey: String(payload.idempotencyKey ?? createIdempotencyKey(payload, messageId))
254
699
  };
255
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
+ }
256
712
  async function deliver(body) {
713
+ if (tryBeacon(body)) return;
257
714
  const fetchImpl = config.fetch ?? globalThis.fetch;
258
715
  if (typeof fetchImpl !== "function") {
259
716
  throw new Error("fetch_unavailable");
@@ -270,26 +727,22 @@ function createDeliveryManager(config) {
270
727
  return {
271
728
  async send(payload) {
272
729
  const body = withEnvelope(payload);
273
- if (payloadByteLength(body) > maxPayloadBytes) {
274
- recordDrop({
275
- messageId: String(body.messageId),
276
- reason: "payload_too_large",
277
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
278
- });
730
+ const preparedBody = preparePayloadForSend(body);
731
+ if (!preparedBody) {
279
732
  return;
280
733
  }
281
734
  diagnostics.inFlight += 1;
282
735
  try {
283
- await deliver(body);
736
+ await deliver(preparedBody);
284
737
  } catch (error) {
285
738
  const deliveryError = error instanceof Error ? error : new Error(String(error));
286
739
  recordError(deliveryError);
287
740
  enqueue({
288
- messageId: String(body.messageId),
741
+ messageId: String(getPayloadMessageId(body)),
289
742
  savedAt: Date.now(),
290
743
  attempts: 1,
291
744
  lastError: deliveryError.message,
292
- payload: body
745
+ payload: preparedBody
293
746
  });
294
747
  } finally {
295
748
  diagnostics.inFlight -= 1;
@@ -402,71 +855,277 @@ function createDeliveryManager(config) {
402
855
  };
403
856
  }
404
857
 
405
- // src/browser/core/privacy.ts
406
- var sensitiveKeys = /* @__PURE__ */ new Set([
407
- "email",
408
- "phone",
409
- "mobile",
410
- "address",
411
- "address1",
412
- "address2",
413
- "first_name",
414
- "last_name",
415
- "name",
416
- "token",
417
- "secret",
418
- "password",
419
- "session",
420
- "cookie"
421
- ]);
422
- function sanitizeValue(value, allow) {
423
- if (Array.isArray(value)) {
424
- 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;
425
868
  }
426
- if (value && typeof value === "object") {
427
- return Object.fromEntries(
428
- Object.entries(value).filter(([key]) => !sensitiveKeys.has(key.toLowerCase()) || allow.has(key.toLowerCase())).map(([key, nestedValue]) => [key, sanitizeValue(nestedValue, allow)])
429
- );
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();
430
912
  }
431
- return value;
432
913
  }
433
- function sanitizeProperties(input, allowRawKeys = []) {
434
- const allow = new Set(allowRawKeys.map((key) => key.toLowerCase()));
435
- return sanitizeValue(input, allow);
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
+ };
436
1058
  }
437
1059
 
438
- // src/browser/core/schema.ts
439
- var reservedKeys = /* @__PURE__ */ new Set(["messageId", "timestamp", "type", "event", "anonymousId", "userId", "context"]);
440
- function validateEventEnvelope(envelope, options = {}) {
441
- if (options.mode === "off") return { ok: true, errors: [] };
442
- const errors = [];
443
- if (typeof envelope.type !== "string") errors.push("type must be a string");
444
- if (envelope.type === "track" && typeof envelope.event !== "string") {
445
- errors.push("track event must include event name");
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;
446
1069
  }
447
- if (typeof envelope.messageId !== "string") errors.push("messageId must be a string");
448
- if (typeof envelope.timestamp !== "string") errors.push("timestamp must be an ISO string");
449
- for (const key of Object.keys(envelope.properties ?? {})) {
450
- if (reservedKeys.has(key)) errors.push(`properties.${key} is reserved`);
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);
451
1084
  }
452
- return { ok: errors.length === 0, errors };
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
+ };
453
1117
  }
454
1118
 
455
1119
  // src/browser/core/DriveMetaDataSDK.ts
456
- function createId2(prefix) {
457
- return `${prefix}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
458
- }
459
1120
  function endpointFromConfig(config) {
460
1121
  const host = config.apiHost ?? "https://sdk.drivemetadata.com/v2";
461
1122
  return `${host.replace(/\/$/, "")}/data-collector`;
462
1123
  }
463
- function getBrowserStorage() {
464
- const browserWindow = getBrowserWindow();
465
- try {
466
- return browserWindow?.localStorage;
467
- } catch {
468
- return void 0;
1124
+ function requireConfigString(value, field) {
1125
+ if (typeof value !== "string" || value.trim() === "") {
1126
+ throw new Error(`DMD SDK config ${field} is required`);
469
1127
  }
1128
+ return value;
470
1129
  }
471
1130
  var DriveMetaDataSDK = class {
472
1131
  constructor(config) {
@@ -474,31 +1133,41 @@ var DriveMetaDataSDK = class {
474
1133
  this.queue = [];
475
1134
  this.offline = false;
476
1135
  this.droppedEvents = 0;
1136
+ requireConfigString(config.clientId, "clientId");
1137
+ requireConfigString(config.workspaceId, "workspaceId");
1138
+ requireConfigString(config.appId, "appId");
1139
+ requireConfigString(config.writeKey || config.token, "writeKey or token");
477
1140
  this.config = config;
478
1141
  this.endpoint = endpointFromConfig(config);
479
- const storage = getBrowserStorage();
1142
+ this.persistence = createBrowserPersistence(config.persistence);
1143
+ this.session = createSessionManager(this.persistence, config.sessionIdleTimeoutSeconds);
480
1144
  const deliveryConfig = {
481
- endpoint: this.endpoint
1145
+ endpoint: this.endpoint,
1146
+ storage: this.persistence
482
1147
  };
483
1148
  if (config.delivery?.maxQueueSize !== void 0) deliveryConfig.maxQueueSize = config.delivery.maxQueueSize;
484
1149
  if (config.delivery?.queueTtlMs !== void 0) deliveryConfig.queueTtlMs = config.delivery.queueTtlMs;
485
1150
  if (config.delivery?.retryDelayMs !== void 0) deliveryConfig.retryDelayMs = config.delivery.retryDelayMs;
486
1151
  if (config.delivery?.maxRetryDelayMs !== void 0) deliveryConfig.maxRetryDelayMs = config.delivery.maxRetryDelayMs;
487
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;
488
1158
  if (config.delivery?.batchSize !== void 0) deliveryConfig.batchSize = config.delivery.batchSize;
489
1159
  if (config.onDrop !== void 0) deliveryConfig.onDrop = config.onDrop;
490
1160
  if (config.onError !== void 0) deliveryConfig.onError = config.onError;
491
- this.delivery = createDeliveryManager(storage ? {
492
- ...deliveryConfig,
493
- storage
494
- } : deliveryConfig);
1161
+ this.delivery = createDeliveryManager(deliveryConfig);
495
1162
  this.initialRetryDelayMs = config.delivery?.retryDelayMs ?? 1e3;
496
1163
  this.retryDelayMs = this.initialRetryDelayMs;
497
1164
  this.maxRetryDelayMs = config.delivery?.maxRetryDelayMs ?? 3e4;
498
1165
  this.writeKey = config.writeKey || config.token || "";
499
- this.identity = { anonymousId: createId2("anon") };
1166
+ this.identity = { anonymousId: resolveAnonymousId(this.persistence) };
1167
+ captureAttributionFromUrl(this.persistence);
500
1168
  this.consentState = normalizeConsent(config.gdprConsent ?? config.consent);
501
1169
  this.gdprConsent = this.consentState.analytics;
1170
+ this.installAutocapture();
502
1171
  if (!config.delivery?.disableLifecycleFlush) {
503
1172
  this.installLifecycleFlush();
504
1173
  }
@@ -538,7 +1207,8 @@ var DriveMetaDataSDK = class {
538
1207
  }
539
1208
  }
540
1209
  reset() {
541
- this.identity = { anonymousId: createId2("anon") };
1210
+ this.identity = { anonymousId: resetAnonymousId(this.persistence) };
1211
+ this.session.reset();
542
1212
  this.queue = [];
543
1213
  this.offline = false;
544
1214
  if (this.retryTimer !== void 0) {
@@ -547,8 +1217,20 @@ var DriveMetaDataSDK = class {
547
1217
  }
548
1218
  this.lifecycleCleanup?.();
549
1219
  this.lifecycleCleanup = void 0;
1220
+ this.autocaptureCleanup?.();
1221
+ this.autocaptureCleanup = void 0;
550
1222
  this.delivery.clearQueue("manual_clear");
551
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
+ }
552
1234
  setConsent(consent) {
553
1235
  this.consentState = typeof consent === "object" ? mergeConsent(this.consentState, consent) : normalizeConsent(consent);
554
1236
  this.gdprConsent = this.consentState.analytics;
@@ -563,6 +1245,7 @@ var DriveMetaDataSDK = class {
563
1245
  initialized: this.initialized,
564
1246
  consent: this.gdprConsent,
565
1247
  consentPurposes: this.consentState,
1248
+ persistence: this.persistence.getHealth(),
566
1249
  queueSize: deliveryDiagnostics.queued,
567
1250
  offline: this.offline || deliveryDiagnostics.queued > 0,
568
1251
  droppedEvents: this.droppedEvents + deliveryDiagnostics.dropped.length,
@@ -577,7 +1260,9 @@ var DriveMetaDataSDK = class {
577
1260
  void this.delivery.send(payload).then(() => {
578
1261
  const diagnostics = this.delivery.getDiagnostics();
579
1262
  this.offline = diagnostics.queued > 0;
580
- this.lastError = diagnostics.lastError;
1263
+ if (diagnostics.lastError !== void 0) {
1264
+ this.lastError = diagnostics.lastError;
1265
+ }
581
1266
  if (diagnostics.queued > 0) {
582
1267
  this.scheduleRetryFlush();
583
1268
  }
@@ -609,6 +1294,28 @@ var DriveMetaDataSDK = class {
609
1294
  }
610
1295
  };
611
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
+ }
612
1319
  scheduleRetryFlush() {
613
1320
  if (this.retryTimer !== void 0) return;
614
1321
  const delay = Math.min(this.retryDelayMs, this.maxRetryDelayMs);
@@ -634,7 +1341,7 @@ var DriveMetaDataSDK = class {
634
1341
  type,
635
1342
  event,
636
1343
  properties: sanitizeProperties(properties),
637
- messageId: options.messageId ?? createId2("msg"),
1344
+ messageId: ensureUuid(options.messageId),
638
1345
  timestamp: options.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
639
1346
  context: options.context ?? {},
640
1347
  anonymousId: this.identity.anonymousId,
@@ -642,7 +1349,8 @@ var DriveMetaDataSDK = class {
642
1349
  workspaceId: this.config.workspaceId,
643
1350
  appId: this.config.appId,
644
1351
  writeKey: this.writeKey,
645
- consent: this.consentState
1352
+ consent: this.consentState,
1353
+ sessionId: this.session.getSessionId()
646
1354
  };
647
1355
  if (this.identity.userId !== void 0) prepared.userId = this.identity.userId;
648
1356
  if (this.identity.groupId !== void 0) prepared.groupId = this.identity.groupId;
@@ -666,7 +1374,41 @@ var DriveMetaDataSDK = class {
666
1374
  if (!validation.ok) {
667
1375
  this.lastError = `DMD SDK schema warning: ${validation.errors.join(", ")}`;
668
1376
  }
669
- this.sendEvent(prepared);
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);
1388
+ }
1389
+ toCollectorPayload(prepared) {
1390
+ const browserWindow = getBrowserWindow();
1391
+ return createBackendCollectorPayload({
1392
+ requestId: prepared.messageId,
1393
+ timestamp: prepared.timestamp,
1394
+ eventType: prepared.event,
1395
+ clientId: prepared.clientId,
1396
+ workspaceId: prepared.workspaceId,
1397
+ token: prepared.writeKey,
1398
+ anonymousId: prepared.anonymousId,
1399
+ sessionId: prepared.sessionId,
1400
+ ua: browserWindow?.navigator?.userAgent,
1401
+ appId: prepared.appId,
1402
+ pageUrl: browserWindow?.location?.href,
1403
+ eventData: mergeStoredAttribution({
1404
+ ...prepared.properties,
1405
+ anonymousId: prepared.anonymousId,
1406
+ sessionId: prepared.sessionId,
1407
+ timestamp: prepared.timestamp,
1408
+ requestSentAt: prepared.timestamp,
1409
+ requestReceivedAt: prepared.timestamp
1410
+ }, this.persistence)
1411
+ });
670
1412
  }
671
1413
  recordDrop(type, reason, event) {
672
1414
  this.droppedEvents += 1;
@@ -680,22 +1422,6 @@ var DriveMetaDataSDK = class {
680
1422
  }
681
1423
  };
682
1424
 
683
- // src/browser/legacy-loader.ts
684
- function getLegacySdkInstanceFromWindow() {
685
- const browserWindow = getBrowserWindow();
686
- return browserWindow?.__DriveMetaDataSDKInstance;
687
- }
688
- function ensureLegacySdkLoaded() {
689
- if (!isBrowserRuntime()) {
690
- throw new Error("DMD legacy SDK is only available in a browser runtime");
691
- }
692
- const browserWindow = getBrowserWindow();
693
- if (!browserWindow?.DriveMetaDataSDK) {
694
- throw new Error("DMD legacy SDK constructor is missing from window.DriveMetaDataSDK");
695
- }
696
- return browserWindow.DriveMetaDataSDK;
697
- }
698
-
699
1425
  // src/browser/client.ts
700
1426
  var singleton;
701
1427
  var publicSingleton;
@@ -704,56 +1430,34 @@ var lastError;
704
1430
  var lastDroppedEvent;
705
1431
  function createPublicClient(instance) {
706
1432
  return {
707
- __legacy: instance,
708
1433
  track(event, properties, options) {
709
- if (instance.trackEvent) {
710
- instance.trackEvent(event, properties, options);
711
- return;
712
- }
713
- instance.sendEvent?.({ eventName: event, event, properties, options });
1434
+ instance.trackEvent(event, properties, options);
714
1435
  },
715
1436
  identify(userId, traits, options) {
716
- if (instance.identify) {
717
- instance.identify(userId, traits, options);
718
- return;
719
- }
720
- instance.identifyUser?.(userId, traits);
1437
+ instance.identify(userId, traits, options);
721
1438
  },
722
1439
  page(name, properties, options) {
723
- if (instance.page) {
724
- instance.page(name, properties, options);
725
- return;
726
- }
727
- instance.trackPageview?.();
1440
+ instance.page(name, properties, options);
728
1441
  },
729
1442
  group(groupId, traits, options) {
730
- instance.group?.(groupId, traits, options);
1443
+ instance.group(groupId, traits, options);
731
1444
  },
732
1445
  alias(previousId, userId, options) {
733
- instance.alias?.(previousId, userId, options);
1446
+ instance.alias(previousId, userId, options);
734
1447
  },
735
1448
  async flush() {
736
- await instance.flush?.();
1449
+ await instance.flush();
737
1450
  },
738
1451
  reset() {
739
- instance.reset?.();
1452
+ instance.reset();
740
1453
  singleton = void 0;
741
1454
  publicSingleton = void 0;
742
1455
  },
743
1456
  setConsent(consent) {
744
- if (instance.setConsent) {
745
- instance.setConsent(consent);
746
- return;
747
- }
748
- if (typeof consent === "string") {
749
- instance.gdprConsent = consent;
750
- }
1457
+ instance.setConsent(consent);
751
1458
  },
752
1459
  getHealth() {
753
- if (instance.getHealth) {
754
- return instance.getHealth();
755
- }
756
- return getDmdHealth();
1460
+ return instance.getHealth();
757
1461
  }
758
1462
  };
759
1463
  }
@@ -778,23 +1482,8 @@ function initDmdSDK(config) {
778
1482
  if (publicSingleton) {
779
1483
  return publicSingleton;
780
1484
  }
781
- const legacyConfig = normalizeBrowserConfig(config);
782
- const existingInstance = getLegacySdkInstanceFromWindow();
783
- if (existingInstance) {
784
- return setSingleton(existingInstance);
785
- }
786
1485
  try {
787
- let instance;
788
- try {
789
- const LegacySdk = ensureLegacySdkLoaded();
790
- instance = new LegacySdk(legacyConfig);
791
- } catch (error) {
792
- if (error instanceof Error && error.message.includes("constructor is missing")) {
793
- instance = new DriveMetaDataSDK(config);
794
- } else {
795
- throw error;
796
- }
797
- }
1486
+ const instance = new DriveMetaDataSDK(normalizeBrowserConfig(config));
798
1487
  return setSingleton(instance);
799
1488
  } catch (error) {
800
1489
  lastError = error instanceof Error ? error.message : String(error);
@@ -861,14 +1550,14 @@ function setConsent(consent) {
861
1550
  getDmdSDK()?.setConsent(consent);
862
1551
  }
863
1552
  function getDmdHealth() {
864
- if (singleton?.getHealth) {
1553
+ if (singleton) {
865
1554
  return singleton.getHealth();
866
1555
  }
867
1556
  const health = {
868
- initialized: Boolean(singleton?.initialized ?? singleton),
869
- consent: singleton?.gdprConsent ?? "pending",
870
- queueSize: singleton?.queue?.length ?? 0,
871
- offline: Boolean(singleton?.offline),
1557
+ initialized: false,
1558
+ consent: "pending",
1559
+ queueSize: 0,
1560
+ offline: false,
872
1561
  droppedEvents
873
1562
  };
874
1563
  if (lastError !== void 0) {