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