@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.
@@ -13,12 +13,62 @@ var __decorateParam = (index, decorator) => (target, key) => decorator(target, k
13
13
  // src/angular/dmd-analytics.service.ts
14
14
  import { Inject, Injectable, Optional } from "@angular/core";
15
15
 
16
+ // src/browser/core/browser-config.ts
17
+ function setIfDefined(target, key, value) {
18
+ if (value !== void 0) {
19
+ target[key] = value;
20
+ }
21
+ }
22
+ function normalizeBrowserConfig(input) {
23
+ const normalized = {
24
+ ...input,
25
+ clientId: input.clientId ?? "",
26
+ workspaceId: input.workspaceId ?? "",
27
+ appId: input.appId ?? ""
28
+ };
29
+ setIfDefined(normalized, "apiHost", input.apiHost);
30
+ setIfDefined(normalized, "capturePageview", input.capturePageview);
31
+ setIfDefined(normalized, "capturePageleave", input.capturePageleave);
32
+ setIfDefined(normalized, "captureDeadClicks", input.captureDeadClicks);
33
+ setIfDefined(normalized, "crossSubdomainCookie", input.crossSubdomainCookie);
34
+ setIfDefined(normalized, "sessionIdleTimeoutSeconds", input.sessionIdleTimeoutSeconds);
35
+ setIfDefined(normalized, "schemaValidation", input.schemaValidation);
36
+ setIfDefined(normalized, "beforeSend", input.beforeSend);
37
+ setIfDefined(normalized, "persistence", input.disablePersistence === true ? "none" : input.persistence);
38
+ return normalized;
39
+ }
40
+
41
+ // src/core/uuid.ts
42
+ function createUuid() {
43
+ const cryptoApi = globalThis.crypto;
44
+ if (typeof cryptoApi?.randomUUID === "function") {
45
+ return cryptoApi.randomUUID();
46
+ }
47
+ const bytes = new Uint8Array(16);
48
+ if (typeof cryptoApi?.getRandomValues === "function") {
49
+ cryptoApi.getRandomValues(bytes);
50
+ } else {
51
+ for (let index = 0; index < bytes.length; index += 1) {
52
+ bytes[index] = Math.floor(Math.random() * 256);
53
+ }
54
+ }
55
+ bytes[6] = (bytes[6] ?? 0) & 15 | 64;
56
+ bytes[8] = (bytes[8] ?? 0) & 63 | 128;
57
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
58
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
59
+ }
60
+ function isUuid(value) {
61
+ 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);
62
+ }
63
+ function ensureUuid(value) {
64
+ return isUuid(value) ? value : createUuid();
65
+ }
66
+
16
67
  // src/core/backend-payload.ts
17
68
  function formatUtcTimestamp(value) {
18
- if (typeof value !== "string") return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
19
- const date = new Date(value);
20
- if (Number.isNaN(date.getTime())) return value;
21
- return date.toISOString().replace("T", " ").slice(0, 19);
69
+ const date = typeof value === "string" || typeof value === "number" || value instanceof Date ? new Date(value) : /* @__PURE__ */ new Date();
70
+ const safeDate = Number.isNaN(date.getTime()) ? /* @__PURE__ */ new Date() : date;
71
+ return safeDate.toISOString().replace("T", " ").slice(0, 19);
22
72
  }
