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