@drivemetadata-ai/sdk 0.1.1-beta.2 → 0.1.1

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.
@@ -45,12 +45,62 @@ module.exports = __toCommonJS(next_exports);
45
45
  // src/react/DmdProvider.tsx
46
46
  var import_react = __toESM(require("react"), 1);
47
47
 
48
+ // src/browser/core/browser-config.ts
49
+ function setIfDefined(target, key, value) {
50
+ if (value !== void 0) {
51
+ target[key] = value;
52
+ }
53
+ }
54
+ function normalizeBrowserConfig(input) {
55
+ const normalized = {
56
+ ...input,
57
+ clientId: input.clientId ?? "",
58
+ workspaceId: input.workspaceId ?? "",
59
+ appId: input.appId ?? ""
60
+ };
61
+ setIfDefined(normalized, "apiHost", input.apiHost);
62
+ setIfDefined(normalized, "capturePageview", input.capturePageview);
63
+ setIfDefined(normalized, "capturePageleave", input.capturePageleave);
64
+ setIfDefined(normalized, "captureDeadClicks", input.captureDeadClicks);
65
+ setIfDefined(normalized, "crossSubdomainCookie", input.crossSubdomainCookie);
66
+ setIfDefined(normalized, "sessionIdleTimeoutSeconds", input.sessionIdleTimeoutSeconds);
67
+ setIfDefined(normalized, "schemaValidation", input.schemaValidation);
68
+ setIfDefined(normalized, "beforeSend", input.beforeSend);
69
+ setIfDefined(normalized, "persistence", input.disablePersistence === true ? "none" : input.persistence);
70
+ return normalized;
71
+ }
72
+
73
+ // src/core/uuid.ts
74
+ function createUuid() {
75
+ const cryptoApi = globalThis.crypto;
76
+ if (typeof cryptoApi?.randomUUID === "function") {
77
+ return cryptoApi.randomUUID();
78
+ }
79
+ const bytes = new Uint8Array(16);
80
+ if (typeof cryptoApi?.getRandomValues === "function") {
81
+ cryptoApi.getRandomValues(bytes);
82
+ } else {
83
+ for (let index = 0; index < bytes.length; index += 1) {
84
+ bytes[index] = Math.floor(Math.random() * 256);
85
+ }
86
+ }
87
+ bytes[6] = (bytes[6] ?? 0) & 15 | 64;
88
+ bytes[8] = (bytes[8] ?? 0) & 63 | 128;
89
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
90
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
91
+ }
92
+ function isUuid(value) {
93
+ 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);
94
+ }
95
+ function ensureUuid(value) {
96
+ return isUuid(value) ? value : createUuid();
97
+ }
98
+
48
99
  // src/core/backend-payload.ts
49
100
  function formatUtcTimestamp(value) {
50
- if (typeof value !== "string") return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
51
- const date = new Date(value);
52
- if (Number.isNaN(date.getTime())) return value;
53
- return date.toISOString().replace("T", " ").slice(0, 19);
101
+ const date = typeof value === "string" || typeof value === "number" || value instanceof Date ? new Date(value) : /* @__PURE__ */ new Date();
102
+ const safeDate = Number.isNaN(date.getTime()) ? /* @__PURE__ */ new Date() : date;
103
+ return safeDate.toISOString().replace("T", " ").slice(0, 19);
54
104
  }
