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