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