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