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