55
105
  function cleanObject(value) {
56
106
  if (Array.isArray(value)) {
@@ -81,15 +131,19 @@ function createBackendCollectorPayload(input) {
81
131
  };
82
132
  const metaData = {
83
133
  ...normalizedEventData,
84
- requestId: input.requestId,
134
+ requestId: ensureUuid(typeof input.requestId === "string" ? input.requestId : void 0),
85
135
  timestamp,
86
136
  eventType: input.eventType,
87
137
  requestFrom: input.requestFrom ?? "3",
88
138
  clientId: input.clientId,
89
139
  workspaceId: input.workspaceId,
90
140
  token: input.token,
91
- anonymousId: eventData.anonymousId ?? input.anonymousId,
92
- sessionId: eventData.sessionId ?? input.sessionId,
141
+ anonymousId: ensureUuid(
142
+ typeof eventData.anonymousId === "string" ? eventData.anonymousId : typeof input.anonymousId === "string" ? input.anonymousId : void 0
143
+ ),
144
+ sessionId: ensureUuid(
145
+ typeof eventData.sessionId === "string" ? eventData.sessionId : typeof input.sessionId === "string" ? input.sessionId : void 0
146
+ ),
93
147
  ua: input.ua,
94
148
  appDetails: { app_id: input.appId },
95
149
  page: { ...page2, url: page2.url ?? input.pageUrl },
@@ -100,12 +154,45 @@ function createBackendCollectorPayload(input) {
100
154
  return cleanObject(payload);
101
155
  }
102
156
 
103
- // src/core/environment.ts
104
- function getBrowserWindow() {
105
- return typeof window === "undefined" ? void 0 : window;
157
+ // src/core/backend-schema.ts
158
+ var requiredMetaDataFields = [
159
+ "requestId",
160
+ "timestamp",
161
+ "eventType",
162
+ "requestFrom",
163
+ "clientId",
164
+ "workspaceId",
165
+ "anonymousId",
166
+ "sessionId"
167
+ ];
168
+ function isPresent(value) {
169
+ return value !== null && value !== void 0 && value !== "";
170
+ }
171
+ function validateBackendCollectorPayload(payload) {
172
+ const errors = [];
173
+ const metaData = payload.metaData;
174
+ if (!metaData || typeof metaData !== "object" || Array.isArray(metaData)) {
175
+ return {
176
+ ok: false,
177
+ errors: ["metaData is required"]
178
+ };
179
+ }
180
+ const metadataRecord = metaData;
181
+ for (const field of requiredMetaDataFields) {
182
+ if (!isPresent(metadataRecord[field])) {
183
+ errors.push(`metaData.${field} is required`);
184
+ }
185
+ }
186
+ if (isPresent(metadataRecord.eventType) && typeof metadataRecord.eventType !== "string") {
187
+ errors.push("metaData.eventType must be a string");
188
+ }
189
+ return {
190
+ ok: errors.length === 0,
191
+ errors
192
+ };
106
193
  }
107
194
 
108
- // src/browser/core/consent.ts
195
+ // src/core/consent.ts
109
196
  var purposes = [
110
197
  "analytics",
111
198
  "advertising",
@@ -145,6 +232,476 @@ function canCollectPurpose(consent, purpose) {
145
232
  return consent[purpose] === "granted";
146
233
  }
147
234
 
235
+ // src/core/event-schema.ts
236
+ var reservedKeys = /* @__PURE__ */ new Set(["messageId", "timestamp", "type", "event", "anonymousId", "userId", "context"]);
237
+ function validateEventEnvelope(envelope, options = {}) {
238
+ if (options.mode === "off") return { ok: true, errors: [] };
239
+ const errors = [];
240
+ if (typeof envelope.type !== "string") errors.push("type must be a string");
241
+ if (envelope.type === "track" && typeof envelope.event !== "string") {
242
+ errors.push("track event must include event name");
243
+ }
244
+ if (typeof envelope.messageId !== "string") errors.push("messageId must be a string");
245
+ if (typeof envelope.timestamp !== "string") errors.push("timestamp must be an ISO string");
246
+ for (const key of Object.keys(envelope.properties ?? {})) {
247
+ if (reservedKeys.has(key)) errors.push(`properties.${key} is reserved`);
248
+ }
249
+ return { ok: errors.length === 0, errors };
250
+ }
251
+
252
+ // src/core/environment.ts
253
+ function getBrowserWindow() {
254
+ return typeof window === "undefined" ? void 0 : window;
255
+ }
256
+
257
+ // src/core/privacy.ts
258
+ var sensitiveKeys = /* @__PURE__ */ new Set([
259
+ "email",
260
+ "phone",
261
+ "mobile",
262
+ "address",
263
+ "address1",
264
+ "address2",
265
+ "first_name",
266
+ "last_name",
267
+ "name",
268
+ "token",
269
+ "secret",
270
+ "password",
271
+ "session",
272
+ "cookie"
273
+ ]);
274
+ function redactUrl(url, allowQueryKeys = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"]) {
275
+ const parsed = new URL(url, "https://placeholder.local");
276
+ for (const key of Array.from(parsed.searchParams.keys())) {
277
+ if (!allowQueryKeys.includes(key)) {
278
+ parsed.searchParams.set(key, "[REDACTED]");
279
+ }
280
+ }
281
+ return url.startsWith("http") ? parsed.toString() : `${parsed.pathname}${parsed.search}`;
282
+ }
283
+ function sanitizeValue(value, allow, path = []) {
284
+ if (Array.isArray(value)) {
285
+ return value.map((item) => sanitizeValue(item, allow, path));
286
+ }
287
+ if (value && typeof value === "object") {
288
+ return Object.fromEntries(
289
+ Object.entries(value).filter(([key]) => {
290
+ const lowerKey = key.toLowerCase();
291
+ const lowerPath = path.map((item) => item.toLowerCase());
292
+ const isEcommerceItemName = lowerKey === "name" && lowerPath.includes("ecommerce") && lowerPath.includes("items");
293
+ return isEcommerceItemName || !sensitiveKeys.has(lowerKey) || allow.has(lowerKey);
294
+ }).map(([key, nestedValue]) => [key, sanitizeValue(nestedValue, allow, [...path, key])])
295
+ );
296
+ }
297
+ return value;
298
+ }
299
+ function sanitizeProperties(input, allowRawKeys = []) {
300
+ const allow = new Set(allowRawKeys.map((key) => key.toLowerCase()));
301
+ return sanitizeValue(input, allow);
302
+ }
303
+
304
+ // src/browser/core/attribute-persistence.ts
305
+ var DMD_ATTRIBUTES_STORAGE_KEY = "dmd_attributes";
306
+ var SESSION_STORAGE_KEY = "sessionData";
307
+ var SESSION_ID_KEY = "sessionId";
308
+ var SESSION_TIMESTAMP_KEY = "timestamp";
309
+ function isBlankValue(value) {
310
+ return value === null || value === void 0 || typeof value === "string" && value.trim() === "";
311
+ }
312
+ function readAttributes(base) {
313
+ const raw = base.getItem(DMD_ATTRIBUTES_STORAGE_KEY);
314
+ if (!raw) return {};
315
+ try {
316
+ const parsed = JSON.parse(raw);
317
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
318
+ } catch {
319
+ base.removeItem(DMD_ATTRIBUTES_STORAGE_KEY);
320
+ return {};
321
+ }
322
+ }
323
+ function sanitizeAttributes(attributes) {
324
+ return Object.fromEntries(Object.entries(attributes).filter(([, value]) => !isBlankValue(value)));
325
+ }
326
+ function writeAttributes(base, attributes) {
327
+ const sanitized = sanitizeAttributes(attributes);
328
+ if (Object.keys(sanitized).length === 0) {
329
+ base.removeItem(DMD_ATTRIBUTES_STORAGE_KEY);
330
+ return;
331
+ }
332
+ base.setItem(DMD_ATTRIBUTES_STORAGE_KEY, JSON.stringify(sanitized));
333
+ }
334
+ function parseSessionData(value) {
335
+ try {
336
+ const parsed = JSON.parse(value);
337
+ const sessionData = {};
338
+ const sessionId = typeof parsed.sessionId === "string" && parsed.sessionId.trim() !== "" ? parsed.sessionId : void 0;
339
+ const timestamp = typeof parsed.timestamp === "number" ? parsed.timestamp : void 0;
340
+ if (sessionId !== void 0) sessionData.sessionId = sessionId;
341
+ if (timestamp !== void 0) sessionData.timestamp = timestamp;
342
+ return sessionData;
343
+ } catch {
344
+ return {};
345
+ }
346
+ }
347
+ function getAggregateValue(attributes, key) {
348
+ if (key === SESSION_STORAGE_KEY) {
349
+ const sessionId = attributes[SESSION_ID_KEY];
350
+ const timestamp = attributes[SESSION_TIMESTAMP_KEY];
351
+ if (typeof sessionId === "string" && !isBlankValue(sessionId) && typeof timestamp === "number") {
352
+ return JSON.stringify({ sessionId, timestamp });
353
+ }
354
+ return null;
355
+ }
356
+ const value = attributes[key];
357
+ if (isBlankValue(value)) return null;
358
+ return typeof value === "string" ? value : JSON.stringify(value);
359
+ }
360
+ function setAggregateValue(attributes, key, value) {
361
+ if (key === SESSION_STORAGE_KEY) {
362
+ const sessionData = parseSessionData(value);
363
+ if (sessionData.sessionId === void 0 || sessionData.timestamp === void 0) {
364
+ delete attributes[SESSION_ID_KEY];
365
+ delete attributes[SESSION_TIMESTAMP_KEY];
366
+ return;
367
+ }
368
+ attributes[SESSION_ID_KEY] = sessionData.sessionId;
369
+ attributes[SESSION_TIMESTAMP_KEY] = sessionData.timestamp;
370
+ return;
371
+ }
372
+ attributes[key] = value;
373
+ }
374
+ function removeAggregateValue(attributes, key) {
375
+ if (key === SESSION_STORAGE_KEY) {
376
+ delete attributes[SESSION_ID_KEY];
377
+ delete attributes[SESSION_TIMESTAMP_KEY];
378
+ return;
379
+ }
380
+ delete attributes[key];
381
+ }
382
+ function createDmdAttributesPersistence(base) {
383
+ return {
384
+ getItem(key) {
385
+ if (key === DMD_ATTRIBUTES_STORAGE_KEY) return base.getItem(key);
386
+ const attributes = readAttributes(base);
387
+ const aggregateValue = getAggregateValue(attributes, key);
388
+ if (aggregateValue !== null) return aggregateValue;
389
+ const legacyValue = base.getItem(key);
390
+ if (isBlankValue(legacyValue)) return null;
391
+ setAggregateValue(attributes, key, String(legacyValue));
392
+ writeAttributes(base, attributes);
393
+ return legacyValue;
394
+ },
395
+ setItem(key, value) {
396
+ if (key === DMD_ATTRIBUTES_STORAGE_KEY) return base.setItem(key, value);
397
+ const attributes = readAttributes(base);
398
+ if (isBlankValue(value)) {
399
+ removeAggregateValue(attributes, key);
400
+ writeAttributes(base, attributes);
401
+ base.removeItem(key);
402
+ return true;
403
+ }
404
+ setAggregateValue(attributes, key, value);
405
+ writeAttributes(base, attributes);
406
+ base.setItem(key, value);
407
+ return true;
408
+ },
409
+ removeItem(key) {
410
+ if (key === DMD_ATTRIBUTES_STORAGE_KEY) {
411
+ base.removeItem(key);
412
+ return;
413
+ }
414
+ const attributes = readAttributes(base);
415
+ removeAggregateValue(attributes, key);
416
+ writeAttributes(base, attributes);
417
+ base.removeItem(key);
418
+ },
419
+ getHealth() {
420
+ return base.getHealth();
421
+ }
422
+ };
423
+ }
424
+
425
+ // src/core/attribution.ts
426
+ var dmdUtmKeys = [
427
+ "utm_source",
428
+ "utm_medium",
429
+ "utm_campaign",
430
+ "utm_term",
431
+ "utm_content",
432
+ "utm_id"
433
+ ];
434
+ var dmdCampaignKeys = ["campaign_id", "ad_id"];
435
+ var dmdClickIdSources = [
436
+ ["gclid", 2],
437
+ ["fbclid", 3],
438
+ ["ScCid", 1],
439
+ ["li_fat_id", 4]
440
+ ];
441
+ function cleanAttributionRecord(record) {
442
+ const cleaned = Object.fromEntries(
443
+ Object.entries(record).filter(([, value]) => value !== null && value !== void 0 && value !== "")
444
+ );
445
+ return Object.keys(cleaned).length > 0 ? cleaned : void 0;
446
+ }
447
+ function objectValue(value) {
448
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
449
+ }
450
+ function mergeAttributionRecords(explicitProperties, stored) {
451
+ const attributionData = objectValue(explicitProperties.attributionData);
452
+ const utmParameter = objectValue(explicitProperties.utmParameter);
453
+ return {
454
+ ...explicitProperties,
455
+ ...stored.attributionData || attributionData ? { attributionData: { ...stored.attributionData ?? {}, ...attributionData ?? {} } } : {},
456
+ ...stored.utmParameter || utmParameter ? { utmParameter: { ...stored.utmParameter ?? {}, ...utmParameter ?? {} } } : {}
457
+ };
458
+ }
459
+
460
+ // src/browser/core/attribution.ts
461
+ var DMD_CLICK_ID_QUERY_KEY = "click_id";
462
+ var DMD_CLICK_ID_LEGACY_QUERY_KEY = "dmd_click_id";
463
+ var DMD_CLICK_ID_STORAGE_KEY = "dmd_click_id";
464
+ var DMD_CLICK_ID_ATTRIBUTE_KEY = "click_id";
465
+ function getCurrentUrl() {
466
+ return getBrowserWindow()?.location?.href;
467
+ }
468
+ function getCookie(name) {
469
+ const cookie = getBrowserWindow()?.document?.cookie;
470
+ if (!cookie) return void 0;
471
+ const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
472
+ const match = new RegExp(`(?:^|; )${escapedName}=([^;]*)`).exec(cookie);
473
+ return match ? decodeURIComponent(match[1] ?? "") : void 0;
474
+ }
475
+ function getParams(url) {
476
+ return new URL(url, "https://placeholder.local").searchParams;
477
+ }
478
+ function setIfPresent(persistence, key, value) {
479
+ if (value !== null && value !== "") {
480
+ persistence.setItem(key, value);
481
+ }
482
+ }
483
+ function getClickIdParam(params) {
484
+ return params.get(DMD_CLICK_ID_LEGACY_QUERY_KEY) || params.get(DMD_CLICK_ID_QUERY_KEY);
485
+ }
486
+ function getStoredString(persistence, key) {
487
+ const value = persistence.getItem(key);
488
+ return value === null || value === "" ? void 0 : value;
489
+ }
490
+ function getStoredNumberOrString(persistence, key) {
491
+ const value = getStoredString(persistence, key);
492
+ if (value === void 0) return void 0;
493
+ const parsed = Number.parseInt(value, 10);
494
+ return Number.isNaN(parsed) ? value : parsed;
495
+ }
496
+ function captureAttributionFromUrl(persistence, url = getCurrentUrl()) {
497
+ if (!url) return;
498
+ const params = getParams(url);
499
+ for (const key of dmdUtmKeys) {
500
+ setIfPresent(persistence, key, params.get(key));
501
+ }
502
+ for (const key of dmdCampaignKeys) {
503
+ setIfPresent(persistence, key, params.get(key));
504
+ }
505
+ const clickId = getClickIdParam(params);
506
+ setIfPresent(persistence, DMD_CLICK_ID_ATTRIBUTE_KEY, clickId);
507
+ setIfPresent(persistence, DMD_CLICK_ID_STORAGE_KEY, clickId);
508
+ for (const [param, sdkPubId] of dmdClickIdSources) {
509
+ const value = params.get(param);
510
+ if (value) {
511
+ persistence.setItem("unique_id", value);
512
+ persistence.setItem("sdk_pub_id", String(sdkPubId));
513
+ }
514
+ }
515
+ }
516
+ function getStoredAttributionData(persistence) {
517
+ return cleanAttributionRecord({
518
+ unique_id: getStoredString(persistence, "unique_id"),
519
+ sdk_pub_id: getStoredNumberOrString(persistence, "sdk_pub_id"),
520
+ fbc: getStoredString(persistence, "fbc") ?? getCookie("_fbc"),
521
+ fbp: getStoredString(persistence, "fbp") ?? getCookie("_fbp"),
522
+ campaign_id: getStoredString(persistence, "campaign_id"),
523
+ ad_id: getStoredString(persistence, "ad_id"),
524
+ click_id: getStoredString(persistence, DMD_CLICK_ID_STORAGE_KEY) ?? getStoredString(persistence, DMD_CLICK_ID_ATTRIBUTE_KEY)
525
+ });
526
+ }
527
+ function getStoredUtmParameter(persistence) {
528
+ return cleanAttributionRecord(Object.fromEntries(dmdUtmKeys.map((key) => [key, getStoredString(persistence, key)])));
529
+ }
530
+ function mergeStoredAttribution(properties, persistence) {
531
+ const stored = {};
532
+ const attributionData = getStoredAttributionData(persistence);
533
+ const utmParameter = getStoredUtmParameter(persistence);
534
+ if (attributionData !== void 0) stored.attributionData = attributionData;
535
+ if (utmParameter !== void 0) stored.utmParameter = utmParameter;
536
+ return mergeAttributionRecords(properties, stored);
537
+ }
538
+
539
+ // src/browser/core/autocapture.ts
540
+ function installAutocapture(config) {
541
+ const trackedPageUrls = /* @__PURE__ */ new Set();
542
+ const timers = /* @__PURE__ */ new Set();
543
+ const pageviewDelayMs = config.pageviewDelayMs ?? 500;
544
+ const history = config.browserWindow.history;
545
+ const originalPushState = history?.pushState;
546
+ const originalReplaceState = history?.replaceState;
547
+ function clearTimer(timer) {
548
+ timers.delete(timer);
549
+ clearTimeout(timer);
550
+ }
551
+ function canonicalUrl() {
552
+ return config.browserWindow.location.href;
553
+ }
554
+ function trackCurrentPageview() {
555
+ if (!config.capturePageview) return;
556
+ const url = canonicalUrl();
557
+ if (trackedPageUrls.has(url)) return;
558
+ trackedPageUrls.add(url);
559
+ config.onPageView();
560
+ }
561
+ function schedulePageview(delayMs) {
562
+ if (!config.capturePageview) return;
563
+ const timer = setTimeout(() => {
564
+ timers.delete(timer);
565
+ trackCurrentPageview();
566
+ }, delayMs);
567
+ timers.add(timer);
568
+ }
569
+ function handleRouteChange() {
570
+ config.onRouteChange?.();
571
+ trackCurrentPageview();
572
+ }
573
+ function wrapHistoryMethod(method) {
574
+ if (!history) return;
575
+ const original = history[method];
576
+ if (typeof original !== "function") return;
577
+ history[method] = function wrappedHistoryMethod(...args) {
578
+ const result = original.apply(this, args);
579
+ const timer = setTimeout(() => {
580
+ timers.delete(timer);
581
+ handleRouteChange();
582
+ }, 0);
583
+ timers.add(timer);
584
+ return result;
585
+ };
586
+ }
587
+ function handlePopOrHashChange() {
588
+ handleRouteChange();
589
+ }
590
+ function handlePageLeave() {
591
+ if (config.capturePageleave) {
592
+ config.onPageLeave();
593
+ }
594
+ }
595
+ schedulePageview(pageviewDelayMs);
596
+ wrapHistoryMethod("pushState");
597
+ wrapHistoryMethod("replaceState");
598
+ const canListen = typeof config.browserWindow.addEventListener === "function" && typeof config.browserWindow.removeEventListener === "function";
599
+ if (canListen) {
600
+ config.browserWindow.addEventListener("popstate", handlePopOrHashChange);
601
+ config.browserWindow.addEventListener("hashchange", handlePopOrHashChange);
602
+ config.browserWindow.addEventListener("beforeunload", handlePageLeave);
603
+ }
604
+ return {
605
+ cleanup() {
606
+ for (const timer of Array.from(timers)) {
607
+ clearTimer(timer);
608
+ }
609
+ if (history && originalPushState) history.pushState = originalPushState;
610
+ if (history && originalReplaceState) history.replaceState = originalReplaceState;
611
+ if (canListen) {
612
+ config.browserWindow.removeEventListener("popstate", handlePopOrHashChange);
613
+ config.browserWindow.removeEventListener("hashchange", handlePopOrHashChange);
614
+ config.browserWindow.removeEventListener("beforeunload", handlePageLeave);
615
+ }
616
+ }
617
+ };
618
+ }
619
+
620
+ // src/core/payload-size.ts
621
+ var preserveStringKeys = /* @__PURE__ */ new Set([
622
+ "requestId",
623
+ "eventType",
624
+ "requestFrom",
625
+ "clientId",
626
+ "workspaceId",
627
+ "anonymousId",
628
+ "sessionId",
629
+ "token"
630
+ ]);
631
+ function payloadByteLength(payload) {
632
+ const serialized = JSON.stringify(payload);
633
+ if (typeof TextEncoder !== "undefined") {
634
+ return new TextEncoder().encode(serialized).length;
635
+ }
636
+ return serialized.length;
637
+ }
638
+ function clonePayload(payload) {
639
+ try {
640
+ return JSON.parse(JSON.stringify(payload));
641
+ } catch {
642
+ return void 0;
643
+ }
644
+ }
645
+ function truncateValue(value, truncateStringLength, key) {
646
+ if (typeof value === "string") {
647
+ if (key && preserveStringKeys.has(key)) return value;
648
+ if (value.length <= truncateStringLength) return value;
649
+ return `${value.slice(0, truncateStringLength)}...[TRUNCATED]`;
650
+ }
651
+ if (Array.isArray(value)) {
652
+ return value.map((item) => truncateValue(item, truncateStringLength));
653
+ }
654
+ if (value && typeof value === "object") {
655
+ return Object.fromEntries(
656
+ Object.entries(value).map(([childKey, childValue]) => [
657
+ childKey,
658
+ truncateValue(childValue, truncateStringLength, childKey)
659
+ ])
660
+ );
661
+ }
662
+ return value;
663
+ }
664
+ function markPayloadTruncated(payload) {
665
+ if (payload.metaData && typeof payload.metaData === "object" && !Array.isArray(payload.metaData)) {
666
+ return {
667
+ ...payload,
668
+ metaData: {
669
+ ...payload.metaData,
670
+ payloadTruncated: true
671
+ }
672
+ };
673
+ }
674
+ return {
675
+ ...payload,
676
+ payloadTruncated: true
677
+ };
678
+ }
679
+ function truncatePayload(payload, truncateStringLength) {
680
+ const cloned = clonePayload(payload);
681
+ if (!cloned) return void 0;
682
+ return markPayloadTruncated(truncateValue(cloned, truncateStringLength));
683
+ }
684
+ function getPayloadMessageId(payload) {
685
+ const value = payload.metaData?.requestId ?? payload.messageId;
686
+ return value === void 0 ? void 0 : String(value);
687
+ }
688
+ function preparePayloadForSizePolicy(payload, options = {}) {
689
+ const maxPayloadBytes = options.maxPayloadBytes ?? 64e3;
690
+ const payloadSizePolicy = options.payloadSizePolicy ?? "drop";
691
+ const payloadTruncateStringLength = options.payloadTruncateStringLength ?? 1024;
692
+ if (payloadByteLength(payload) <= maxPayloadBytes) {
693
+ return { ok: true, payload, truncated: false };
694
+ }
695
+ if (payloadSizePolicy === "truncate") {
696
+ const truncatedPayload = truncatePayload(payload, payloadTruncateStringLength);
697
+ if (truncatedPayload && payloadByteLength(truncatedPayload) <= maxPayloadBytes) {
698
+ return { ok: true, payload: truncatedPayload, truncated: true };
699
+ }
700
+ }
701
+ const messageId = getPayloadMessageId(payload);
702
+ return messageId === void 0 ? { ok: false, reason: "payload_too_large" } : { ok: false, reason: "payload_too_large", messageId };
703
+ }
704
+
148
705
  // src/browser/core/delivery.ts
149
706
  function createId(prefix) {
150
707
  return `${prefix}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
@@ -176,7 +733,6 @@ function createDeliveryManager(config) {
176
733
  const lockTtlMs = config.lockTtlMs ?? 5e3;
177
734
  const tabId = config.tabId ?? createId("tab");
178
735
  const batchSize = config.batchSize ?? 25;
179
- const maxPayloadBytes = config.maxPayloadBytes ?? 64e3;
180
736
  function recordDrop(event) {
181
737
  diagnostics.dropped.push(event);
182
738
  config.onDrop?.(event);
@@ -185,12 +741,22 @@ function createDeliveryManager(config) {
185
741
  diagnostics.lastError = error.message;
186
742
  config.onError?.(error);
187
743
  }
188
- function payloadByteLength(payload) {
189
- const serialized = JSON.stringify(payload);
190
- if (typeof TextEncoder !== "undefined") {
191
- return new TextEncoder().encode(serialized).length;
744
+ function preparePayloadForSend(payload) {
745
+ const sizePolicyOptions = {};
746
+ if (config.maxPayloadBytes !== void 0) sizePolicyOptions.maxPayloadBytes = config.maxPayloadBytes;
747
+ if (config.payloadSizePolicy !== void 0) sizePolicyOptions.payloadSizePolicy = config.payloadSizePolicy;
748
+ if (config.payloadTruncateStringLength !== void 0) {
749
+ sizePolicyOptions.payloadTruncateStringLength = config.payloadTruncateStringLength;
192
750
  }
193
- return serialized.length;
751
+ const prepared = preparePayloadForSizePolicy(payload, sizePolicyOptions);
752
+ if (prepared.ok) return prepared.payload;
753
+ const diagnostic = {
754
+ reason: prepared.reason,
755
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
756
+ };
757
+ if (prepared.messageId !== void 0) diagnostic.messageId = prepared.messageId;
758
+ recordDrop(diagnostic);
759
+ return void 0;
194
760
  }
195
761
  function recordStorageUnavailable() {
196
762
  recordDrop({
@@ -280,7 +846,19 @@ function createDeliveryManager(config) {
280
846
  idempotencyKey: String(payload.idempotencyKey ?? createIdempotencyKey(payload, messageId))
281
847
  };
282
848
  }
849
+ function tryBeacon(body) {
850
+ if (!config.useBeacon) return false;
851
+ const sendBeacon = globalThis.navigator?.sendBeacon;
852
+ if (typeof sendBeacon !== "function") return false;
853
+ try {
854
+ const blob = new Blob([JSON.stringify(body)], { type: "application/json" });
855
+ return sendBeacon.call(globalThis.navigator, config.endpoint, blob);
856
+ } catch {
857
+ return false;
858
+ }
859
+ }
283
860
  async function deliver(body) {
861
+ if (tryBeacon(body)) return;
284
862
  const fetchImpl = config.fetch ?? globalThis.fetch;
285
863
  if (typeof fetchImpl !== "function") {
286
864
  throw new Error("fetch_unavailable");
@@ -297,26 +875,22 @@ function createDeliveryManager(config) {
297
875
  return {
298
876
  async send(payload) {
299
877
  const body = withEnvelope(payload);
300
- if (payloadByteLength(body) > maxPayloadBytes) {
301
- recordDrop({
302
- messageId: String(body.metaData?.requestId ?? body.messageId),
303
- reason: "payload_too_large",
304
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
305
- });
878
+ const preparedBody = preparePayloadForSend(body);
879
+ if (!preparedBody) {
306
880
  return;
307
881
  }
308
882
  diagnostics.inFlight += 1;
309
883
  try {
310
- await deliver(body);
884
+ await deliver(preparedBody);
311
885
  } catch (error) {
312
886
  const deliveryError = error instanceof Error ? error : new Error(String(error));
313
887
  recordError(deliveryError);
314
888
  enqueue({
315
- messageId: String(body.metaData?.requestId ?? body.messageId),
889
+ messageId: String(getPayloadMessageId(body)),
316
890
  savedAt: Date.now(),
317
891
  attempts: 1,
318
892
  lastError: deliveryError.message,
319
- payload: body
893
+ payload: preparedBody
320
894
  });
321
895
  } finally {
322
896
  diagnostics.inFlight -= 1;
@@ -429,60 +1003,286 @@ function createDeliveryManager(config) {
429
1003
  };
430
1004
  }
431
1005
 
432
- // src/browser/core/privacy.ts
433
- var sensitiveKeys = /* @__PURE__ */ new Set([
434
- "email",
435
- "phone",
436
- "mobile",
437
- "address",
438
- "address1",
439
- "address2",
440
- "first_name",
441
- "last_name",
442
- "name",
443
- "token",
444
- "secret",
445
- "password",
446
- "session",
447
- "cookie"
448
- ]);
449
- function sanitizeValue(value, allow) {
450
- if (Array.isArray(value)) {
451
- return value.map((item) => sanitizeValue(item, allow));
1006
+ // src/browser/core/identity.ts
1007
+ var ANONYMOUS_ID_STORAGE_KEY = "dmd_anonymous_id";
1008
+ var LEGACY_ANONYMOUS_ID_STORAGE_KEY = "anonymousId";
1009
+ function getUrlParam(name) {
1010
+ const href = getBrowserWindow()?.location?.href;
1011
+ if (!href) return null;
1012
+ try {
1013
+ return new URL(href).searchParams.get(name);
1014
+ } catch {
1015
+ return null;
452
1016
  }
453
- if (value && typeof value === "object") {
454
- return Object.fromEntries(
455
- Object.entries(value).filter(([key]) => !sensitiveKeys.has(key.toLowerCase()) || allow.has(key.toLowerCase())).map(([key, nestedValue]) => [key, sanitizeValue(nestedValue, allow)])
456
- );
1017
+ }
1018
+ function resolveAnonymousId(persistence) {
1019
+ const urlAnonymousId = getUrlParam("aid");
1020
+ if (isUuid(urlAnonymousId)) {
1021
+ persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, urlAnonymousId);
1022
+ return urlAnonymousId;
457
1023
  }
458
- return value;
1024
+ const storedAnonymousId = persistence.getItem(ANONYMOUS_ID_STORAGE_KEY);
1025
+ if (isUuid(storedAnonymousId)) {
1026
+ return storedAnonymousId;
1027
+ }
1028
+ const legacyAnonymousId = persistence.getItem(LEGACY_ANONYMOUS_ID_STORAGE_KEY);
1029
+ if (isUuid(legacyAnonymousId)) {
1030
+ persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, legacyAnonymousId);
1031
+ return legacyAnonymousId;
1032
+ }
1033
+ const anonymousId = createUuid();
1034
+ persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, anonymousId);
1035
+ return anonymousId;
459
1036
  }
460
- function sanitizeProperties(input, allowRawKeys = []) {
461
- const allow = new Set(allowRawKeys.map((key) => key.toLowerCase()));
462
- return sanitizeValue(input, allow);
1037
+ function resetAnonymousId(persistence) {
1038
+ const anonymousId = createUuid();
1039
+ persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, anonymousId);
1040
+ return anonymousId;
463
1041
  }
464
1042
 
465
- // src/browser/core/schema.ts
466
- var reservedKeys = /* @__PURE__ */ new Set(["messageId", "timestamp", "type", "event", "anonymousId", "userId", "context"]);
467
- function validateEventEnvelope(envelope, options = {}) {
468
- if (options.mode === "off") return { ok: true, errors: [] };
469
- const errors = [];
470
- if (typeof envelope.type !== "string") errors.push("type must be a string");
471
- if (envelope.type === "track" && typeof envelope.event !== "string") {
472
- errors.push("track event must include event name");
1043
+ // src/browser/core/page-context.ts
1044
+ function redactSearch(search) {
1045
+ const redacted = redactUrl(search);
1046
+ return redacted.startsWith("/?") ? redacted.slice(1) : redacted;
1047
+ }
1048
+ function getBrowserPageContext() {
1049
+ const browserWindow = getBrowserWindow();
1050
+ const documentRef = browserWindow?.document ?? globalThis.document;
1051
+ const location = browserWindow?.location;
1052
+ const page2 = {};
1053
+ if (location?.href) page2.url = location.href;
1054
+ if (location?.pathname) page2.path = location.pathname;
1055
+ if (location?.search) page2.search = redactSearch(location.search);
1056
+ if (documentRef?.title) page2.title = documentRef.title;
1057
+ if (documentRef?.referrer) page2.referrer = redactUrl(documentRef.referrer);
1058
+ return page2;
1059
+ }
1060
+
1061
+ // src/browser/core/persistence.ts
1062
+ function createPersistenceHealth(requested) {
1063
+ return {
1064
+ requested,
1065
+ cookieFallbackUsed: false,
1066
+ memoryFallbackUsed: requested === "memory",
1067
+ failures: []
1068
+ };
1069
+ }
1070
+ function recordFailure(health, backend, operation, error) {
1071
+ health.failures.push({
1072
+ backend,
1073
+ operation,
1074
+ message: error instanceof Error ? error.message : String(error)
1075
+ });
1076
+ if (health.failures.length > 25) {
1077
+ health.failures.shift();
473
1078
  }
474
- if (typeof envelope.messageId !== "string") errors.push("messageId must be a string");
475
- if (typeof envelope.timestamp !== "string") errors.push("timestamp must be an ISO string");
476
- for (const key of Object.keys(envelope.properties ?? {})) {
477
- if (reservedKeys.has(key)) errors.push(`properties.${key} is reserved`);
1079
+ }
1080
+ function createMemoryPersistence(health = createPersistenceHealth("memory")) {
1081
+ const memory = {};
1082
+ return {
1083
+ getItem(key) {
1084
+ return Object.prototype.hasOwnProperty.call(memory, key) ? memory[key] ?? null : null;
1085
+ },
1086
+ setItem(key, value) {
1087
+ memory[key] = value;
1088
+ return true;
1089
+ },
1090
+ removeItem(key) {
1091
+ delete memory[key];
1092
+ },
1093
+ getHealth() {
1094
+ return {
1095
+ ...health,
1096
+ failures: health.failures.map((failure) => ({ ...failure }))
1097
+ };
1098
+ }
1099
+ };
1100
+ }
1101
+ function getCookie2(name) {
1102
+ const browserWindow = getBrowserWindow();
1103
+ const cookie = browserWindow?.document?.cookie;
1104
+ if (!cookie) return null;
1105
+ const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1106
+ const match = new RegExp(`(?:^|; )${escapedName}=([^;]*)`).exec(cookie);
1107
+ return match ? decodeURIComponent(match[1] ?? "") : null;
1108
+ }
1109
+ function setCookie(name, value) {
1110
+ const browserWindow = getBrowserWindow();
1111
+ if (!browserWindow?.document) return false;
1112
+ try {
1113
+ const expires = new Date(Date.now() + 365 * 24 * 60 * 60 * 1e3).toUTCString();
1114
+ const secure = browserWindow.location?.protocol === "https:" ? "; Secure" : "";
1115
+ browserWindow.document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/${secure}; SameSite=Lax`;
1116
+ return true;
1117
+ } catch {
1118
+ return false;
478
1119
  }
479
- return { ok: errors.length === 0, errors };
1120
+ }
1121
+ function removeCookie(name) {
1122
+ const browserWindow = getBrowserWindow();
1123
+ if (!browserWindow?.document) return;
1124
+ browserWindow.document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
1125
+ }
1126
+ function getLocalStorage() {
1127
+ const browserWindow = getBrowserWindow();
1128
+ try {
1129
+ return browserWindow?.localStorage;
1130
+ } catch {
1131
+ return void 0;
1132
+ }
1133
+ }
1134
+ function getSessionStorage() {
1135
+ const browserWindow = getBrowserWindow();
1136
+ try {
1137
+ return browserWindow?.sessionStorage;
1138
+ } catch {
1139
+ return void 0;
1140
+ }
1141
+ }
1142
+ function createBrowserPersistence(mode = "localStorage+cookie") {
1143
+ const health = createPersistenceHealth(mode);
1144
+ const memory = createMemoryPersistence(health);
1145
+ if (mode === "none") {
1146
+ return {
1147
+ getItem() {
1148
+ return null;
1149
+ },
1150
+ setItem() {
1151
+ return false;
1152
+ },
1153
+ removeItem() {
1154
+ },
1155
+ getHealth() {
1156
+ return {
1157
+ ...health,
1158
+ failures: health.failures.map((failure) => ({ ...failure }))
1159
+ };
1160
+ }
1161
+ };
1162
+ }
1163
+ if (mode === "memory") return memory;
1164
+ const primary = mode === "sessionStorage" ? getSessionStorage() : getLocalStorage();
1165
+ const useCookie = mode === "cookie" || mode === "localStorage+cookie" || !primary;
1166
+ return {
1167
+ getItem(key) {
1168
+ try {
1169
+ const stored = primary?.getItem(key);
1170
+ if (stored) return stored;
1171
+ } catch (error) {
1172
+ recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "get", error);
1173
+ }
1174
+ if (useCookie) {
1175
+ const cookie = getCookie2(key);
1176
+ if (cookie) {
1177
+ health.cookieFallbackUsed = true;
1178
+ return cookie;
1179
+ }
1180
+ }
1181
+ const value = memory.getItem(key);
1182
+ if (value !== null) health.memoryFallbackUsed = true;
1183
+ return value;
1184
+ },
1185
+ setItem(key, value) {
1186
+ let wrote = false;
1187
+ try {
1188
+ primary?.setItem(key, value);
1189
+ wrote = primary !== void 0;
1190
+ } catch (error) {
1191
+ recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "set", error);
1192
+ wrote = false;
1193
+ }
1194
+ if (useCookie) {
1195
+ const cookieWrote = setCookie(key, value);
1196
+ if (cookieWrote) health.cookieFallbackUsed = true;
1197
+ wrote = cookieWrote || wrote;
1198
+ }
1199
+ if (!wrote) {
1200
+ health.memoryFallbackUsed = true;
1201
+ return memory.setItem(key, value);
1202
+ }
1203
+ if (mode === "localStorage+cookie") {
1204
+ memory.setItem(key, value);
1205
+ }
1206
+ return true;
1207
+ },
1208
+ removeItem(key) {
1209
+ try {
1210
+ primary?.removeItem(key);
1211
+ } catch (error) {
1212
+ recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "remove", error);
1213
+ }
1214
+ if (useCookie) removeCookie(key);
1215
+ memory.removeItem(key);
1216
+ },
1217
+ getHealth() {
1218
+ return {
1219
+ ...health,
1220
+ failures: health.failures.map((failure) => ({ ...failure }))
1221
+ };
1222
+ }
1223
+ };
480
1224
  }
481
1225
 
482
- // src/browser/core/DriveMetaDataSDK.ts
483
- function createId2(prefix) {
484
- return `${prefix}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
1226
+ // src/browser/core/session.ts
1227
+ var SESSION_STORAGE_KEY2 = "sessionData";
1228
+ function getUrlParam2(name) {
1229
+ const href = getBrowserWindow()?.location?.href;
1230
+ if (!href) return null;
1231
+ try {
1232
+ return new URL(href).searchParams.get(name);
1233
+ } catch {
1234
+ return null;
1235
+ }
485
1236
  }
1237
+ function readStoredSession(persistence) {
1238
+ const rawSession = persistence.getItem(SESSION_STORAGE_KEY2);
1239
+ if (!rawSession) return void 0;
1240
+ try {
1241
+ const parsed = JSON.parse(rawSession);
1242
+ if (isUuid(parsed.sessionId) && typeof parsed.timestamp === "number") {
1243
+ return {
1244
+ sessionId: parsed.sessionId,
1245
+ timestamp: parsed.timestamp
1246
+ };
1247
+ }
1248
+ } catch {
1249
+ persistence.removeItem(SESSION_STORAGE_KEY2);
1250
+ }
1251
+ return void 0;
1252
+ }
1253
+ function writeStoredSession(persistence, sessionId, timestamp) {
1254
+ persistence.setItem(SESSION_STORAGE_KEY2, JSON.stringify({ sessionId, timestamp }));
1255
+ }
1256
+ function createSessionManager(persistence, idleTimeoutSeconds = 30 * 60) {
1257
+ function resolveSessionId() {
1258
+ const now = Date.now();
1259
+ const urlSessionId = getUrlParam2("sid") ?? getUrlParam2("session_id");
1260
+ if (isUuid(urlSessionId)) {
1261
+ writeStoredSession(persistence, urlSessionId, now);
1262
+ return urlSessionId;
1263
+ }
1264
+ const storedSession = readStoredSession(persistence);
1265
+ if (storedSession && now - storedSession.timestamp <= idleTimeoutSeconds * 1e3) {
1266
+ writeStoredSession(persistence, storedSession.sessionId, now);
1267
+ return storedSession.sessionId;
1268
+ }
1269
+ const sessionId = createUuid();
1270
+ writeStoredSession(persistence, sessionId, now);
1271
+ return sessionId;
1272
+ }
1273
+ return {
1274
+ getSessionId() {
1275
+ return resolveSessionId();
1276
+ },
1277
+ reset() {
1278
+ const sessionId = createUuid();
1279
+ writeStoredSession(persistence, sessionId, Date.now());
1280
+ return sessionId;
1281
+ }
1282
+ };
1283
+ }
1284
+
1285
+ // src/browser/core/DriveMetaDataSDK.ts
486
1286
  function endpointFromConfig(config) {
487
1287
  const host = config.apiHost ?? "https://sdk.drivemetadata.com/v2";
488
1288
  return `${host.replace(/\/$/, "")}/data-collector`;
@@ -493,14 +1293,6 @@ function requireConfigString(value, field) {
493
1293
  }
494
1294
  return value;
495
1295
  }
496
- function getBrowserStorage() {
497
- const browserWindow = getBrowserWindow();
498
- try {
499
- return browserWindow?.localStorage;
500
- } catch {
501
- return void 0;
502
- }
503
- }
504
1296
  var DriveMetaDataSDK = class {
505
1297
  constructor(config) {
506
1298
  this.initialized = true;
@@ -513,30 +1305,36 @@ var DriveMetaDataSDK = class {
513
1305
  requireConfigString(config.writeKey || config.token, "writeKey or token");
514
1306
  this.config = config;
515
1307
  this.endpoint = endpointFromConfig(config);
516
- const storage = getBrowserStorage();
1308
+ const storagePersistence = createBrowserPersistence(config.persistence);
1309
+ this.persistence = createDmdAttributesPersistence(storagePersistence);
1310
+ this.session = createSessionManager(this.persistence, config.sessionIdleTimeoutSeconds);
517
1311
  const deliveryConfig = {
518
- endpoint: this.endpoint
1312
+ endpoint: this.endpoint,
1313
+ storage: storagePersistence
519
1314
  };
520
1315
  if (config.delivery?.maxQueueSize !== void 0) deliveryConfig.maxQueueSize = config.delivery.maxQueueSize;
521
1316
  if (config.delivery?.queueTtlMs !== void 0) deliveryConfig.queueTtlMs = config.delivery.queueTtlMs;
522
1317
  if (config.delivery?.retryDelayMs !== void 0) deliveryConfig.retryDelayMs = config.delivery.retryDelayMs;
523
1318
  if (config.delivery?.maxRetryDelayMs !== void 0) deliveryConfig.maxRetryDelayMs = config.delivery.maxRetryDelayMs;
524
1319
  if (config.delivery?.maxPayloadBytes !== void 0) deliveryConfig.maxPayloadBytes = config.delivery.maxPayloadBytes;
1320
+ if (config.delivery?.payloadSizePolicy !== void 0) deliveryConfig.payloadSizePolicy = config.delivery.payloadSizePolicy;
1321
+ if (config.delivery?.payloadTruncateStringLength !== void 0) {
1322
+ deliveryConfig.payloadTruncateStringLength = config.delivery.payloadTruncateStringLength;
1323
+ }
1324
+ if (config.delivery?.useBeacon !== void 0) deliveryConfig.useBeacon = config.delivery.useBeacon;
525
1325
  if (config.delivery?.batchSize !== void 0) deliveryConfig.batchSize = config.delivery.batchSize;
526
1326
  if (config.onDrop !== void 0) deliveryConfig.onDrop = config.onDrop;
527
1327
  if (config.onError !== void 0) deliveryConfig.onError = config.onError;
528
- this.delivery = createDeliveryManager(storage ? {
529
- ...deliveryConfig,
530
- storage
531
- } : deliveryConfig);
1328
+ this.delivery = createDeliveryManager(deliveryConfig);
532
1329
  this.initialRetryDelayMs = config.delivery?.retryDelayMs ?? 1e3;
533
1330
  this.retryDelayMs = this.initialRetryDelayMs;
534
1331
  this.maxRetryDelayMs = config.delivery?.maxRetryDelayMs ?? 3e4;
535
1332
  this.writeKey = config.writeKey || config.token || "";
536
- this.identity = { anonymousId: createId2("anon") };
537
- this.sessionId = createId2("session");
1333
+ this.identity = { anonymousId: resolveAnonymousId(this.persistence) };
1334
+ captureAttributionFromUrl(this.persistence);
538
1335
  this.consentState = normalizeConsent(config.gdprConsent ?? config.consent);
539
1336
  this.gdprConsent = this.consentState.analytics;
1337
+ this.installAutocapture();
540
1338
  if (!config.delivery?.disableLifecycleFlush) {
541
1339
  this.installLifecycleFlush();
542
1340
  }
@@ -576,7 +1374,8 @@ var DriveMetaDataSDK = class {
576
1374
  }
577
1375
  }
578
1376
  reset() {
579
- this.identity = { anonymousId: createId2("anon") };
1377
+ this.identity = { anonymousId: resetAnonymousId(this.persistence) };
1378
+ this.session.reset();
580
1379
  this.queue = [];
581
1380
  this.offline = false;
582
1381
  if (this.retryTimer !== void 0) {
@@ -585,8 +1384,20 @@ var DriveMetaDataSDK = class {
585
1384
  }
586
1385
  this.lifecycleCleanup?.();
587
1386
  this.lifecycleCleanup = void 0;
1387
+ this.autocaptureCleanup?.();
1388
+ this.autocaptureCleanup = void 0;
588
1389
  this.delivery.clearQueue("manual_clear");
589
1390
  }
1391
+ disposeForTests() {
1392
+ if (this.retryTimer !== void 0) {
1393
+ clearTimeout(this.retryTimer);
1394
+ this.retryTimer = void 0;
1395
+ }
1396
+ this.lifecycleCleanup?.();
1397
+ this.lifecycleCleanup = void 0;
1398
+ this.autocaptureCleanup?.();
1399
+ this.autocaptureCleanup = void 0;
1400
+ }
590
1401
  setConsent(consent) {
591
1402
  this.consentState = typeof consent === "object" ? mergeConsent(this.consentState, consent) : normalizeConsent(consent);
592
1403
  this.gdprConsent = this.consentState.analytics;
@@ -601,6 +1412,7 @@ var DriveMetaDataSDK = class {
601
1412
  initialized: this.initialized,
602
1413
  consent: this.gdprConsent,
603
1414
  consentPurposes: this.consentState,
1415
+ persistence: this.persistence.getHealth(),
604
1416
  queueSize: deliveryDiagnostics.queued,
605
1417
  offline: this.offline || deliveryDiagnostics.queued > 0,
606
1418
  droppedEvents: this.droppedEvents + deliveryDiagnostics.dropped.length,
@@ -615,7 +1427,9 @@ var DriveMetaDataSDK = class {
615
1427
  void this.delivery.send(payload).then(() => {
616
1428
  const diagnostics = this.delivery.getDiagnostics();
617
1429
  this.offline = diagnostics.queued > 0;
618
- this.lastError = diagnostics.lastError;
1430
+ if (diagnostics.lastError !== void 0) {
1431
+ this.lastError = diagnostics.lastError;
1432
+ }
619
1433
  if (diagnostics.queued > 0) {
620
1434
  this.scheduleRetryFlush();
621
1435
  }
@@ -647,6 +1461,28 @@ var DriveMetaDataSDK = class {
647
1461
  }
648
1462
  };
649
1463
  }
1464
+ installAutocapture() {
1465
+ const browserWindow = getBrowserWindow();
1466
+ if (!browserWindow) return;
1467
+ if (this.config.autocapture === false) return;
1468
+ const capturePageview = this.config.capturePageview !== false;
1469
+ const capturePageleave = this.config.capturePageleave !== false;
1470
+ const controller = installAutocapture({
1471
+ browserWindow,
1472
+ capturePageview,
1473
+ capturePageleave,
1474
+ onPageView: () => {
1475
+ this.page();
1476
+ },
1477
+ onPageLeave: () => {
1478
+ this.trackEvent("page_leave", { url: browserWindow.location.href });
1479
+ },
1480
+ onRouteChange: () => {
1481
+ captureAttributionFromUrl(this.persistence);
1482
+ }
1483
+ });
1484
+ this.autocaptureCleanup = controller.cleanup;
1485
+ }
650
1486
  scheduleRetryFlush() {
651
1487
  if (this.retryTimer !== void 0) return;
652
1488
  const delay = Math.min(this.retryDelayMs, this.maxRetryDelayMs);
@@ -672,7 +1508,7 @@ var DriveMetaDataSDK = class {
672
1508
  type,
673
1509
  event,
674
1510
  properties: sanitizeProperties(properties),
675
- messageId: options.messageId ?? createId2("msg"),
1511
+ messageId: ensureUuid(options.messageId),
676
1512
  timestamp: options.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
677
1513
  context: options.context ?? {},
678
1514
  anonymousId: this.identity.anonymousId,
@@ -681,7 +1517,7 @@ var DriveMetaDataSDK = class {
681
1517
  appId: this.config.appId,
682
1518
  writeKey: this.writeKey,
683
1519
  consent: this.consentState,
684
- sessionId: this.sessionId
1520
+ sessionId: this.session.getSessionId()
685
1521
  };
686
1522
  if (this.identity.userId !== void 0) prepared.userId = this.identity.userId;
687
1523
  if (this.identity.groupId !== void 0) prepared.groupId = this.identity.groupId;
@@ -705,10 +1541,21 @@ var DriveMetaDataSDK = class {
705
1541
  if (!validation.ok) {
706
1542
  this.lastError = `DMD SDK schema warning: ${validation.errors.join(", ")}`;
707
1543
  }
708
- this.sendEvent(this.toCollectorPayload(prepared));
1544
+ const collectorPayload = this.toCollectorPayload(prepared);
1545
+ const backendValidation = validateBackendCollectorPayload(collectorPayload);
1546
+ if (!backendValidation.ok && this.config.schemaValidation === "strict") {
1547
+ this.lastError = `DMD SDK backend schema warning: ${backendValidation.errors.join(", ")}`;
1548
+ this.recordDrop(type, "invalid_payload", event);
1549
+ return;
1550
+ }
1551
+ if (!backendValidation.ok) {
1552
+ this.lastError = `DMD SDK backend schema warning: ${backendValidation.errors.join(", ")}`;
1553
+ }
1554
+ this.sendEvent(collectorPayload);
709
1555
  }
710
1556
  toCollectorPayload(prepared) {
711
1557
  const browserWindow = getBrowserWindow();
1558
+ const pageContext = getBrowserPageContext();
712
1559
  return createBackendCollectorPayload({
713
1560
  requestId: prepared.messageId,
714
1561
  timestamp: prepared.timestamp,
@@ -720,15 +1567,19 @@ var DriveMetaDataSDK = class {
720
1567
  sessionId: prepared.sessionId,
721
1568
  ua: browserWindow?.navigator?.userAgent,
722
1569
  appId: prepared.appId,
723
- pageUrl: browserWindow?.location?.href,
724
- eventData: {
1570
+ pageUrl: pageContext.url ?? browserWindow?.location?.href,
1571
+ eventData: mergeStoredAttribution({
725
1572
  ...prepared.properties,
1573
+ page: {
1574
+ ...pageContext,
1575
+ ...prepared.properties.page && typeof prepared.properties.page === "object" ? prepared.properties.page : {}
1576
+ },
726
1577
  anonymousId: prepared.anonymousId,
727
1578
  sessionId: prepared.sessionId,
728
1579
  timestamp: prepared.timestamp,
729
1580
  requestSentAt: prepared.timestamp,
730
1581
  requestReceivedAt: prepared.timestamp
731
- }
1582
+ }, this.persistence)
732
1583
  });
733
1584
  }
734
1585
  recordDrop(type, reason, event) {
@@ -804,7 +1655,7 @@ function initDmdSDK(config) {
804
1655
  return publicSingleton;
805
1656
  }
806
1657
  try {
807
- const instance = new DriveMetaDataSDK(config);
1658
+ const instance = new DriveMetaDataSDK(normalizeBrowserConfig(config));
808
1659
  return setSingleton(instance);
809
1660
  } catch (error) {
810
1661
  lastError = error instanceof Error ? error.message : String(error);