23
73
  function cleanObject(value) {
24
74
  if (Array.isArray(value)) {
@@ -49,15 +99,19 @@ function createBackendCollectorPayload(input) {
49
99
  };
50
100
  const metaData = {
51
101
  ...normalizedEventData,
52
- requestId: input.requestId,
102
+ requestId: ensureUuid(typeof input.requestId === "string" ? input.requestId : void 0),
53
103
  timestamp,
54
104
  eventType: input.eventType,
55
105
  requestFrom: input.requestFrom ?? "3",
56
106
  clientId: input.clientId,
57
107
  workspaceId: input.workspaceId,
58
108
  token: input.token,
59
- anonymousId: eventData.anonymousId ?? input.anonymousId,
60
- sessionId: eventData.sessionId ?? input.sessionId,
109
+ anonymousId: ensureUuid(
110
+ typeof eventData.anonymousId === "string" ? eventData.anonymousId : typeof input.anonymousId === "string" ? input.anonymousId : void 0
111
+ ),
112
+ sessionId: ensureUuid(
113
+ typeof eventData.sessionId === "string" ? eventData.sessionId : typeof input.sessionId === "string" ? input.sessionId : void 0
114
+ ),
61
115
  ua: input.ua,
62
116
  appDetails: { app_id: input.appId },
63
117
  page: { ...page2, url: page2.url ?? input.pageUrl },
@@ -68,12 +122,45 @@ function createBackendCollectorPayload(input) {
68
122
  return cleanObject(payload);
69
123
  }
70
124
 
71
- // src/core/environment.ts
72
- function getBrowserWindow() {
73
- return typeof window === "undefined" ? void 0 : window;
125
+ // src/core/backend-schema.ts
126
+ var requiredMetaDataFields = [
127
+ "requestId",
128
+ "timestamp",
129
+ "eventType",
130
+ "requestFrom",
131
+ "clientId",
132
+ "workspaceId",
133
+ "anonymousId",
134
+ "sessionId"
135
+ ];
136
+ function isPresent(value) {
137
+ return value !== null && value !== void 0 && value !== "";
138
+ }
139
+ function validateBackendCollectorPayload(payload) {
140
+ const errors = [];
141
+ const metaData = payload.metaData;
142
+ if (!metaData || typeof metaData !== "object" || Array.isArray(metaData)) {
143
+ return {
144
+ ok: false,
145
+ errors: ["metaData is required"]
146
+ };
147
+ }
148
+ const metadataRecord = metaData;
149
+ for (const field of requiredMetaDataFields) {
150
+ if (!isPresent(metadataRecord[field])) {
151
+ errors.push(`metaData.${field} is required`);
152
+ }
153
+ }
154
+ if (isPresent(metadataRecord.eventType) && typeof metadataRecord.eventType !== "string") {
155
+ errors.push("metaData.eventType must be a string");
156
+ }
157
+ return {
158
+ ok: errors.length === 0,
159
+ errors
160
+ };
74
161
  }
75
162
 
76
- // src/browser/core/consent.ts
163
+ // src/core/consent.ts
77
164
  var purposes = [
78
165
  "analytics",
79
166
  "advertising",
@@ -113,6 +200,476 @@ function canCollectPurpose(consent, purpose) {
113
200
  return consent[purpose] === "granted";
114
201
  }
115
202
 
203
+ // src/core/event-schema.ts
204
+ var reservedKeys = /* @__PURE__ */ new Set(["messageId", "timestamp", "type", "event", "anonymousId", "userId", "context"]);
205
+ function validateEventEnvelope(envelope, options = {}) {
206
+ if (options.mode === "off") return { ok: true, errors: [] };
207
+ const errors = [];
208
+ if (typeof envelope.type !== "string") errors.push("type must be a string");
209
+ if (envelope.type === "track" && typeof envelope.event !== "string") {
210
+ errors.push("track event must include event name");
211
+ }
212
+ if (typeof envelope.messageId !== "string") errors.push("messageId must be a string");
213
+ if (typeof envelope.timestamp !== "string") errors.push("timestamp must be an ISO string");
214
+ for (const key of Object.keys(envelope.properties ?? {})) {
215
+ if (reservedKeys.has(key)) errors.push(`properties.${key} is reserved`);
216
+ }
217
+ return { ok: errors.length === 0, errors };
218
+ }
219
+
220
+ // src/core/environment.ts
221
+ function getBrowserWindow() {
222
+ return typeof window === "undefined" ? void 0 : window;
223
+ }
224
+
225
+ // src/core/privacy.ts
226
+ var sensitiveKeys = /* @__PURE__ */ new Set([
227
+ "email",
228
+ "phone",
229
+ "mobile",
230
+ "address",
231
+ "address1",
232
+ "address2",
233
+ "first_name",
234
+ "last_name",
235
+ "name",
236
+ "token",
237
+ "secret",
238
+ "password",
239
+ "session",
240
+ "cookie"
241
+ ]);
242
+ function redactUrl(url, allowQueryKeys = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"]) {
243
+ const parsed = new URL(url, "https://placeholder.local");
244
+ for (const key of Array.from(parsed.searchParams.keys())) {
245
+ if (!allowQueryKeys.includes(key)) {
246
+ parsed.searchParams.set(key, "[REDACTED]");
247
+ }
248
+ }
249
+ return url.startsWith("http") ? parsed.toString() : `${parsed.pathname}${parsed.search}`;
250
+ }
251
+ function sanitizeValue(value, allow, path = []) {
252
+ if (Array.isArray(value)) {
253
+ return value.map((item) => sanitizeValue(item, allow, path));
254
+ }
255
+ if (value && typeof value === "object") {
256
+ return Object.fromEntries(
257
+ Object.entries(value).filter(([key]) => {
258
+ const lowerKey = key.toLowerCase();
259
+ const lowerPath = path.map((item) => item.toLowerCase());
260
+ const isEcommerceItemName = lowerKey === "name" && lowerPath.includes("ecommerce") && lowerPath.includes("items");
261
+ return isEcommerceItemName || !sensitiveKeys.has(lowerKey) || allow.has(lowerKey);
262
+ }).map(([key, nestedValue]) => [key, sanitizeValue(nestedValue, allow, [...path, key])])
263
+ );
264
+ }
265
+ return value;
266
+ }
267
+ function sanitizeProperties(input, allowRawKeys = []) {
268
+ const allow = new Set(allowRawKeys.map((key) => key.toLowerCase()));
269
+ return sanitizeValue(input, allow);
270
+ }
271
+
272
+ // src/browser/core/attribute-persistence.ts
273
+ var DMD_ATTRIBUTES_STORAGE_KEY = "dmd_attributes";
274
+ var SESSION_STORAGE_KEY = "sessionData";
275
+ var SESSION_ID_KEY = "sessionId";
276
+ var SESSION_TIMESTAMP_KEY = "timestamp";
277
+ function isBlankValue(value) {
278
+ return value === null || value === void 0 || typeof value === "string" && value.trim() === "";
279
+ }
280
+ function readAttributes(base) {
281
+ const raw = base.getItem(DMD_ATTRIBUTES_STORAGE_KEY);
282
+ if (!raw) return {};
283
+ try {
284
+ const parsed = JSON.parse(raw);
285
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
286
+ } catch {
287
+ base.removeItem(DMD_ATTRIBUTES_STORAGE_KEY);
288
+ return {};
289
+ }
290
+ }
291
+ function sanitizeAttributes(attributes) {
292
+ return Object.fromEntries(Object.entries(attributes).filter(([, value]) => !isBlankValue(value)));
293
+ }
294
+ function writeAttributes(base, attributes) {
295
+ const sanitized = sanitizeAttributes(attributes);
296
+ if (Object.keys(sanitized).length === 0) {
297
+ base.removeItem(DMD_ATTRIBUTES_STORAGE_KEY);
298
+ return;
299
+ }
300
+ base.setItem(DMD_ATTRIBUTES_STORAGE_KEY, JSON.stringify(sanitized));
301
+ }
302
+ function parseSessionData(value) {
303
+ try {
304
+ const parsed = JSON.parse(value);
305
+ const sessionData = {};
306
+ const sessionId = typeof parsed.sessionId === "string" && parsed.sessionId.trim() !== "" ? parsed.sessionId : void 0;
307
+ const timestamp = typeof parsed.timestamp === "number" ? parsed.timestamp : void 0;
308
+ if (sessionId !== void 0) sessionData.sessionId = sessionId;
309
+ if (timestamp !== void 0) sessionData.timestamp = timestamp;
310
+ return sessionData;
311
+ } catch {
312
+ return {};
313
+ }
314
+ }
315
+ function getAggregateValue(attributes, key) {
316
+ if (key === SESSION_STORAGE_KEY) {
317
+ const sessionId = attributes[SESSION_ID_KEY];
318
+ const timestamp = attributes[SESSION_TIMESTAMP_KEY];
319
+ if (typeof sessionId === "string" && !isBlankValue(sessionId) && typeof timestamp === "number") {
320
+ return JSON.stringify({ sessionId, timestamp });
321
+ }
322
+ return null;
323
+ }
324
+ const value = attributes[key];
325
+ if (isBlankValue(value)) return null;
326
+ return typeof value === "string" ? value : JSON.stringify(value);
327
+ }
328
+ function setAggregateValue(attributes, key, value) {
329
+ if (key === SESSION_STORAGE_KEY) {
330
+ const sessionData = parseSessionData(value);
331
+ if (sessionData.sessionId === void 0 || sessionData.timestamp === void 0) {
332
+ delete attributes[SESSION_ID_KEY];
333
+ delete attributes[SESSION_TIMESTAMP_KEY];
334
+ return;
335
+ }
336
+ attributes[SESSION_ID_KEY] = sessionData.sessionId;
337
+ attributes[SESSION_TIMESTAMP_KEY] = sessionData.timestamp;
338
+ return;
339
+ }
340
+ attributes[key] = value;
341
+ }
342
+ function removeAggregateValue(attributes, key) {
343
+ if (key === SESSION_STORAGE_KEY) {
344
+ delete attributes[SESSION_ID_KEY];
345
+ delete attributes[SESSION_TIMESTAMP_KEY];
346
+ return;
347
+ }
348
+ delete attributes[key];
349
+ }
350
+ function createDmdAttributesPersistence(base) {
351
+ return {
352
+ getItem(key) {
353
+ if (key === DMD_ATTRIBUTES_STORAGE_KEY) return base.getItem(key);
354
+ const attributes = readAttributes(base);
355
+ const aggregateValue = getAggregateValue(attributes, key);
356
+ if (aggregateValue !== null) return aggregateValue;
357
+ const legacyValue = base.getItem(key);
358
+ if (isBlankValue(legacyValue)) return null;
359
+ setAggregateValue(attributes, key, String(legacyValue));
360
+ writeAttributes(base, attributes);
361
+ return legacyValue;
362
+ },
363
+ setItem(key, value) {
364
+ if (key === DMD_ATTRIBUTES_STORAGE_KEY) return base.setItem(key, value);
365
+ const attributes = readAttributes(base);
366
+ if (isBlankValue(value)) {
367
+ removeAggregateValue(attributes, key);
368
+ writeAttributes(base, attributes);
369
+ base.removeItem(key);
370
+ return true;
371
+ }
372
+ setAggregateValue(attributes, key, value);
373
+ writeAttributes(base, attributes);
374
+ base.setItem(key, value);
375
+ return true;
376
+ },
377
+ removeItem(key) {
378
+ if (key === DMD_ATTRIBUTES_STORAGE_KEY) {
379
+ base.removeItem(key);
380
+ return;
381
+ }
382
+ const attributes = readAttributes(base);
383
+ removeAggregateValue(attributes, key);
384
+ writeAttributes(base, attributes);
385
+ base.removeItem(key);
386
+ },
387
+ getHealth() {
388
+ return base.getHealth();
389
+ }
390
+ };
391
+ }
392
+
393
+ // src/core/attribution.ts
394
+ var dmdUtmKeys = [
395
+ "utm_source",
396
+ "utm_medium",
397
+ "utm_campaign",
398
+ "utm_term",
399
+ "utm_content",
400
+ "utm_id"
401
+ ];
402
+ var dmdCampaignKeys = ["campaign_id", "ad_id"];
403
+ var dmdClickIdSources = [
404
+ ["gclid", 2],
405
+ ["fbclid", 3],
406
+ ["ScCid", 1],
407
+ ["li_fat_id", 4]
408
+ ];
409
+ function cleanAttributionRecord(record) {
410
+ const cleaned = Object.fromEntries(
411
+ Object.entries(record).filter(([, value]) => value !== null && value !== void 0 && value !== "")
412
+ );
413
+ return Object.keys(cleaned).length > 0 ? cleaned : void 0;
414
+ }
415
+ function objectValue(value) {
416
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
417
+ }
418
+ function mergeAttributionRecords(explicitProperties, stored) {
419
+ const attributionData = objectValue(explicitProperties.attributionData);
420
+ const utmParameter = objectValue(explicitProperties.utmParameter);
421
+ return {
422
+ ...explicitProperties,
423
+ ...stored.attributionData || attributionData ? { attributionData: { ...stored.attributionData ?? {}, ...attributionData ?? {} } } : {},
424
+ ...stored.utmParameter || utmParameter ? { utmParameter: { ...stored.utmParameter ?? {}, ...utmParameter ?? {} } } : {}
425
+ };
426
+ }
427
+
428
+ // src/browser/core/attribution.ts
429
+ var DMD_CLICK_ID_QUERY_KEY = "click_id";
430
+ var DMD_CLICK_ID_LEGACY_QUERY_KEY = "dmd_click_id";
431
+ var DMD_CLICK_ID_STORAGE_KEY = "dmd_click_id";
432
+ var DMD_CLICK_ID_ATTRIBUTE_KEY = "click_id";
433
+ function getCurrentUrl() {
434
+ return getBrowserWindow()?.location?.href;
435
+ }
436
+ function getCookie(name) {
437
+ const cookie = getBrowserWindow()?.document?.cookie;
438
+ if (!cookie) return void 0;
439
+ const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
440
+ const match = new RegExp(`(?:^|; )${escapedName}=([^;]*)`).exec(cookie);
441
+ return match ? decodeURIComponent(match[1] ?? "") : void 0;
442
+ }
443
+ function getParams(url) {
444
+ return new URL(url, "https://placeholder.local").searchParams;
445
+ }
446
+ function setIfPresent(persistence, key, value) {
447
+ if (value !== null && value !== "") {
448
+ persistence.setItem(key, value);
449
+ }
450
+ }
451
+ function getClickIdParam(params) {
452
+ return params.get(DMD_CLICK_ID_LEGACY_QUERY_KEY) || params.get(DMD_CLICK_ID_QUERY_KEY);
453
+ }
454
+ function getStoredString(persistence, key) {
455
+ const value = persistence.getItem(key);
456
+ return value === null || value === "" ? void 0 : value;
457
+ }
458
+ function getStoredNumberOrString(persistence, key) {
459
+ const value = getStoredString(persistence, key);
460
+ if (value === void 0) return void 0;
461
+ const parsed = Number.parseInt(value, 10);
462
+ return Number.isNaN(parsed) ? value : parsed;
463
+ }
464
+ function captureAttributionFromUrl(persistence, url = getCurrentUrl()) {
465
+ if (!url) return;
466
+ const params = getParams(url);
467
+ for (const key of dmdUtmKeys) {
468
+ setIfPresent(persistence, key, params.get(key));
469
+ }
470
+ for (const key of dmdCampaignKeys) {
471
+ setIfPresent(persistence, key, params.get(key));
472
+ }
473
+ const clickId = getClickIdParam(params);
474
+ setIfPresent(persistence, DMD_CLICK_ID_ATTRIBUTE_KEY, clickId);
475
+ setIfPresent(persistence, DMD_CLICK_ID_STORAGE_KEY, clickId);
476
+ for (const [param, sdkPubId] of dmdClickIdSources) {
477
+ const value = params.get(param);
478
+ if (value) {
479
+ persistence.setItem("unique_id", value);
480
+ persistence.setItem("sdk_pub_id", String(sdkPubId));
481
+ }
482
+ }
483
+ }
484
+ function getStoredAttributionData(persistence) {
485
+ return cleanAttributionRecord({
486
+ unique_id: getStoredString(persistence, "unique_id"),
487
+ sdk_pub_id: getStoredNumberOrString(persistence, "sdk_pub_id"),
488
+ fbc: getStoredString(persistence, "fbc") ?? getCookie("_fbc"),
489
+ fbp: getStoredString(persistence, "fbp") ?? getCookie("_fbp"),
490
+ campaign_id: getStoredString(persistence, "campaign_id"),
491
+ ad_id: getStoredString(persistence, "ad_id"),
492
+ click_id: getStoredString(persistence, DMD_CLICK_ID_STORAGE_KEY) ?? getStoredString(persistence, DMD_CLICK_ID_ATTRIBUTE_KEY)
493
+ });
494
+ }
495
+ function getStoredUtmParameter(persistence) {
496
+ return cleanAttributionRecord(Object.fromEntries(dmdUtmKeys.map((key) => [key, getStoredString(persistence, key)])));
497
+ }
498
+ function mergeStoredAttribution(properties, persistence) {
499
+ const stored = {};
500
+ const attributionData = getStoredAttributionData(persistence);
501
+ const utmParameter = getStoredUtmParameter(persistence);
502
+ if (attributionData !== void 0) stored.attributionData = attributionData;
503
+ if (utmParameter !== void 0) stored.utmParameter = utmParameter;
504
+ return mergeAttributionRecords(properties, stored);
505
+ }
506
+
507
+ // src/browser/core/autocapture.ts
508
+ function installAutocapture(config) {
509
+ const trackedPageUrls = /* @__PURE__ */ new Set();
510
+ const timers = /* @__PURE__ */ new Set();
511
+ const pageviewDelayMs = config.pageviewDelayMs ?? 500;
512
+ const history = config.browserWindow.history;
513
+ const originalPushState = history?.pushState;
514
+ const originalReplaceState = history?.replaceState;
515
+ function clearTimer(timer) {
516
+ timers.delete(timer);
517
+ clearTimeout(timer);
518
+ }
519
+ function canonicalUrl() {
520
+ return config.browserWindow.location.href;
521
+ }
522
+ function trackCurrentPageview() {
523
+ if (!config.capturePageview) return;
524
+ const url = canonicalUrl();
525
+ if (trackedPageUrls.has(url)) return;
526
+ trackedPageUrls.add(url);
527
+ config.onPageView();
528
+ }
529
+ function schedulePageview(delayMs) {
530
+ if (!config.capturePageview) return;
531
+ const timer = setTimeout(() => {
532
+ timers.delete(timer);
533
+ trackCurrentPageview();
534
+ }, delayMs);
535
+ timers.add(timer);
536
+ }
537
+ function handleRouteChange() {
538
+ config.onRouteChange?.();
539
+ trackCurrentPageview();
540
+ }
541
+ function wrapHistoryMethod(method) {
542
+ if (!history) return;
543
+ const original = history[method];
544
+ if (typeof original !== "function") return;
545
+ history[method] = function wrappedHistoryMethod(...args) {
546
+ const result = original.apply(this, args);
547
+ const timer = setTimeout(() => {
548
+ timers.delete(timer);
549
+ handleRouteChange();
550
+ }, 0);
551
+ timers.add(timer);
552
+ return result;
553
+ };
554
+ }
555
+ function handlePopOrHashChange() {
556
+ handleRouteChange();
557
+ }
558
+ function handlePageLeave() {
559
+ if (config.capturePageleave) {
560
+ config.onPageLeave();
561
+ }
562
+ }
563
+ schedulePageview(pageviewDelayMs);
564
+ wrapHistoryMethod("pushState");
565
+ wrapHistoryMethod("replaceState");
566
+ const canListen = typeof config.browserWindow.addEventListener === "function" && typeof config.browserWindow.removeEventListener === "function";
567
+ if (canListen) {
568
+ config.browserWindow.addEventListener("popstate", handlePopOrHashChange);
569
+ config.browserWindow.addEventListener("hashchange", handlePopOrHashChange);
570
+ config.browserWindow.addEventListener("beforeunload", handlePageLeave);
571
+ }
572
+ return {
573
+ cleanup() {
574
+ for (const timer of Array.from(timers)) {
575
+ clearTimer(timer);
576
+ }
577
+ if (history && originalPushState) history.pushState = originalPushState;
578
+ if (history && originalReplaceState) history.replaceState = originalReplaceState;
579
+ if (canListen) {
580
+ config.browserWindow.removeEventListener("popstate", handlePopOrHashChange);
581
+ config.browserWindow.removeEventListener("hashchange", handlePopOrHashChange);
582
+ config.browserWindow.removeEventListener("beforeunload", handlePageLeave);
583
+ }
584
+ }
585
+ };
586
+ }
587
+
588
+ // src/core/payload-size.ts
589
+ var preserveStringKeys = /* @__PURE__ */ new Set([
590
+ "requestId",
591
+ "eventType",
592
+ "requestFrom",
593
+ "clientId",
594
+ "workspaceId",
595
+ "anonymousId",
596
+ "sessionId",
597
+ "token"
598
+ ]);
599
+ function payloadByteLength(payload) {
600
+ const serialized = JSON.stringify(payload);
601
+ if (typeof TextEncoder !== "undefined") {
602
+ return new TextEncoder().encode(serialized).length;
603
+ }
604
+ return serialized.length;
605
+ }
606
+ function clonePayload(payload) {
607
+ try {
608
+ return JSON.parse(JSON.stringify(payload));
609
+ } catch {
610
+ return void 0;
611
+ }
612
+ }
613
+ function truncateValue(value, truncateStringLength, key) {
614
+ if (typeof value === "string") {
615
+ if (key && preserveStringKeys.has(key)) return value;
616
+ if (value.length <= truncateStringLength) return value;
617
+ return `${value.slice(0, truncateStringLength)}...[TRUNCATED]`;
618
+ }
619
+ if (Array.isArray(value)) {
620
+ return value.map((item) => truncateValue(item, truncateStringLength));
621
+ }
622
+ if (value && typeof value === "object") {
623
+ return Object.fromEntries(
624
+ Object.entries(value).map(([childKey, childValue]) => [
625
+ childKey,
626
+ truncateValue(childValue, truncateStringLength, childKey)
627
+ ])
628
+ );
629
+ }
630
+ return value;
631
+ }
632
+ function markPayloadTruncated(payload) {
633
+ if (payload.metaData && typeof payload.metaData === "object" && !Array.isArray(payload.metaData)) {
634
+ return {
635
+ ...payload,
636
+ metaData: {
637
+ ...payload.metaData,
638
+ payloadTruncated: true
639
+ }
640
+ };
641
+ }
642
+ return {
643
+ ...payload,
644
+ payloadTruncated: true
645
+ };
646
+ }
647
+ function truncatePayload(payload, truncateStringLength) {
648
+ const cloned = clonePayload(payload);
649
+ if (!cloned) return void 0;
650
+ return markPayloadTruncated(truncateValue(cloned, truncateStringLength));
651
+ }
652
+ function getPayloadMessageId(payload) {
653
+ const value = payload.metaData?.requestId ?? payload.messageId;
654
+ return value === void 0 ? void 0 : String(value);
655
+ }
656
+ function preparePayloadForSizePolicy(payload, options = {}) {
657
+ const maxPayloadBytes = options.maxPayloadBytes ?? 64e3;
658
+ const payloadSizePolicy = options.payloadSizePolicy ?? "drop";
659
+ const payloadTruncateStringLength = options.payloadTruncateStringLength ?? 1024;
660
+ if (payloadByteLength(payload) <= maxPayloadBytes) {
661
+ return { ok: true, payload, truncated: false };
662
+ }
663
+ if (payloadSizePolicy === "truncate") {
664
+ const truncatedPayload = truncatePayload(payload, payloadTruncateStringLength);
665
+ if (truncatedPayload && payloadByteLength(truncatedPayload) <= maxPayloadBytes) {
666
+ return { ok: true, payload: truncatedPayload, truncated: true };
667
+ }
668
+ }
669
+ const messageId = getPayloadMessageId(payload);
670
+ return messageId === void 0 ? { ok: false, reason: "payload_too_large" } : { ok: false, reason: "payload_too_large", messageId };
671
+ }
672
+
116
673
  // src/browser/core/delivery.ts
117
674
  function createId(prefix) {
118
675
  return `${prefix}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
@@ -144,7 +701,6 @@ function createDeliveryManager(config) {
144
701
  const lockTtlMs = config.lockTtlMs ?? 5e3;
145
702
  const tabId = config.tabId ?? createId("tab");
146
703
  const batchSize = config.batchSize ?? 25;
147
- const maxPayloadBytes = config.maxPayloadBytes ?? 64e3;
148
704
  function recordDrop(event) {
149
705
  diagnostics.dropped.push(event);
150
706
  config.onDrop?.(event);
@@ -153,12 +709,22 @@ function createDeliveryManager(config) {
153
709
  diagnostics.lastError = error.message;
154
710
  config.onError?.(error);
155
711
  }
156
- function payloadByteLength(payload) {
157
- const serialized = JSON.stringify(payload);
158
- if (typeof TextEncoder !== "undefined") {
159
- return new TextEncoder().encode(serialized).length;
712
+ function preparePayloadForSend(payload) {
713
+ const sizePolicyOptions = {};
714
+ if (config.maxPayloadBytes !== void 0) sizePolicyOptions.maxPayloadBytes = config.maxPayloadBytes;
715
+ if (config.payloadSizePolicy !== void 0) sizePolicyOptions.payloadSizePolicy = config.payloadSizePolicy;
716
+ if (config.payloadTruncateStringLength !== void 0) {
717
+ sizePolicyOptions.payloadTruncateStringLength = config.payloadTruncateStringLength;
160
718
  }
161
- return serialized.length;
719
+ const prepared = preparePayloadForSizePolicy(payload, sizePolicyOptions);
720
+ if (prepared.ok) return prepared.payload;
721
+ const diagnostic = {
722
+ reason: prepared.reason,
723
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
724
+ };
725
+ if (prepared.messageId !== void 0) diagnostic.messageId = prepared.messageId;
726
+ recordDrop(diagnostic);
727
+ return void 0;
162
728
  }
163
729
  function recordStorageUnavailable() {
164
730
  recordDrop({
@@ -248,7 +814,19 @@ function createDeliveryManager(config) {
248
814
  idempotencyKey: String(payload.idempotencyKey ?? createIdempotencyKey(payload, messageId))
249
815
  };
250
816
  }
817
+ function tryBeacon(body) {
818
+ if (!config.useBeacon) return false;
819
+ const sendBeacon = globalThis.navigator?.sendBeacon;
820
+ if (typeof sendBeacon !== "function") return false;
821
+ try {
822
+ const blob = new Blob([JSON.stringify(body)], { type: "application/json" });
823
+ return sendBeacon.call(globalThis.navigator, config.endpoint, blob);
824
+ } catch {
825
+ return false;
826
+ }
827
+ }
251
828
  async function deliver(body) {
829
+ if (tryBeacon(body)) return;
252
830
  const fetchImpl = config.fetch ?? globalThis.fetch;
253
831
  if (typeof fetchImpl !== "function") {
254
832
  throw new Error("fetch_unavailable");
@@ -265,26 +843,22 @@ function createDeliveryManager(config) {
265
843
  return {
266
844
  async send(payload) {
267
845
  const body = withEnvelope(payload);
268
- if (payloadByteLength(body) > maxPayloadBytes) {
269
- recordDrop({
270
- messageId: String(body.metaData?.requestId ?? body.messageId),
271
- reason: "payload_too_large",
272
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
273
- });
846
+ const preparedBody = preparePayloadForSend(body);
847
+ if (!preparedBody) {
274
848
  return;
275
849
  }
276
850
  diagnostics.inFlight += 1;
277
851
  try {
278
- await deliver(body);
852
+ await deliver(preparedBody);
279
853
  } catch (error) {
280
854
  const deliveryError = error instanceof Error ? error : new Error(String(error));
281
855
  recordError(deliveryError);
282
856
  enqueue({
283
- messageId: String(body.metaData?.requestId ?? body.messageId),
857
+ messageId: String(getPayloadMessageId(body)),
284
858
  savedAt: Date.now(),
285
859
  attempts: 1,
286
860
  lastError: deliveryError.message,
287
- payload: body
861
+ payload: preparedBody
288
862
  });
289
863
  } finally {
290
864
  diagnostics.inFlight -= 1;
@@ -397,60 +971,286 @@ function createDeliveryManager(config) {
397
971
  };
398
972
  }
399
973
 
400
- // src/browser/core/privacy.ts
401
- var sensitiveKeys = /* @__PURE__ */ new Set([
402
- "email",
403
- "phone",
404
- "mobile",
405
- "address",
406
- "address1",
407
- "address2",
408
- "first_name",
409
- "last_name",
410
- "name",
411
- "token",
412
- "secret",
413
- "password",
414
- "session",
415
- "cookie"
416
- ]);
417
- function sanitizeValue(value, allow) {
418
- if (Array.isArray(value)) {
419
- return value.map((item) => sanitizeValue(item, allow));
974
+ // src/browser/core/identity.ts
975
+ var ANONYMOUS_ID_STORAGE_KEY = "dmd_anonymous_id";
976
+ var LEGACY_ANONYMOUS_ID_STORAGE_KEY = "anonymousId";
977
+ function getUrlParam(name) {
978
+ const href = getBrowserWindow()?.location?.href;
979
+ if (!href) return null;
980
+ try {
981
+ return new URL(href).searchParams.get(name);
982
+ } catch {
983
+ return null;
420
984
  }
421
- if (value && typeof value === "object") {
422
- return Object.fromEntries(
423
- Object.entries(value).filter(([key]) => !sensitiveKeys.has(key.toLowerCase()) || allow.has(key.toLowerCase())).map(([key, nestedValue]) => [key, sanitizeValue(nestedValue, allow)])
424
- );
985
+ }
986
+ function resolveAnonymousId(persistence) {
987
+ const urlAnonymousId = getUrlParam("aid");
988
+ if (isUuid(urlAnonymousId)) {
989
+ persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, urlAnonymousId);
990
+ return urlAnonymousId;
991
+ }
992
+ const storedAnonymousId = persistence.getItem(ANONYMOUS_ID_STORAGE_KEY);
993
+ if (isUuid(storedAnonymousId)) {
994
+ return storedAnonymousId;
995
+ }
996
+ const legacyAnonymousId = persistence.getItem(LEGACY_ANONYMOUS_ID_STORAGE_KEY);
997
+ if (isUuid(legacyAnonymousId)) {
998
+ persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, legacyAnonymousId);
999
+ return legacyAnonymousId;
1000
+ }
1001
+ const anonymousId = createUuid();
1002
+ persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, anonymousId);
1003
+ return anonymousId;
1004
+ }
1005
+ function resetAnonymousId(persistence) {
1006
+ const anonymousId = createUuid();
1007
+ persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, anonymousId);
1008
+ return anonymousId;
1009
+ }
1010
+
1011
+ // src/browser/core/page-context.ts
1012
+ function redactSearch(search) {
1013
+ const redacted = redactUrl(search);
1014
+ return redacted.startsWith("/?") ? redacted.slice(1) : redacted;
1015
+ }
1016
+ function getBrowserPageContext() {
1017
+ const browserWindow = getBrowserWindow();
1018
+ const documentRef = browserWindow?.document ?? globalThis.document;
1019
+ const location = browserWindow?.location;
1020
+ const page2 = {};
1021
+ if (location?.href) page2.url = location.href;
1022
+ if (location?.pathname) page2.path = location.pathname;
1023
+ if (location?.search) page2.search = redactSearch(location.search);
1024
+ if (documentRef?.title) page2.title = documentRef.title;
1025
+ if (documentRef?.referrer) page2.referrer = redactUrl(documentRef.referrer);
1026
+ return page2;
1027
+ }
1028
+
1029
+ // src/browser/core/persistence.ts
1030
+ function createPersistenceHealth(requested) {
1031
+ return {
1032
+ requested,
1033
+ cookieFallbackUsed: false,
1034
+ memoryFallbackUsed: requested === "memory",
1035
+ failures: []
1036
+ };
1037
+ }
1038
+ function recordFailure(health, backend, operation, error) {
1039
+ health.failures.push({
1040
+ backend,
1041
+ operation,
1042
+ message: error instanceof Error ? error.message : String(error)
1043
+ });
1044
+ if (health.failures.length > 25) {
1045
+ health.failures.shift();
425
1046
  }
426
- return value;
427
1047
  }
428
- function sanitizeProperties(input, allowRawKeys = []) {
429
- const allow = new Set(allowRawKeys.map((key) => key.toLowerCase()));
430
- return sanitizeValue(input, allow);
1048
+ function createMemoryPersistence(health = createPersistenceHealth("memory")) {
1049
+ const memory = {};
1050
+ return {
1051
+ getItem(key) {
1052
+ return Object.prototype.hasOwnProperty.call(memory, key) ? memory[key] ?? null : null;
1053
+ },
1054
+ setItem(key, value) {
1055
+ memory[key] = value;
1056
+ return true;
1057
+ },
1058
+ removeItem(key) {
1059
+ delete memory[key];
1060
+ },
1061
+ getHealth() {
1062
+ return {
1063
+ ...health,
1064
+ failures: health.failures.map((failure) => ({ ...failure }))
1065
+ };
1066
+ }
1067
+ };
1068
+ }
1069
+ function getCookie2(name) {
1070
+ const browserWindow = getBrowserWindow();
1071
+ const cookie = browserWindow?.document?.cookie;
1072
+ if (!cookie) return null;
1073
+ const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1074
+ const match = new RegExp(`(?:^|; )${escapedName}=([^;]*)`).exec(cookie);
1075
+ return match ? decodeURIComponent(match[1] ?? "") : null;
1076
+ }
1077
+ function setCookie(name, value) {
1078
+ const browserWindow = getBrowserWindow();
1079
+ if (!browserWindow?.document) return false;
1080
+ try {
1081
+ const expires = new Date(Date.now() + 365 * 24 * 60 * 60 * 1e3).toUTCString();
1082
+ const secure = browserWindow.location?.protocol === "https:" ? "; Secure" : "";
1083
+ browserWindow.document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/${secure}; SameSite=Lax`;
1084
+ return true;
1085
+ } catch {
1086
+ return false;
1087
+ }
1088
+ }
1089
+ function removeCookie(name) {
1090
+ const browserWindow = getBrowserWindow();
1091
+ if (!browserWindow?.document) return;
1092
+ browserWindow.document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
1093
+ }
1094
+ function getLocalStorage() {
1095
+ const browserWindow = getBrowserWindow();
1096
+ try {
1097
+ return browserWindow?.localStorage;
1098
+ } catch {
1099
+ return void 0;
1100
+ }
1101
+ }
1102
+ function getSessionStorage() {
1103
+ const browserWindow = getBrowserWindow();
1104
+ try {
1105
+ return browserWindow?.sessionStorage;
1106
+ } catch {
1107
+ return void 0;
1108
+ }
1109
+ }
1110
+ function createBrowserPersistence(mode = "localStorage+cookie") {
1111
+ const health = createPersistenceHealth(mode);
1112
+ const memory = createMemoryPersistence(health);
1113
+ if (mode === "none") {
1114
+ return {
1115
+ getItem() {
1116
+ return null;
1117
+ },
1118
+ setItem() {
1119
+ return false;
1120
+ },
1121
+ removeItem() {
1122
+ },
1123
+ getHealth() {
1124
+ return {
1125
+ ...health,
1126
+ failures: health.failures.map((failure) => ({ ...failure }))
1127
+ };
1128
+ }
1129
+ };
1130
+ }
1131
+ if (mode === "memory") return memory;
1132
+ const primary = mode === "sessionStorage" ? getSessionStorage() : getLocalStorage();
1133
+ const useCookie = mode === "cookie" || mode === "localStorage+cookie" || !primary;
1134
+ return {
1135
+ getItem(key) {
1136
+ try {
1137
+ const stored = primary?.getItem(key);
1138
+ if (stored) return stored;
1139
+ } catch (error) {
1140
+ recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "get", error);
1141
+ }
1142
+ if (useCookie) {
1143
+ const cookie = getCookie2(key);
1144
+ if (cookie) {
1145
+ health.cookieFallbackUsed = true;
1146
+ return cookie;
1147
+ }
1148
+ }
1149
+ const value = memory.getItem(key);
1150
+ if (value !== null) health.memoryFallbackUsed = true;
1151
+ return value;
1152
+ },
1153
+ setItem(key, value) {
1154
+ let wrote = false;
1155
+ try {
1156
+ primary?.setItem(key, value);
1157
+ wrote = primary !== void 0;
1158
+ } catch (error) {
1159
+ recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "set", error);
1160
+ wrote = false;
1161
+ }
1162
+ if (useCookie) {
1163
+ const cookieWrote = setCookie(key, value);
1164
+ if (cookieWrote) health.cookieFallbackUsed = true;
1165
+ wrote = cookieWrote || wrote;
1166
+ }
1167
+ if (!wrote) {
1168
+ health.memoryFallbackUsed = true;
1169
+ return memory.setItem(key, value);
1170
+ }
1171
+ if (mode === "localStorage+cookie") {
1172
+ memory.setItem(key, value);
1173
+ }
1174
+ return true;
1175
+ },
1176
+ removeItem(key) {
1177
+ try {
1178
+ primary?.removeItem(key);
1179
+ } catch (error) {
1180
+ recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "remove", error);
1181
+ }
1182
+ if (useCookie) removeCookie(key);
1183
+ memory.removeItem(key);
1184
+ },
1185
+ getHealth() {
1186
+ return {
1187
+ ...health,
1188
+ failures: health.failures.map((failure) => ({ ...failure }))
1189
+ };
1190
+ }
1191
+ };
431
1192
  }
432
1193
 
433
- // src/browser/core/schema.ts
434
- var reservedKeys = /* @__PURE__ */ new Set(["messageId", "timestamp", "type", "event", "anonymousId", "userId", "context"]);
435
- function validateEventEnvelope(envelope, options = {}) {
436
- if (options.mode === "off") return { ok: true, errors: [] };
437
- const errors = [];
438
- if (typeof envelope.type !== "string") errors.push("type must be a string");
439
- if (envelope.type === "track" && typeof envelope.event !== "string") {
440
- errors.push("track event must include event name");
1194
+ // src/browser/core/session.ts
1195
+ var SESSION_STORAGE_KEY2 = "sessionData";
1196
+ function getUrlParam2(name) {
1197
+ const href = getBrowserWindow()?.location?.href;
1198
+ if (!href) return null;
1199
+ try {
1200
+ return new URL(href).searchParams.get(name);
1201
+ } catch {
1202
+ return null;
441
1203
  }
442
- if (typeof envelope.messageId !== "string") errors.push("messageId must be a string");
443
- if (typeof envelope.timestamp !== "string") errors.push("timestamp must be an ISO string");
444
- for (const key of Object.keys(envelope.properties ?? {})) {
445
- if (reservedKeys.has(key)) errors.push(`properties.${key} is reserved`);
1204
+ }
1205
+ function readStoredSession(persistence) {
1206
+ const rawSession = persistence.getItem(SESSION_STORAGE_KEY2);
1207
+ if (!rawSession) return void 0;
1208
+ try {
1209
+ const parsed = JSON.parse(rawSession);
1210
+ if (isUuid(parsed.sessionId) && typeof parsed.timestamp === "number") {
1211
+ return {
1212
+ sessionId: parsed.sessionId,
1213
+ timestamp: parsed.timestamp
1214
+ };
1215
+ }
1216
+ } catch {
1217
+ persistence.removeItem(SESSION_STORAGE_KEY2);
446
1218
  }
447
- return { ok: errors.length === 0, errors };
1219
+ return void 0;
1220
+ }
1221
+ function writeStoredSession(persistence, sessionId, timestamp) {
1222
+ persistence.setItem(SESSION_STORAGE_KEY2, JSON.stringify({ sessionId, timestamp }));
1223
+ }
1224
+ function createSessionManager(persistence, idleTimeoutSeconds = 30 * 60) {
1225
+ function resolveSessionId() {
1226
+ const now = Date.now();
1227
+ const urlSessionId = getUrlParam2("sid") ?? getUrlParam2("session_id");
1228
+ if (isUuid(urlSessionId)) {
1229
+ writeStoredSession(persistence, urlSessionId, now);
1230
+ return urlSessionId;
1231
+ }
1232
+ const storedSession = readStoredSession(persistence);
1233
+ if (storedSession && now - storedSession.timestamp <= idleTimeoutSeconds * 1e3) {
1234
+ writeStoredSession(persistence, storedSession.sessionId, now);
1235
+ return storedSession.sessionId;
1236
+ }
1237
+ const sessionId = createUuid();
1238
+ writeStoredSession(persistence, sessionId, now);
1239
+ return sessionId;
1240
+ }
1241
+ return {
1242
+ getSessionId() {
1243
+ return resolveSessionId();
1244
+ },
1245
+ reset() {
1246
+ const sessionId = createUuid();
1247
+ writeStoredSession(persistence, sessionId, Date.now());
1248
+ return sessionId;
1249
+ }
1250
+ };
448
1251
  }
449
1252
 
450
1253
  // src/browser/core/DriveMetaDataSDK.ts
451
- function createId2(prefix) {
452
- return `${prefix}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
453
- }
454
1254
  function endpointFromConfig(config) {
455
1255
  const host = config.apiHost ?? "https://sdk.drivemetadata.com/v2";
456
1256
  return `${host.replace(/\/$/, "")}/data-collector`;
@@ -461,14 +1261,6 @@ function requireConfigString(value, field) {
461
1261
  }
462
1262
  return value;
463
1263
  }
464
- function getBrowserStorage() {
465
- const browserWindow = getBrowserWindow();
466
- try {
467
- return browserWindow?.localStorage;
468
- } catch {
469
- return void 0;
470
- }
471
- }
472
1264
  var DriveMetaDataSDK = class {
473
1265
  constructor(config) {
474
1266
  this.initialized = true;
@@ -481,30 +1273,36 @@ var DriveMetaDataSDK = class {
481
1273
  requireConfigString(config.writeKey || config.token, "writeKey or token");
482
1274
  this.config = config;
483
1275
  this.endpoint = endpointFromConfig(config);
484
- const storage = getBrowserStorage();
1276
+ const storagePersistence = createBrowserPersistence(config.persistence);
1277
+ this.persistence = createDmdAttributesPersistence(storagePersistence);
1278
+ this.session = createSessionManager(this.persistence, config.sessionIdleTimeoutSeconds);
485
1279
  const deliveryConfig = {
486
- endpoint: this.endpoint
1280
+ endpoint: this.endpoint,
1281
+ storage: storagePersistence
487
1282
  };
488
1283
  if (config.delivery?.maxQueueSize !== void 0) deliveryConfig.maxQueueSize = config.delivery.maxQueueSize;
489
1284
  if (config.delivery?.queueTtlMs !== void 0) deliveryConfig.queueTtlMs = config.delivery.queueTtlMs;
490
1285
  if (config.delivery?.retryDelayMs !== void 0) deliveryConfig.retryDelayMs = config.delivery.retryDelayMs;
491
1286
  if (config.delivery?.maxRetryDelayMs !== void 0) deliveryConfig.maxRetryDelayMs = config.delivery.maxRetryDelayMs;
492
1287
  if (config.delivery?.maxPayloadBytes !== void 0) deliveryConfig.maxPayloadBytes = config.delivery.maxPayloadBytes;
1288
+ if (config.delivery?.payloadSizePolicy !== void 0) deliveryConfig.payloadSizePolicy = config.delivery.payloadSizePolicy;
1289
+ if (config.delivery?.payloadTruncateStringLength !== void 0) {
1290
+ deliveryConfig.payloadTruncateStringLength = config.delivery.payloadTruncateStringLength;
1291
+ }
1292
+ if (config.delivery?.useBeacon !== void 0) deliveryConfig.useBeacon = config.delivery.useBeacon;
493
1293
  if (config.delivery?.batchSize !== void 0) deliveryConfig.batchSize = config.delivery.batchSize;
494
1294
  if (config.onDrop !== void 0) deliveryConfig.onDrop = config.onDrop;
495
1295
  if (config.onError !== void 0) deliveryConfig.onError = config.onError;
496
- this.delivery = createDeliveryManager(storage ? {
497
- ...deliveryConfig,
498
- storage
499
- } : deliveryConfig);
1296
+ this.delivery = createDeliveryManager(deliveryConfig);
500
1297
  this.initialRetryDelayMs = config.delivery?.retryDelayMs ?? 1e3;
501
1298
  this.retryDelayMs = this.initialRetryDelayMs;
502
1299
  this.maxRetryDelayMs = config.delivery?.maxRetryDelayMs ?? 3e4;
503
1300
  this.writeKey = config.writeKey || config.token || "";
504
- this.identity = { anonymousId: createId2("anon") };
505
- this.sessionId = createId2("session");
1301
+ this.identity = { anonymousId: resolveAnonymousId(this.persistence) };
1302
+ captureAttributionFromUrl(this.persistence);
506
1303
  this.consentState = normalizeConsent(config.gdprConsent ?? config.consent);
507
1304
  this.gdprConsent = this.consentState.analytics;
1305
+ this.installAutocapture();
508
1306
  if (!config.delivery?.disableLifecycleFlush) {
509
1307
  this.installLifecycleFlush();
510
1308
  }
@@ -544,7 +1342,8 @@ var DriveMetaDataSDK = class {
544
1342
  }
545
1343
  }
546
1344
  reset() {
547
- this.identity = { anonymousId: createId2("anon") };
1345
+ this.identity = { anonymousId: resetAnonymousId(this.persistence) };
1346
+ this.session.reset();
548
1347
  this.queue = [];
549
1348
  this.offline = false;
550
1349
  if (this.retryTimer !== void 0) {
@@ -553,8 +1352,20 @@ var DriveMetaDataSDK = class {
553
1352
  }
554
1353
  this.lifecycleCleanup?.();
555
1354
  this.lifecycleCleanup = void 0;
1355
+ this.autocaptureCleanup?.();
1356
+ this.autocaptureCleanup = void 0;
556
1357
  this.delivery.clearQueue("manual_clear");
557
1358
  }
1359
+ disposeForTests() {
1360
+ if (this.retryTimer !== void 0) {
1361
+ clearTimeout(this.retryTimer);
1362
+ this.retryTimer = void 0;
1363
+ }
1364
+ this.lifecycleCleanup?.();
1365
+ this.lifecycleCleanup = void 0;
1366
+ this.autocaptureCleanup?.();
1367
+ this.autocaptureCleanup = void 0;
1368
+ }
558
1369
  setConsent(consent) {
559
1370
  this.consentState = typeof consent === "object" ? mergeConsent(this.consentState, consent) : normalizeConsent(consent);
560
1371
  this.gdprConsent = this.consentState.analytics;
@@ -569,6 +1380,7 @@ var DriveMetaDataSDK = class {
569
1380
  initialized: this.initialized,
570
1381
  consent: this.gdprConsent,
571
1382
  consentPurposes: this.consentState,
1383
+ persistence: this.persistence.getHealth(),
572
1384
  queueSize: deliveryDiagnostics.queued,
573
1385
  offline: this.offline || deliveryDiagnostics.queued > 0,
574
1386
  droppedEvents: this.droppedEvents + deliveryDiagnostics.dropped.length,
@@ -583,7 +1395,9 @@ var DriveMetaDataSDK = class {
583
1395
  void this.delivery.send(payload).then(() => {
584
1396
  const diagnostics = this.delivery.getDiagnostics();
585
1397
  this.offline = diagnostics.queued > 0;
586
- this.lastError = diagnostics.lastError;
1398
+ if (diagnostics.lastError !== void 0) {
1399
+ this.lastError = diagnostics.lastError;
1400
+ }
587
1401
  if (diagnostics.queued > 0) {
588
1402
  this.scheduleRetryFlush();
589
1403
  }
@@ -615,6 +1429,28 @@ var DriveMetaDataSDK = class {
615
1429
  }
616
1430
  };
617
1431
  }
1432
+ installAutocapture() {
1433
+ const browserWindow = getBrowserWindow();
1434
+ if (!browserWindow) return;
1435
+ if (this.config.autocapture === false) return;
1436
+ const capturePageview = this.config.capturePageview !== false;
1437
+ const capturePageleave = this.config.capturePageleave !== false;
1438
+ const controller = installAutocapture({
1439
+ browserWindow,
1440
+ capturePageview,
1441
+ capturePageleave,
1442
+ onPageView: () => {
1443
+ this.page();
1444
+ },
1445
+ onPageLeave: () => {
1446
+ this.trackEvent("page_leave", { url: browserWindow.location.href });
1447
+ },
1448
+ onRouteChange: () => {
1449
+ captureAttributionFromUrl(this.persistence);
1450
+ }
1451
+ });
1452
+ this.autocaptureCleanup = controller.cleanup;
1453
+ }
618
1454
  scheduleRetryFlush() {
619
1455
  if (this.retryTimer !== void 0) return;
620
1456
  const delay = Math.min(this.retryDelayMs, this.maxRetryDelayMs);
@@ -640,7 +1476,7 @@ var DriveMetaDataSDK = class {
640
1476
  type,
641
1477
  event,
642
1478
  properties: sanitizeProperties(properties),
643
- messageId: options.messageId ?? createId2("msg"),
1479
+ messageId: ensureUuid(options.messageId),
644
1480
  timestamp: options.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
645
1481
  context: options.context ?? {},
646
1482
  anonymousId: this.identity.anonymousId,
@@ -649,7 +1485,7 @@ var DriveMetaDataSDK = class {
649
1485
  appId: this.config.appId,
650
1486
  writeKey: this.writeKey,
651
1487
  consent: this.consentState,
652
- sessionId: this.sessionId
1488
+ sessionId: this.session.getSessionId()
653
1489
  };
654
1490
  if (this.identity.userId !== void 0) prepared.userId = this.identity.userId;
655
1491
  if (this.identity.groupId !== void 0) prepared.groupId = this.identity.groupId;
@@ -673,10 +1509,21 @@ var DriveMetaDataSDK = class {
673
1509
  if (!validation.ok) {
674
1510
  this.lastError = `DMD SDK schema warning: ${validation.errors.join(", ")}`;
675
1511
  }
676
- this.sendEvent(this.toCollectorPayload(prepared));
1512
+ const collectorPayload = this.toCollectorPayload(prepared);
1513
+ const backendValidation = validateBackendCollectorPayload(collectorPayload);
1514
+ if (!backendValidation.ok && this.config.schemaValidation === "strict") {
1515
+ this.lastError = `DMD SDK backend schema warning: ${backendValidation.errors.join(", ")}`;
1516
+ this.recordDrop(type, "invalid_payload", event);
1517
+ return;
1518
+ }
1519
+ if (!backendValidation.ok) {
1520
+ this.lastError = `DMD SDK backend schema warning: ${backendValidation.errors.join(", ")}`;
1521
+ }
1522
+ this.sendEvent(collectorPayload);
677
1523
  }
678
1524
  toCollectorPayload(prepared) {
679
1525
  const browserWindow = getBrowserWindow();
1526
+ const pageContext = getBrowserPageContext();
680
1527
  return createBackendCollectorPayload({
681
1528
  requestId: prepared.messageId,
682
1529
  timestamp: prepared.timestamp,
@@ -688,15 +1535,19 @@ var DriveMetaDataSDK = class {
688
1535
  sessionId: prepared.sessionId,
689
1536
  ua: browserWindow?.navigator?.userAgent,
690
1537
  appId: prepared.appId,
691
- pageUrl: browserWindow?.location?.href,
692
- eventData: {
1538
+ pageUrl: pageContext.url ?? browserWindow?.location?.href,
1539
+ eventData: mergeStoredAttribution({
693
1540
  ...prepared.properties,
1541
+ page: {
1542
+ ...pageContext,
1543
+ ...prepared.properties.page && typeof prepared.properties.page === "object" ? prepared.properties.page : {}
1544
+ },
694
1545
  anonymousId: prepared.anonymousId,
695
1546
  sessionId: prepared.sessionId,
696
1547
  timestamp: prepared.timestamp,
697
1548
  requestSentAt: prepared.timestamp,
698
1549
  requestReceivedAt: prepared.timestamp
699
- }
1550
+ }, this.persistence)
700
1551
  });
701
1552
  }
702
1553
  recordDrop(type, reason, event) {
@@ -772,7 +1623,7 @@ function initDmdSDK(config) {
772
1623
  return publicSingleton;
773
1624
  }
774
1625
  try {
775
- const instance = new DriveMetaDataSDK(config);
1626
+ const instance = new DriveMetaDataSDK(normalizeBrowserConfig(config));
776
1627
  return setSingleton(instance);
777
1628
  } catch (error) {
778
1629
  lastError = error instanceof Error ? error.message : String(error);