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