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