@drivemetadata-ai/sdk 0.1.0

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +241 -0
  4. package/dist/angular/index.cjs +1675 -0
  5. package/dist/angular/index.cjs.map +1 -0
  6. package/dist/angular/index.d.cts +26 -0
  7. package/dist/angular/index.d.ts +26 -0
  8. package/dist/angular/index.js +1649 -0
  9. package/dist/angular/index.js.map +1 -0
  10. package/dist/browser/index.cjs +1635 -0
  11. package/dist/browser/index.cjs.map +1 -0
  12. package/dist/browser/index.d.cts +21 -0
  13. package/dist/browser/index.d.ts +21 -0
  14. package/dist/browser/index.js +1595 -0
  15. package/dist/browser/index.js.map +1 -0
  16. package/dist/next/index.cjs +1693 -0
  17. package/dist/next/index.cjs.map +1 -0
  18. package/dist/next/index.d.cts +18 -0
  19. package/dist/next/index.d.ts +18 -0
  20. package/dist/next/index.js +1650 -0
  21. package/dist/next/index.js.map +1 -0
  22. package/dist/node/index.cjs +384 -0
  23. package/dist/node/index.cjs.map +1 -0
  24. package/dist/node/index.d.cts +91 -0
  25. package/dist/node/index.d.ts +91 -0
  26. package/dist/node/index.js +354 -0
  27. package/dist/node/index.js.map +1 -0
  28. package/dist/react/index.cjs +1721 -0
  29. package/dist/react/index.cjs.map +1 -0
  30. package/dist/react/index.d.cts +26 -0
  31. package/dist/react/index.d.ts +26 -0
  32. package/dist/react/index.js +1674 -0
  33. package/dist/react/index.js.map +1 -0
  34. package/dist/types-mgbdL1V7.d.cts +123 -0
  35. package/dist/types-mgbdL1V7.d.ts +123 -0
  36. package/docs/angular-integration.md +106 -0
  37. package/docs/architecture.md +109 -0
  38. package/docs/index.md +18 -0
  39. package/docs/integration.md +520 -0
  40. package/docs/node-server-integration.md +147 -0
  41. package/docs/npm-browser-sdk.md +143 -0
  42. package/docs/react-next-integration.md +168 -0
  43. package/docs/security-privacy.md +128 -0
  44. package/package.json +101 -0
@@ -0,0 +1,1650 @@
1
+ "use client";
2
+
3
+ // src/react/DmdProvider.tsx
4
+ import React from "react";
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
+
57
+ // src/core/backend-payload.ts
58
+ function formatUtcTimestamp(value) {
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);
62
+ }
63
+ function cleanObject(value) {
64
+ if (Array.isArray(value)) {
65
+ const cleaned = value.map((item) => cleanObject(item)).filter((item) => item !== null && item !== void 0 && item !== "");
66
+ return cleaned.length > 0 ? cleaned : void 0;
67
+ }
68
+ if (value && typeof value === "object") {
69
+ const cleaned = Object.fromEntries(
70
+ Object.entries(value).map(([key, item]) => [key, cleanObject(item)]).filter(([, item]) => {
71
+ if (item === null || item === void 0 || item === "") return false;
72
+ if (typeof item === "object" && !Array.isArray(item) && Object.keys(item).length === 0) return false;
73
+ return true;
74
+ })
75
+ );
76
+ return Object.keys(cleaned).length > 0 ? cleaned : void 0;
77
+ }
78
+ return value;
79
+ }
80
+ function createBackendCollectorPayload(input) {
81
+ const eventData = input.eventData && typeof input.eventData === "object" ? input.eventData : {};
82
+ const page2 = eventData.page && typeof eventData.page === "object" ? eventData.page : {};
83
+ const timestamp = formatUtcTimestamp(eventData.timestamp ?? input.timestamp);
84
+ const normalizedEventData = {
85
+ ...eventData,
86
+ timestamp,
87
+ requestSentAt: formatUtcTimestamp(eventData.requestSentAt ?? input.requestSentAt ?? timestamp),
88
+ requestReceivedAt: formatUtcTimestamp(eventData.requestReceivedAt ?? input.requestReceivedAt ?? timestamp)
89
+ };
90
+ const metaData = {
91
+ ...normalizedEventData,
92
+ requestId: ensureUuid(typeof input.requestId === "string" ? input.requestId : void 0),
93
+ timestamp,
94
+ eventType: input.eventType,
95
+ requestFrom: input.requestFrom ?? "3",
96
+ clientId: input.clientId,
97
+ workspaceId: input.workspaceId,
98
+ token: input.token,
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
+ ),
105
+ ua: input.ua,
106
+ appDetails: { app_id: input.appId },
107
+ page: { ...page2, url: page2.url ?? input.pageUrl },
108
+ requestSentAt: normalizedEventData.requestSentAt,
109
+ requestReceivedAt: normalizedEventData.requestReceivedAt
110
+ };
111
+ const payload = { metaData };
112
+ return cleanObject(payload);
113
+ }
114
+
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
+ };
151
+ }
152
+
153
+ // src/core/consent.ts
154
+ var purposes = [
155
+ "analytics",
156
+ "advertising",
157
+ "personalization",
158
+ "functional",
159
+ "saleOfData"
160
+ ];
161
+ function normalizeConsentValue(value) {
162
+ if (value === true) return "granted";
163
+ if (value === false) return "denied";
164
+ if (value === "granted" || value === "denied" || value === "pending") return value;
165
+ return "pending";
166
+ }
167
+ function normalizeConsent(input) {
168
+ const defaultValue = normalizeConsentValue(input ?? "pending");
169
+ const resolved = Object.fromEntries(
170
+ purposes.map((purpose) => [purpose, defaultValue])
171
+ );
172
+ if (input && typeof input === "object") {
173
+ for (const purpose of purposes) {
174
+ const value = input[purpose];
175
+ if (value !== void 0) {
176
+ resolved[purpose] = normalizeConsentValue(value);
177
+ }
178
+ }
179
+ }
180
+ return resolved;
181
+ }
182
+ function mergeConsent(current, update) {
183
+ const next = { ...current };
184
+ for (const [purpose, value] of Object.entries(update)) {
185
+ next[purpose] = normalizeConsentValue(value);
186
+ }
187
+ return next;
188
+ }
189
+ function canCollectPurpose(consent, purpose) {
190
+ return consent[purpose] === "granted";
191
+ }
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/core/attribution.ts
263
+ var dmdUtmKeys = [
264
+ "utm_source",
265
+ "utm_medium",
266
+ "utm_campaign",
267
+ "utm_term",
268
+ "utm_content",
269
+ "utm_id"
270
+ ];
271
+ var dmdCampaignKeys = ["campaign_id", "ad_id"];
272
+ var dmdClickIdSources = [
273
+ ["gclid", 2],
274
+ ["fbclid", 3],
275
+ ["ScCid", 1],
276
+ ["li_fat_id", 4]
277
+ ];
278
+ function cleanAttributionRecord(record) {
279
+ const cleaned = Object.fromEntries(
280
+ Object.entries(record).filter(([, value]) => value !== null && value !== void 0 && value !== "")
281
+ );
282
+ return Object.keys(cleaned).length > 0 ? cleaned : void 0;
283
+ }
284
+ function objectValue(value) {
285
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
286
+ }
287
+ function mergeAttributionRecords(explicitProperties, stored) {
288
+ const attributionData = objectValue(explicitProperties.attributionData);
289
+ const utmParameter = objectValue(explicitProperties.utmParameter);
290
+ return {
291
+ ...explicitProperties,
292
+ ...stored.attributionData || attributionData ? { attributionData: { ...stored.attributionData ?? {}, ...attributionData ?? {} } } : {},
293
+ ...stored.utmParameter || utmParameter ? { utmParameter: { ...stored.utmParameter ?? {}, ...utmParameter ?? {} } } : {}
294
+ };
295
+ }
296
+
297
+ // src/browser/core/attribution.ts
298
+ function getCurrentUrl() {
299
+ return getBrowserWindow()?.location?.href;
300
+ }
301
+ function getCookie(name) {
302
+ const cookie = getBrowserWindow()?.document?.cookie;
303
+ if (!cookie) return void 0;
304
+ const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
305
+ const match = new RegExp(`(?:^|; )${escapedName}=([^;]*)`).exec(cookie);
306
+ return match ? decodeURIComponent(match[1] ?? "") : void 0;
307
+ }
308
+ function getParams(url) {
309
+ return new URL(url, "https://placeholder.local").searchParams;
310
+ }
311
+ function setIfPresent(persistence, key, value) {
312
+ if (value !== null && value !== "") {
313
+ persistence.setItem(key, value);
314
+ }
315
+ }
316
+ function getStoredString(persistence, key) {
317
+ const value = persistence.getItem(key);
318
+ return value === null || value === "" ? void 0 : value;
319
+ }
320
+ function getStoredNumberOrString(persistence, key) {
321
+ const value = getStoredString(persistence, key);
322
+ if (value === void 0) return void 0;
323
+ const parsed = Number.parseInt(value, 10);
324
+ return Number.isNaN(parsed) ? value : parsed;
325
+ }
326
+ function captureAttributionFromUrl(persistence, url = getCurrentUrl()) {
327
+ if (!url) return;
328
+ const params = getParams(url);
329
+ for (const key of dmdUtmKeys) {
330
+ setIfPresent(persistence, key, params.get(key));
331
+ }
332
+ for (const key of dmdCampaignKeys) {
333
+ setIfPresent(persistence, key, params.get(key));
334
+ }
335
+ for (const [param, sdkPubId] of dmdClickIdSources) {
336
+ const value = params.get(param);
337
+ if (value) {
338
+ persistence.setItem("unique_id", value);
339
+ persistence.setItem("sdk_pub_id", String(sdkPubId));
340
+ }
341
+ }
342
+ }
343
+ function getStoredAttributionData(persistence) {
344
+ return cleanAttributionRecord({
345
+ unique_id: getStoredString(persistence, "unique_id"),
346
+ sdk_pub_id: getStoredNumberOrString(persistence, "sdk_pub_id"),
347
+ fbc: getStoredString(persistence, "fbc") ?? getCookie("_fbc"),
348
+ fbp: getStoredString(persistence, "fbp") ?? getCookie("_fbp"),
349
+ campaign_id: getStoredString(persistence, "campaign_id"),
350
+ ad_id: getStoredString(persistence, "ad_id")
351
+ });
352
+ }
353
+ function getStoredUtmParameter(persistence) {
354
+ return cleanAttributionRecord(Object.fromEntries(dmdUtmKeys.map((key) => [key, getStoredString(persistence, key)])));
355
+ }
356
+ function mergeStoredAttribution(properties, persistence) {
357
+ const stored = {};
358
+ const attributionData = getStoredAttributionData(persistence);
359
+ const utmParameter = getStoredUtmParameter(persistence);
360
+ if (attributionData !== void 0) stored.attributionData = attributionData;
361
+ if (utmParameter !== void 0) stored.utmParameter = utmParameter;
362
+ return mergeAttributionRecords(properties, stored);
363
+ }
364
+
365
+ // src/browser/core/autocapture.ts
366
+ function installAutocapture(config) {
367
+ const trackedPageUrls = /* @__PURE__ */ new Set();
368
+ const timers = /* @__PURE__ */ new Set();
369
+ const pageviewDelayMs = config.pageviewDelayMs ?? 500;
370
+ const history = config.browserWindow.history;
371
+ const originalPushState = history?.pushState;
372
+ const originalReplaceState = history?.replaceState;
373
+ function clearTimer(timer) {
374
+ timers.delete(timer);
375
+ clearTimeout(timer);
376
+ }
377
+ function canonicalUrl() {
378
+ return config.browserWindow.location.href;
379
+ }
380
+ function trackCurrentPageview() {
381
+ if (!config.capturePageview) return;
382
+ const url = canonicalUrl();
383
+ if (trackedPageUrls.has(url)) return;
384
+ trackedPageUrls.add(url);
385
+ config.onPageView();
386
+ }
387
+ function schedulePageview(delayMs) {
388
+ if (!config.capturePageview) return;
389
+ const timer = setTimeout(() => {
390
+ timers.delete(timer);
391
+ trackCurrentPageview();
392
+ }, delayMs);
393
+ timers.add(timer);
394
+ }
395
+ function handleRouteChange() {
396
+ config.onRouteChange?.();
397
+ trackCurrentPageview();
398
+ }
399
+ function wrapHistoryMethod(method) {
400
+ if (!history) return;
401
+ const original = history[method];
402
+ if (typeof original !== "function") return;
403
+ history[method] = function wrappedHistoryMethod(...args) {
404
+ const result = original.apply(this, args);
405
+ const timer = setTimeout(() => {
406
+ timers.delete(timer);
407
+ handleRouteChange();
408
+ }, 0);
409
+ timers.add(timer);
410
+ return result;
411
+ };
412
+ }
413
+ function handlePopOrHashChange() {
414
+ handleRouteChange();
415
+ }
416
+ function handlePageLeave() {
417
+ if (config.capturePageleave) {
418
+ config.onPageLeave();
419
+ }
420
+ }
421
+ schedulePageview(pageviewDelayMs);
422
+ wrapHistoryMethod("pushState");
423
+ wrapHistoryMethod("replaceState");
424
+ const canListen = typeof config.browserWindow.addEventListener === "function" && typeof config.browserWindow.removeEventListener === "function";
425
+ if (canListen) {
426
+ config.browserWindow.addEventListener("popstate", handlePopOrHashChange);
427
+ config.browserWindow.addEventListener("hashchange", handlePopOrHashChange);
428
+ config.browserWindow.addEventListener("beforeunload", handlePageLeave);
429
+ }
430
+ return {
431
+ cleanup() {
432
+ for (const timer of Array.from(timers)) {
433
+ clearTimer(timer);
434
+ }
435
+ if (history && originalPushState) history.pushState = originalPushState;
436
+ if (history && originalReplaceState) history.replaceState = originalReplaceState;
437
+ if (canListen) {
438
+ config.browserWindow.removeEventListener("popstate", handlePopOrHashChange);
439
+ config.browserWindow.removeEventListener("hashchange", handlePopOrHashChange);
440
+ config.browserWindow.removeEventListener("beforeunload", handlePageLeave);
441
+ }
442
+ }
443
+ };
444
+ }
445
+
446
+ // src/core/payload-size.ts
447
+ var preserveStringKeys = /* @__PURE__ */ new Set([
448
+ "requestId",
449
+ "eventType",
450
+ "requestFrom",
451
+ "clientId",
452
+ "workspaceId",
453
+ "anonymousId",
454
+ "sessionId",
455
+ "token"
456
+ ]);
457
+ function payloadByteLength(payload) {
458
+ const serialized = JSON.stringify(payload);
459
+ if (typeof TextEncoder !== "undefined") {
460
+ return new TextEncoder().encode(serialized).length;
461
+ }
462
+ return serialized.length;
463
+ }
464
+ function clonePayload(payload) {
465
+ try {
466
+ return JSON.parse(JSON.stringify(payload));
467
+ } catch {
468
+ return void 0;
469
+ }
470
+ }
471
+ function truncateValue(value, truncateStringLength, key) {
472
+ if (typeof value === "string") {
473
+ if (key && preserveStringKeys.has(key)) return value;
474
+ if (value.length <= truncateStringLength) return value;
475
+ return `${value.slice(0, truncateStringLength)}...[TRUNCATED]`;
476
+ }
477
+ if (Array.isArray(value)) {
478
+ return value.map((item) => truncateValue(item, truncateStringLength));
479
+ }
480
+ if (value && typeof value === "object") {
481
+ return Object.fromEntries(
482
+ Object.entries(value).map(([childKey, childValue]) => [
483
+ childKey,
484
+ truncateValue(childValue, truncateStringLength, childKey)
485
+ ])
486
+ );
487
+ }
488
+ return value;
489
+ }
490
+ function markPayloadTruncated(payload) {
491
+ if (payload.metaData && typeof payload.metaData === "object" && !Array.isArray(payload.metaData)) {
492
+ return {
493
+ ...payload,
494
+ metaData: {
495
+ ...payload.metaData,
496
+ payloadTruncated: true
497
+ }
498
+ };
499
+ }
500
+ return {
501
+ ...payload,
502
+ payloadTruncated: true
503
+ };
504
+ }
505
+ function truncatePayload(payload, truncateStringLength) {
506
+ const cloned = clonePayload(payload);
507
+ if (!cloned) return void 0;
508
+ return markPayloadTruncated(truncateValue(cloned, truncateStringLength));
509
+ }
510
+ function getPayloadMessageId(payload) {
511
+ const value = payload.metaData?.requestId ?? payload.messageId;
512
+ return value === void 0 ? void 0 : String(value);
513
+ }
514
+ function preparePayloadForSizePolicy(payload, options = {}) {
515
+ const maxPayloadBytes = options.maxPayloadBytes ?? 64e3;
516
+ const payloadSizePolicy = options.payloadSizePolicy ?? "drop";
517
+ const payloadTruncateStringLength = options.payloadTruncateStringLength ?? 1024;
518
+ if (payloadByteLength(payload) <= maxPayloadBytes) {
519
+ return { ok: true, payload, truncated: false };
520
+ }
521
+ if (payloadSizePolicy === "truncate") {
522
+ const truncatedPayload = truncatePayload(payload, payloadTruncateStringLength);
523
+ if (truncatedPayload && payloadByteLength(truncatedPayload) <= maxPayloadBytes) {
524
+ return { ok: true, payload: truncatedPayload, truncated: true };
525
+ }
526
+ }
527
+ const messageId = getPayloadMessageId(payload);
528
+ return messageId === void 0 ? { ok: false, reason: "payload_too_large" } : { ok: false, reason: "payload_too_large", messageId };
529
+ }
530
+
531
+ // src/browser/core/delivery.ts
532
+ function createId(prefix) {
533
+ return `${prefix}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
534
+ }
535
+ function stableStringify(value) {
536
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
537
+ return JSON.stringify(value);
538
+ }
539
+ return JSON.stringify(
540
+ Object.fromEntries(
541
+ Object.entries(value).sort(([left], [right]) => left.localeCompare(right))
542
+ )
543
+ );
544
+ }
545
+ function createIdempotencyKey(payload, messageId) {
546
+ const metaData = payload.metaData;
547
+ const event = String(metaData?.eventType ?? payload.event ?? payload.type ?? "event");
548
+ const properties = metaData ?? payload.properties;
549
+ const orderId = properties?.orderId ?? properties?.order_id ?? properties?.transaction_id;
550
+ if (orderId !== void 0) return `${event}:${String(orderId)}`;
551
+ return `${event}:${stableStringify(properties ?? {}) || messageId}`;
552
+ }
553
+ function createDeliveryManager(config) {
554
+ const queue = [];
555
+ const diagnostics = { queued: 0, inFlight: 0, dropped: [] };
556
+ const maxQueueSize = config.maxQueueSize ?? 100;
557
+ const queueKey = config.queueKey ?? "dmd_delivery_queue";
558
+ const lockKey = config.lockKey ?? "dmd_delivery_flush_lock";
559
+ const lockTtlMs = config.lockTtlMs ?? 5e3;
560
+ const tabId = config.tabId ?? createId("tab");
561
+ const batchSize = config.batchSize ?? 25;
562
+ function recordDrop(event) {
563
+ diagnostics.dropped.push(event);
564
+ config.onDrop?.(event);
565
+ }
566
+ function recordError(error) {
567
+ diagnostics.lastError = error.message;
568
+ config.onError?.(error);
569
+ }
570
+ function preparePayloadForSend(payload) {
571
+ const sizePolicyOptions = {};
572
+ if (config.maxPayloadBytes !== void 0) sizePolicyOptions.maxPayloadBytes = config.maxPayloadBytes;
573
+ if (config.payloadSizePolicy !== void 0) sizePolicyOptions.payloadSizePolicy = config.payloadSizePolicy;
574
+ if (config.payloadTruncateStringLength !== void 0) {
575
+ sizePolicyOptions.payloadTruncateStringLength = config.payloadTruncateStringLength;
576
+ }
577
+ const prepared = preparePayloadForSizePolicy(payload, sizePolicyOptions);
578
+ if (prepared.ok) return prepared.payload;
579
+ const diagnostic = {
580
+ reason: prepared.reason,
581
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
582
+ };
583
+ if (prepared.messageId !== void 0) diagnostic.messageId = prepared.messageId;
584
+ recordDrop(diagnostic);
585
+ return void 0;
586
+ }
587
+ function recordStorageUnavailable() {
588
+ recordDrop({
589
+ reason: "storage_unavailable",
590
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
591
+ });
592
+ }
593
+ function safeGetItem(key) {
594
+ try {
595
+ return config.storage?.getItem(key) ?? null;
596
+ } catch {
597
+ recordStorageUnavailable();
598
+ return null;
599
+ }
600
+ }
601
+ function safeSetItem(key, value) {
602
+ try {
603
+ config.storage?.setItem(key, value);
604
+ return true;
605
+ } catch {
606
+ recordStorageUnavailable();
607
+ return false;
608
+ }
609
+ }
610
+ function safeRemoveItem(key) {
611
+ try {
612
+ config.storage?.removeItem(key);
613
+ return true;
614
+ } catch {
615
+ recordStorageUnavailable();
616
+ return false;
617
+ }
618
+ }
619
+ function persistQueue() {
620
+ if (!config.storage) return;
621
+ if (queue.length === 0) {
622
+ safeRemoveItem(queueKey);
623
+ return;
624
+ }
625
+ safeSetItem(queueKey, JSON.stringify(queue));
626
+ }
627
+ function loadQueue() {
628
+ if (!config.storage) return;
629
+ const rawQueue = safeGetItem(queueKey);
630
+ if (!rawQueue) return;
631
+ try {
632
+ const records = JSON.parse(rawQueue);
633
+ queue.splice(0, queue.length, ...records.filter((record) => record && typeof record.messageId === "string"));
634
+ diagnostics.queued = queue.length;
635
+ } catch {
636
+ safeRemoveItem(queueKey);
637
+ recordDrop({
638
+ reason: "queue_corrupt",
639
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
640
+ });
641
+ }
642
+ }
643
+ function enqueue(record) {
644
+ queue.push(record);
645
+ while (queue.length > maxQueueSize) {
646
+ const dropped = queue.shift();
647
+ const diagnostic = {
648
+ reason: "queue_limit_exceeded",
649
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
650
+ };
651
+ if (dropped?.messageId !== void 0) diagnostic.messageId = dropped.messageId;
652
+ recordDrop(diagnostic);
653
+ }
654
+ diagnostics.queued = queue.length;
655
+ persistQueue();
656
+ }
657
+ function withEnvelope(payload) {
658
+ const metaData = payload.metaData;
659
+ const messageId = String(metaData?.requestId ?? payload.messageId ?? createId("msg"));
660
+ if (metaData) {
661
+ return {
662
+ ...payload,
663
+ metaData: {
664
+ ...metaData,
665
+ requestId: messageId
666
+ }
667
+ };
668
+ }
669
+ return {
670
+ ...payload,
671
+ messageId,
672
+ idempotencyKey: String(payload.idempotencyKey ?? createIdempotencyKey(payload, messageId))
673
+ };
674
+ }
675
+ function tryBeacon(body) {
676
+ if (!config.useBeacon) return false;
677
+ const sendBeacon = globalThis.navigator?.sendBeacon;
678
+ if (typeof sendBeacon !== "function") return false;
679
+ try {
680
+ const blob = new Blob([JSON.stringify(body)], { type: "application/json" });
681
+ return sendBeacon.call(globalThis.navigator, config.endpoint, blob);
682
+ } catch {
683
+ return false;
684
+ }
685
+ }
686
+ async function deliver(body) {
687
+ if (tryBeacon(body)) return;
688
+ const fetchImpl = config.fetch ?? globalThis.fetch;
689
+ if (typeof fetchImpl !== "function") {
690
+ throw new Error("fetch_unavailable");
691
+ }
692
+ const response = await fetchImpl(config.endpoint, {
693
+ method: "POST",
694
+ headers: { "Content-Type": "application/json" },
695
+ body: JSON.stringify(body),
696
+ keepalive: true
697
+ });
698
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
699
+ }
700
+ loadQueue();
701
+ return {
702
+ async send(payload) {
703
+ const body = withEnvelope(payload);
704
+ const preparedBody = preparePayloadForSend(body);
705
+ if (!preparedBody) {
706
+ return;
707
+ }
708
+ diagnostics.inFlight += 1;
709
+ try {
710
+ await deliver(preparedBody);
711
+ } catch (error) {
712
+ const deliveryError = error instanceof Error ? error : new Error(String(error));
713
+ recordError(deliveryError);
714
+ enqueue({
715
+ messageId: String(getPayloadMessageId(body)),
716
+ savedAt: Date.now(),
717
+ attempts: 1,
718
+ lastError: deliveryError.message,
719
+ payload: preparedBody
720
+ });
721
+ } finally {
722
+ diagnostics.inFlight -= 1;
723
+ diagnostics.queued = queue.length;
724
+ }
725
+ },
726
+ async flushQueue() {
727
+ this.flushExpired();
728
+ if (queue.length === 0 || !this.acquireFlushLease()) return;
729
+ diagnostics.inFlight += 1;
730
+ try {
731
+ let sentInBatch = 0;
732
+ for (let index = 0; index < queue.length && sentInBatch < batchSize; ) {
733
+ const record = queue[index];
734
+ if (!record) {
735
+ index += 1;
736
+ continue;
737
+ }
738
+ try {
739
+ await deliver(record.payload);
740
+ queue.splice(index, 1);
741
+ sentInBatch += 1;
742
+ persistQueue();
743
+ } catch (error) {
744
+ const deliveryError = error instanceof Error ? error : new Error(String(error));
745
+ record.attempts += 1;
746
+ record.lastError = deliveryError.message;
747
+ recordError(deliveryError);
748
+ index += 1;
749
+ persistQueue();
750
+ break;
751
+ }
752
+ }
753
+ } finally {
754
+ diagnostics.inFlight -= 1;
755
+ diagnostics.queued = queue.length;
756
+ this.releaseFlushLease();
757
+ }
758
+ },
759
+ clearQueue(reason) {
760
+ for (const record of queue) {
761
+ recordDrop({
762
+ messageId: record.messageId,
763
+ reason,
764
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
765
+ });
766
+ }
767
+ queue.splice(0, queue.length);
768
+ diagnostics.queued = 0;
769
+ persistQueue();
770
+ },
771
+ enqueueForTests(record) {
772
+ enqueue(record);
773
+ },
774
+ flushExpired() {
775
+ const ttl = config.queueTtlMs ?? 864e5;
776
+ const now = Date.now();
777
+ for (let index = queue.length - 1; index >= 0; index -= 1) {
778
+ const record = queue[index];
779
+ if (record && now - record.savedAt > ttl) {
780
+ recordDrop({
781
+ messageId: record.messageId,
782
+ reason: "queue_ttl_expired",
783
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
784
+ });
785
+ queue.splice(index, 1);
786
+ }
787
+ }
788
+ diagnostics.queued = queue.length;
789
+ persistQueue();
790
+ },
791
+ acquireFlushLease() {
792
+ if (!config.storage) return true;
793
+ const now = Date.now();
794
+ const rawLock = safeGetItem(lockKey);
795
+ let existingLock;
796
+ if (rawLock) {
797
+ try {
798
+ existingLock = JSON.parse(rawLock);
799
+ } catch {
800
+ existingLock = void 0;
801
+ }
802
+ }
803
+ const lockExpired = !existingLock?.expiresAt || existingLock.expiresAt <= now;
804
+ const lockOwnedByThisTab = existingLock?.owner === tabId;
805
+ if (!existingLock || lockExpired || lockOwnedByThisTab) {
806
+ if (!safeSetItem(lockKey, JSON.stringify({ owner: tabId, expiresAt: now + lockTtlMs }))) {
807
+ return true;
808
+ }
809
+ return true;
810
+ }
811
+ return false;
812
+ },
813
+ releaseFlushLease() {
814
+ if (!config.storage) return;
815
+ const rawLock = safeGetItem(lockKey);
816
+ if (!rawLock) return;
817
+ try {
818
+ const existingLock = JSON.parse(rawLock);
819
+ if (existingLock.owner === tabId) {
820
+ safeRemoveItem(lockKey);
821
+ }
822
+ } catch {
823
+ safeRemoveItem(lockKey);
824
+ }
825
+ },
826
+ getDiagnostics() {
827
+ return diagnostics;
828
+ }
829
+ };
830
+ }
831
+
832
+ // src/browser/core/identity.ts
833
+ var ANONYMOUS_ID_STORAGE_KEY = "dmd_anonymous_id";
834
+ var LEGACY_ANONYMOUS_ID_STORAGE_KEY = "anonymousId";
835
+ function getUrlParam(name) {
836
+ const href = getBrowserWindow()?.location?.href;
837
+ if (!href) return null;
838
+ try {
839
+ return new URL(href).searchParams.get(name);
840
+ } catch {
841
+ return null;
842
+ }
843
+ }
844
+ function resolveAnonymousId(persistence) {
845
+ const urlAnonymousId = getUrlParam("aid");
846
+ if (isUuid(urlAnonymousId)) {
847
+ persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, urlAnonymousId);
848
+ return urlAnonymousId;
849
+ }
850
+ const storedAnonymousId = persistence.getItem(ANONYMOUS_ID_STORAGE_KEY);
851
+ if (isUuid(storedAnonymousId)) {
852
+ return storedAnonymousId;
853
+ }
854
+ const legacyAnonymousId = persistence.getItem(LEGACY_ANONYMOUS_ID_STORAGE_KEY);
855
+ if (isUuid(legacyAnonymousId)) {
856
+ persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, legacyAnonymousId);
857
+ return legacyAnonymousId;
858
+ }
859
+ const anonymousId = createUuid();
860
+ persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, anonymousId);
861
+ return anonymousId;
862
+ }
863
+ function resetAnonymousId(persistence) {
864
+ const anonymousId = createUuid();
865
+ persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, anonymousId);
866
+ return anonymousId;
867
+ }
868
+
869
+ // src/browser/core/page-context.ts
870
+ function redactSearch(search) {
871
+ const redacted = redactUrl(search);
872
+ return redacted.startsWith("/?") ? redacted.slice(1) : redacted;
873
+ }
874
+ function getBrowserPageContext() {
875
+ const browserWindow = getBrowserWindow();
876
+ const documentRef = browserWindow?.document ?? globalThis.document;
877
+ const location = browserWindow?.location;
878
+ const page2 = {};
879
+ if (location?.href) page2.url = location.href;
880
+ if (location?.pathname) page2.path = location.pathname;
881
+ if (location?.search) page2.search = redactSearch(location.search);
882
+ if (documentRef?.title) page2.title = documentRef.title;
883
+ if (documentRef?.referrer) page2.referrer = redactUrl(documentRef.referrer);
884
+ return page2;
885
+ }
886
+
887
+ // src/browser/core/persistence.ts
888
+ function createPersistenceHealth(requested) {
889
+ return {
890
+ requested,
891
+ cookieFallbackUsed: false,
892
+ memoryFallbackUsed: requested === "memory",
893
+ failures: []
894
+ };
895
+ }
896
+ function recordFailure(health, backend, operation, error) {
897
+ health.failures.push({
898
+ backend,
899
+ operation,
900
+ message: error instanceof Error ? error.message : String(error)
901
+ });
902
+ if (health.failures.length > 25) {
903
+ health.failures.shift();
904
+ }
905
+ }
906
+ function createMemoryPersistence(health = createPersistenceHealth("memory")) {
907
+ const memory = {};
908
+ return {
909
+ getItem(key) {
910
+ return Object.prototype.hasOwnProperty.call(memory, key) ? memory[key] ?? null : null;
911
+ },
912
+ setItem(key, value) {
913
+ memory[key] = value;
914
+ return true;
915
+ },
916
+ removeItem(key) {
917
+ delete memory[key];
918
+ },
919
+ getHealth() {
920
+ return {
921
+ ...health,
922
+ failures: health.failures.map((failure) => ({ ...failure }))
923
+ };
924
+ }
925
+ };
926
+ }
927
+ function getCookie2(name) {
928
+ const browserWindow = getBrowserWindow();
929
+ const cookie = browserWindow?.document?.cookie;
930
+ if (!cookie) return null;
931
+ const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
932
+ const match = new RegExp(`(?:^|; )${escapedName}=([^;]*)`).exec(cookie);
933
+ return match ? decodeURIComponent(match[1] ?? "") : null;
934
+ }
935
+ function setCookie(name, value) {
936
+ const browserWindow = getBrowserWindow();
937
+ if (!browserWindow?.document) return false;
938
+ try {
939
+ const expires = new Date(Date.now() + 365 * 24 * 60 * 60 * 1e3).toUTCString();
940
+ const secure = browserWindow.location?.protocol === "https:" ? "; Secure" : "";
941
+ browserWindow.document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/${secure}; SameSite=Lax`;
942
+ return true;
943
+ } catch {
944
+ return false;
945
+ }
946
+ }
947
+ function removeCookie(name) {
948
+ const browserWindow = getBrowserWindow();
949
+ if (!browserWindow?.document) return;
950
+ browserWindow.document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
951
+ }
952
+ function getLocalStorage() {
953
+ const browserWindow = getBrowserWindow();
954
+ try {
955
+ return browserWindow?.localStorage;
956
+ } catch {
957
+ return void 0;
958
+ }
959
+ }
960
+ function getSessionStorage() {
961
+ const browserWindow = getBrowserWindow();
962
+ try {
963
+ return browserWindow?.sessionStorage;
964
+ } catch {
965
+ return void 0;
966
+ }
967
+ }
968
+ function createBrowserPersistence(mode = "localStorage+cookie") {
969
+ const health = createPersistenceHealth(mode);
970
+ const memory = createMemoryPersistence(health);
971
+ if (mode === "none") {
972
+ return {
973
+ getItem() {
974
+ return null;
975
+ },
976
+ setItem() {
977
+ return false;
978
+ },
979
+ removeItem() {
980
+ },
981
+ getHealth() {
982
+ return {
983
+ ...health,
984
+ failures: health.failures.map((failure) => ({ ...failure }))
985
+ };
986
+ }
987
+ };
988
+ }
989
+ if (mode === "memory") return memory;
990
+ const primary = mode === "sessionStorage" ? getSessionStorage() : getLocalStorage();
991
+ const useCookie = mode === "cookie" || mode === "localStorage+cookie" || !primary;
992
+ return {
993
+ getItem(key) {
994
+ try {
995
+ const stored = primary?.getItem(key);
996
+ if (stored) return stored;
997
+ } catch (error) {
998
+ recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "get", error);
999
+ }
1000
+ if (useCookie) {
1001
+ const cookie = getCookie2(key);
1002
+ if (cookie) {
1003
+ health.cookieFallbackUsed = true;
1004
+ return cookie;
1005
+ }
1006
+ }
1007
+ const value = memory.getItem(key);
1008
+ if (value !== null) health.memoryFallbackUsed = true;
1009
+ return value;
1010
+ },
1011
+ setItem(key, value) {
1012
+ let wrote = false;
1013
+ try {
1014
+ primary?.setItem(key, value);
1015
+ wrote = primary !== void 0;
1016
+ } catch (error) {
1017
+ recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "set", error);
1018
+ wrote = false;
1019
+ }
1020
+ if (useCookie) {
1021
+ const cookieWrote = setCookie(key, value);
1022
+ if (cookieWrote) health.cookieFallbackUsed = true;
1023
+ wrote = cookieWrote || wrote;
1024
+ }
1025
+ if (!wrote) {
1026
+ health.memoryFallbackUsed = true;
1027
+ return memory.setItem(key, value);
1028
+ }
1029
+ if (mode === "localStorage+cookie") {
1030
+ memory.setItem(key, value);
1031
+ }
1032
+ return true;
1033
+ },
1034
+ removeItem(key) {
1035
+ try {
1036
+ primary?.removeItem(key);
1037
+ } catch (error) {
1038
+ recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "remove", error);
1039
+ }
1040
+ if (useCookie) removeCookie(key);
1041
+ memory.removeItem(key);
1042
+ },
1043
+ getHealth() {
1044
+ return {
1045
+ ...health,
1046
+ failures: health.failures.map((failure) => ({ ...failure }))
1047
+ };
1048
+ }
1049
+ };
1050
+ }
1051
+
1052
+ // src/browser/core/session.ts
1053
+ var SESSION_STORAGE_KEY = "sessionData";
1054
+ function getUrlParam2(name) {
1055
+ const href = getBrowserWindow()?.location?.href;
1056
+ if (!href) return null;
1057
+ try {
1058
+ return new URL(href).searchParams.get(name);
1059
+ } catch {
1060
+ return null;
1061
+ }
1062
+ }
1063
+ function readStoredSession(persistence) {
1064
+ const rawSession = persistence.getItem(SESSION_STORAGE_KEY);
1065
+ if (!rawSession) return void 0;
1066
+ try {
1067
+ const parsed = JSON.parse(rawSession);
1068
+ if (isUuid(parsed.sessionId) && typeof parsed.timestamp === "number") {
1069
+ return {
1070
+ sessionId: parsed.sessionId,
1071
+ timestamp: parsed.timestamp
1072
+ };
1073
+ }
1074
+ } catch {
1075
+ persistence.removeItem(SESSION_STORAGE_KEY);
1076
+ }
1077
+ return void 0;
1078
+ }
1079
+ function writeStoredSession(persistence, sessionId, timestamp) {
1080
+ persistence.setItem(SESSION_STORAGE_KEY, JSON.stringify({ sessionId, timestamp }));
1081
+ }
1082
+ function createSessionManager(persistence, idleTimeoutSeconds = 30 * 60) {
1083
+ function resolveSessionId() {
1084
+ const now = Date.now();
1085
+ const urlSessionId = getUrlParam2("sid") ?? getUrlParam2("session_id");
1086
+ if (isUuid(urlSessionId)) {
1087
+ writeStoredSession(persistence, urlSessionId, now);
1088
+ return urlSessionId;
1089
+ }
1090
+ const storedSession = readStoredSession(persistence);
1091
+ if (storedSession && now - storedSession.timestamp <= idleTimeoutSeconds * 1e3) {
1092
+ writeStoredSession(persistence, storedSession.sessionId, now);
1093
+ return storedSession.sessionId;
1094
+ }
1095
+ const sessionId = createUuid();
1096
+ writeStoredSession(persistence, sessionId, now);
1097
+ return sessionId;
1098
+ }
1099
+ return {
1100
+ getSessionId() {
1101
+ return resolveSessionId();
1102
+ },
1103
+ reset() {
1104
+ const sessionId = createUuid();
1105
+ writeStoredSession(persistence, sessionId, Date.now());
1106
+ return sessionId;
1107
+ }
1108
+ };
1109
+ }
1110
+
1111
+ // src/browser/core/DriveMetaDataSDK.ts
1112
+ function endpointFromConfig(config) {
1113
+ const host = config.apiHost ?? "https://sdk.drivemetadata.com/v2";
1114
+ return `${host.replace(/\/$/, "")}/data-collector`;
1115
+ }
1116
+ function requireConfigString(value, field) {
1117
+ if (typeof value !== "string" || value.trim() === "") {
1118
+ throw new Error(`DMD SDK config ${field} is required`);
1119
+ }
1120
+ return value;
1121
+ }
1122
+ var DriveMetaDataSDK = class {
1123
+ constructor(config) {
1124
+ this.initialized = true;
1125
+ this.queue = [];
1126
+ this.offline = false;
1127
+ this.droppedEvents = 0;
1128
+ requireConfigString(config.clientId, "clientId");
1129
+ requireConfigString(config.workspaceId, "workspaceId");
1130
+ requireConfigString(config.appId, "appId");
1131
+ requireConfigString(config.writeKey || config.token, "writeKey or token");
1132
+ this.config = config;
1133
+ this.endpoint = endpointFromConfig(config);
1134
+ this.persistence = createBrowserPersistence(config.persistence);
1135
+ this.session = createSessionManager(this.persistence, config.sessionIdleTimeoutSeconds);
1136
+ const deliveryConfig = {
1137
+ endpoint: this.endpoint,
1138
+ storage: this.persistence
1139
+ };
1140
+ if (config.delivery?.maxQueueSize !== void 0) deliveryConfig.maxQueueSize = config.delivery.maxQueueSize;
1141
+ if (config.delivery?.queueTtlMs !== void 0) deliveryConfig.queueTtlMs = config.delivery.queueTtlMs;
1142
+ if (config.delivery?.retryDelayMs !== void 0) deliveryConfig.retryDelayMs = config.delivery.retryDelayMs;
1143
+ if (config.delivery?.maxRetryDelayMs !== void 0) deliveryConfig.maxRetryDelayMs = config.delivery.maxRetryDelayMs;
1144
+ if (config.delivery?.maxPayloadBytes !== void 0) deliveryConfig.maxPayloadBytes = config.delivery.maxPayloadBytes;
1145
+ if (config.delivery?.payloadSizePolicy !== void 0) deliveryConfig.payloadSizePolicy = config.delivery.payloadSizePolicy;
1146
+ if (config.delivery?.payloadTruncateStringLength !== void 0) {
1147
+ deliveryConfig.payloadTruncateStringLength = config.delivery.payloadTruncateStringLength;
1148
+ }
1149
+ if (config.delivery?.useBeacon !== void 0) deliveryConfig.useBeacon = config.delivery.useBeacon;
1150
+ if (config.delivery?.batchSize !== void 0) deliveryConfig.batchSize = config.delivery.batchSize;
1151
+ if (config.onDrop !== void 0) deliveryConfig.onDrop = config.onDrop;
1152
+ if (config.onError !== void 0) deliveryConfig.onError = config.onError;
1153
+ this.delivery = createDeliveryManager(deliveryConfig);
1154
+ this.initialRetryDelayMs = config.delivery?.retryDelayMs ?? 1e3;
1155
+ this.retryDelayMs = this.initialRetryDelayMs;
1156
+ this.maxRetryDelayMs = config.delivery?.maxRetryDelayMs ?? 3e4;
1157
+ this.writeKey = config.writeKey || config.token || "";
1158
+ this.identity = { anonymousId: resolveAnonymousId(this.persistence) };
1159
+ captureAttributionFromUrl(this.persistence);
1160
+ this.consentState = normalizeConsent(config.gdprConsent ?? config.consent);
1161
+ this.gdprConsent = this.consentState.analytics;
1162
+ this.installAutocapture();
1163
+ if (!config.delivery?.disableLifecycleFlush) {
1164
+ this.installLifecycleFlush();
1165
+ }
1166
+ }
1167
+ trackEvent(event, properties = {}, options = {}) {
1168
+ this.sendPreparedEvent("track", event, properties, options);
1169
+ }
1170
+ page(name, properties = {}, options = {}) {
1171
+ this.sendPreparedEvent("page", "page_view", { name, ...properties }, options);
1172
+ }
1173
+ trackPageview() {
1174
+ this.page();
1175
+ }
1176
+ identify(userId, traits = {}, options = {}) {
1177
+ this.identity = { ...this.identity, userId, traits };
1178
+ this.sendPreparedEvent("identify", "identify", { userId, traits }, options);
1179
+ }
1180
+ identifyUser(userId, traits = {}) {
1181
+ this.identify(userId, traits);
1182
+ }
1183
+ group(groupId, traits = {}, options = {}) {
1184
+ this.identity = { ...this.identity, groupId };
1185
+ this.sendPreparedEvent("group", "group", { groupId, traits }, options);
1186
+ }
1187
+ alias(previousId, userId, options = {}) {
1188
+ this.sendPreparedEvent("alias", "alias", { previousId, userId }, options);
1189
+ }
1190
+ async flush() {
1191
+ await this.delivery.flushQueue();
1192
+ const diagnostics = this.delivery.getDiagnostics();
1193
+ this.offline = diagnostics.queued > 0;
1194
+ this.lastError = diagnostics.lastError;
1195
+ if (diagnostics.queued > 0) {
1196
+ this.scheduleRetryFlush();
1197
+ } else {
1198
+ this.retryDelayMs = this.initialRetryDelayMs;
1199
+ }
1200
+ }
1201
+ reset() {
1202
+ this.identity = { anonymousId: resetAnonymousId(this.persistence) };
1203
+ this.session.reset();
1204
+ this.queue = [];
1205
+ this.offline = false;
1206
+ if (this.retryTimer !== void 0) {
1207
+ clearTimeout(this.retryTimer);
1208
+ this.retryTimer = void 0;
1209
+ }
1210
+ this.lifecycleCleanup?.();
1211
+ this.lifecycleCleanup = void 0;
1212
+ this.autocaptureCleanup?.();
1213
+ this.autocaptureCleanup = void 0;
1214
+ this.delivery.clearQueue("manual_clear");
1215
+ }
1216
+ disposeForTests() {
1217
+ if (this.retryTimer !== void 0) {
1218
+ clearTimeout(this.retryTimer);
1219
+ this.retryTimer = void 0;
1220
+ }
1221
+ this.lifecycleCleanup?.();
1222
+ this.lifecycleCleanup = void 0;
1223
+ this.autocaptureCleanup?.();
1224
+ this.autocaptureCleanup = void 0;
1225
+ }
1226
+ setConsent(consent) {
1227
+ this.consentState = typeof consent === "object" ? mergeConsent(this.consentState, consent) : normalizeConsent(consent);
1228
+ this.gdprConsent = this.consentState.analytics;
1229
+ if (!canCollectPurpose(this.consentState, "analytics")) {
1230
+ this.queue = [];
1231
+ this.delivery.clearQueue("consent_revoked");
1232
+ }
1233
+ }
1234
+ getHealth() {
1235
+ const deliveryDiagnostics = this.delivery.getDiagnostics();
1236
+ const health = {
1237
+ initialized: this.initialized,
1238
+ consent: this.gdprConsent,
1239
+ consentPurposes: this.consentState,
1240
+ persistence: this.persistence.getHealth(),
1241
+ queueSize: deliveryDiagnostics.queued,
1242
+ offline: this.offline || deliveryDiagnostics.queued > 0,
1243
+ droppedEvents: this.droppedEvents + deliveryDiagnostics.dropped.length,
1244
+ delivery: deliveryDiagnostics
1245
+ };
1246
+ const lastError2 = this.lastError ?? deliveryDiagnostics.lastError;
1247
+ if (lastError2 !== void 0) health.lastError = lastError2;
1248
+ if (this.lastDroppedEvent !== void 0) health.lastDroppedEvent = this.lastDroppedEvent;
1249
+ return health;
1250
+ }
1251
+ sendEvent(payload) {
1252
+ void this.delivery.send(payload).then(() => {
1253
+ const diagnostics = this.delivery.getDiagnostics();
1254
+ this.offline = diagnostics.queued > 0;
1255
+ if (diagnostics.lastError !== void 0) {
1256
+ this.lastError = diagnostics.lastError;
1257
+ }
1258
+ if (diagnostics.queued > 0) {
1259
+ this.scheduleRetryFlush();
1260
+ }
1261
+ });
1262
+ }
1263
+ installLifecycleFlush() {
1264
+ const browserWindow = getBrowserWindow();
1265
+ if (!browserWindow) return;
1266
+ if (typeof browserWindow.addEventListener !== "function") return;
1267
+ if (typeof browserWindow.removeEventListener !== "function") return;
1268
+ const flushOnLifecycle = () => {
1269
+ void this.flush();
1270
+ };
1271
+ const flushOnVisibility = () => {
1272
+ if (browserWindow.document?.visibilityState === "hidden") {
1273
+ void this.flush();
1274
+ }
1275
+ };
1276
+ browserWindow.addEventListener("online", flushOnLifecycle);
1277
+ browserWindow.addEventListener("pagehide", flushOnLifecycle);
1278
+ if (typeof browserWindow.document?.addEventListener === "function") {
1279
+ browserWindow.document.addEventListener("visibilitychange", flushOnVisibility);
1280
+ }
1281
+ this.lifecycleCleanup = () => {
1282
+ browserWindow.removeEventListener("online", flushOnLifecycle);
1283
+ browserWindow.removeEventListener("pagehide", flushOnLifecycle);
1284
+ if (typeof browserWindow.document?.removeEventListener === "function") {
1285
+ browserWindow.document.removeEventListener("visibilitychange", flushOnVisibility);
1286
+ }
1287
+ };
1288
+ }
1289
+ installAutocapture() {
1290
+ const browserWindow = getBrowserWindow();
1291
+ if (!browserWindow) return;
1292
+ if (this.config.autocapture === false) return;
1293
+ const capturePageview = this.config.capturePageview !== false;
1294
+ const capturePageleave = this.config.capturePageleave !== false;
1295
+ const controller = installAutocapture({
1296
+ browserWindow,
1297
+ capturePageview,
1298
+ capturePageleave,
1299
+ onPageView: () => {
1300
+ this.page();
1301
+ },
1302
+ onPageLeave: () => {
1303
+ this.trackEvent("page_leave", { url: browserWindow.location.href });
1304
+ },
1305
+ onRouteChange: () => {
1306
+ captureAttributionFromUrl(this.persistence);
1307
+ }
1308
+ });
1309
+ this.autocaptureCleanup = controller.cleanup;
1310
+ }
1311
+ scheduleRetryFlush() {
1312
+ if (this.retryTimer !== void 0) return;
1313
+ const delay = Math.min(this.retryDelayMs, this.maxRetryDelayMs);
1314
+ this.retryTimer = setTimeout(() => {
1315
+ this.retryTimer = void 0;
1316
+ void this.flush().then(() => {
1317
+ if (this.delivery.getDiagnostics().queued > 0) {
1318
+ this.retryDelayMs = Math.min(this.retryDelayMs * 2, this.maxRetryDelayMs);
1319
+ this.scheduleRetryFlush();
1320
+ }
1321
+ });
1322
+ }, delay);
1323
+ }
1324
+ sendPreparedEvent(type, event, properties, options) {
1325
+ void this.prepareAndSendEvent(type, event, properties, options);
1326
+ }
1327
+ async prepareAndSendEvent(type, event, properties, options) {
1328
+ if (!canCollectPurpose(this.consentState, "analytics")) {
1329
+ this.recordDrop(type, "consent_denied", event);
1330
+ return;
1331
+ }
1332
+ let prepared = {
1333
+ type,
1334
+ event,
1335
+ properties: sanitizeProperties(properties),
1336
+ messageId: ensureUuid(options.messageId),
1337
+ timestamp: options.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
1338
+ context: options.context ?? {},
1339
+ anonymousId: this.identity.anonymousId,
1340
+ clientId: this.config.clientId,
1341
+ workspaceId: this.config.workspaceId,
1342
+ appId: this.config.appId,
1343
+ writeKey: this.writeKey,
1344
+ consent: this.consentState,
1345
+ sessionId: this.session.getSessionId()
1346
+ };
1347
+ if (this.identity.userId !== void 0) prepared.userId = this.identity.userId;
1348
+ if (this.identity.groupId !== void 0) prepared.groupId = this.identity.groupId;
1349
+ if (this.config.beforeSend !== void 0) {
1350
+ const beforeSendResult = await this.config.beforeSend(prepared);
1351
+ if (beforeSendResult === null) {
1352
+ this.recordDrop(type, "before_send_dropped", event);
1353
+ return;
1354
+ }
1355
+ prepared = {
1356
+ ...prepared,
1357
+ ...beforeSendResult,
1358
+ properties: sanitizeProperties(beforeSendResult.properties ?? prepared.properties)
1359
+ };
1360
+ }
1361
+ const validation = validateEventEnvelope(prepared, { mode: this.config.schemaValidation ?? "warn" });
1362
+ if (!validation.ok && this.config.schemaValidation === "strict") {
1363
+ this.recordDrop(type, "invalid_payload", event);
1364
+ return;
1365
+ }
1366
+ if (!validation.ok) {
1367
+ this.lastError = `DMD SDK schema warning: ${validation.errors.join(", ")}`;
1368
+ }
1369
+ const collectorPayload = this.toCollectorPayload(prepared);
1370
+ const backendValidation = validateBackendCollectorPayload(collectorPayload);
1371
+ if (!backendValidation.ok && this.config.schemaValidation === "strict") {
1372
+ this.lastError = `DMD SDK backend schema warning: ${backendValidation.errors.join(", ")}`;
1373
+ this.recordDrop(type, "invalid_payload", event);
1374
+ return;
1375
+ }
1376
+ if (!backendValidation.ok) {
1377
+ this.lastError = `DMD SDK backend schema warning: ${backendValidation.errors.join(", ")}`;
1378
+ }
1379
+ this.sendEvent(collectorPayload);
1380
+ }
1381
+ toCollectorPayload(prepared) {
1382
+ const browserWindow = getBrowserWindow();
1383
+ const pageContext = getBrowserPageContext();
1384
+ return createBackendCollectorPayload({
1385
+ requestId: prepared.messageId,
1386
+ timestamp: prepared.timestamp,
1387
+ eventType: prepared.event,
1388
+ clientId: prepared.clientId,
1389
+ workspaceId: prepared.workspaceId,
1390
+ token: prepared.writeKey,
1391
+ anonymousId: prepared.anonymousId,
1392
+ sessionId: prepared.sessionId,
1393
+ ua: browserWindow?.navigator?.userAgent,
1394
+ appId: prepared.appId,
1395
+ pageUrl: pageContext.url ?? browserWindow?.location?.href,
1396
+ eventData: mergeStoredAttribution({
1397
+ ...prepared.properties,
1398
+ page: {
1399
+ ...pageContext,
1400
+ ...prepared.properties.page && typeof prepared.properties.page === "object" ? prepared.properties.page : {}
1401
+ },
1402
+ anonymousId: prepared.anonymousId,
1403
+ sessionId: prepared.sessionId,
1404
+ timestamp: prepared.timestamp,
1405
+ requestSentAt: prepared.timestamp,
1406
+ requestReceivedAt: prepared.timestamp
1407
+ }, this.persistence)
1408
+ });
1409
+ }
1410
+ recordDrop(type, reason, event) {
1411
+ this.droppedEvents += 1;
1412
+ this.lastDroppedEvent = {
1413
+ type,
1414
+ reason,
1415
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1416
+ };
1417
+ if (event !== void 0) this.lastDroppedEvent.event = event;
1418
+ this.lastError = `DMD SDK ${type} dropped because ${reason}`;
1419
+ }
1420
+ };
1421
+
1422
+ // src/browser/client.ts
1423
+ var singleton;
1424
+ var publicSingleton;
1425
+ var droppedEvents = 0;
1426
+ var lastError;
1427
+ var lastDroppedEvent;
1428
+ function createPublicClient(instance) {
1429
+ return {
1430
+ track(event, properties, options) {
1431
+ instance.trackEvent(event, properties, options);
1432
+ },
1433
+ identify(userId, traits, options) {
1434
+ instance.identify(userId, traits, options);
1435
+ },
1436
+ page(name, properties, options) {
1437
+ instance.page(name, properties, options);
1438
+ },
1439
+ group(groupId, traits, options) {
1440
+ instance.group(groupId, traits, options);
1441
+ },
1442
+ alias(previousId, userId, options) {
1443
+ instance.alias(previousId, userId, options);
1444
+ },
1445
+ async flush() {
1446
+ await instance.flush();
1447
+ },
1448
+ reset() {
1449
+ instance.reset();
1450
+ singleton = void 0;
1451
+ publicSingleton = void 0;
1452
+ },
1453
+ setConsent(consent) {
1454
+ instance.setConsent(consent);
1455
+ },
1456
+ getHealth() {
1457
+ return instance.getHealth();
1458
+ }
1459
+ };
1460
+ }
1461
+ function setSingleton(instance) {
1462
+ singleton = instance;
1463
+ publicSingleton = createPublicClient(instance);
1464
+ return publicSingleton;
1465
+ }
1466
+ function recordDroppedEvent(type, event) {
1467
+ droppedEvents += 1;
1468
+ lastDroppedEvent = {
1469
+ type,
1470
+ reason: "not_initialized",
1471
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1472
+ };
1473
+ if (event !== void 0) {
1474
+ lastDroppedEvent.event = event;
1475
+ }
1476
+ lastError = `DMD SDK ${type} called before initialization`;
1477
+ }
1478
+ function initDmdSDK(config) {
1479
+ if (publicSingleton) {
1480
+ return publicSingleton;
1481
+ }
1482
+ try {
1483
+ const instance = new DriveMetaDataSDK(normalizeBrowserConfig(config));
1484
+ return setSingleton(instance);
1485
+ } catch (error) {
1486
+ lastError = error instanceof Error ? error.message : String(error);
1487
+ throw error;
1488
+ }
1489
+ }
1490
+ function getDmdSDK() {
1491
+ return publicSingleton;
1492
+ }
1493
+ function track(event, properties, options) {
1494
+ const sdk = getDmdSDK();
1495
+ if (!sdk) {
1496
+ recordDroppedEvent("track", event);
1497
+ return;
1498
+ }
1499
+ sdk.track(event, properties, options);
1500
+ }
1501
+ function identify(userId, traits, options) {
1502
+ const sdk = getDmdSDK();
1503
+ if (!sdk) {
1504
+ recordDroppedEvent("identify");
1505
+ return;
1506
+ }
1507
+ sdk.identify(userId, traits, options);
1508
+ }
1509
+ function page(name, properties, options) {
1510
+ const sdk = getDmdSDK();
1511
+ if (!sdk) {
1512
+ recordDroppedEvent("page");
1513
+ return;
1514
+ }
1515
+ sdk.page(name, properties, options);
1516
+ }
1517
+ function setConsent(consent) {
1518
+ getDmdSDK()?.setConsent(consent);
1519
+ }
1520
+ function getDmdHealth() {
1521
+ if (singleton) {
1522
+ return singleton.getHealth();
1523
+ }
1524
+ const health = {
1525
+ initialized: false,
1526
+ consent: "pending",
1527
+ queueSize: 0,
1528
+ offline: false,
1529
+ droppedEvents
1530
+ };
1531
+ if (lastError !== void 0) {
1532
+ health.lastError = lastError;
1533
+ }
1534
+ if (lastDroppedEvent !== void 0) {
1535
+ health.lastDroppedEvent = lastDroppedEvent;
1536
+ }
1537
+ return health;
1538
+ }
1539
+
1540
+ // src/react/DmdProvider.tsx
1541
+ import { jsx } from "react/jsx-runtime";
1542
+ var DmdContext = React.createContext(void 0);
1543
+ function DmdProvider({
1544
+ config,
1545
+ children,
1546
+ enabled = true,
1547
+ onReady,
1548
+ onError
1549
+ }) {
1550
+ const [client, setClient] = React.useState(() => getDmdSDK());
1551
+ React.useEffect(() => {
1552
+ if (!enabled) return;
1553
+ try {
1554
+ const initializedClient = initDmdSDK(config);
1555
+ const configuredConsent = config.gdprConsent ?? config.consent;
1556
+ if (configuredConsent !== void 0) {
1557
+ initializedClient.setConsent(configuredConsent);
1558
+ }
1559
+ setClient(initializedClient);
1560
+ onReady?.(initializedClient);
1561
+ } catch (error) {
1562
+ const normalizedError = error instanceof Error ? error : new Error(String(error));
1563
+ onError?.(normalizedError);
1564
+ }
1565
+ }, [
1566
+ enabled,
1567
+ config.clientId,
1568
+ config.workspaceId,
1569
+ config.appId,
1570
+ config.writeKey,
1571
+ config.token,
1572
+ config.apiHost,
1573
+ config.consent,
1574
+ config.gdprConsent,
1575
+ onReady,
1576
+ onError
1577
+ ]);
1578
+ return /* @__PURE__ */ jsx(DmdContext.Provider, { value: client, children });
1579
+ }
1580
+
1581
+ // src/next/useDmdAppRouterPageTracking.ts
1582
+ import React2 from "react";
1583
+ function useDmdAppRouterPageTracking(pathname, search, options = {}) {
1584
+ const lastRouteKey = React2.useRef(void 0);
1585
+ const routeKey = pathname ? `${pathname}?${search ?? ""}` : "";
1586
+ React2.useEffect(() => {
1587
+ if (!pathname || !routeKey || lastRouteKey.current === routeKey) return;
1588
+ lastRouteKey.current = routeKey;
1589
+ page(options.name, {
1590
+ ...options.properties,
1591
+ pathname,
1592
+ search: search ?? ""
1593
+ });
1594
+ }, [options.name, options.properties, pathname, routeKey, search]);
1595
+ }
1596
+
1597
+ // src/next/useDmdPagesRouterPageTracking.ts
1598
+ import React3 from "react";
1599
+ function useDmdPagesRouterPageTracking(router, options = {}) {
1600
+ const lastRouteKey = React3.useRef(void 0);
1601
+ const routeKey = router?.asPath ?? router?.pathname ?? "";
1602
+ React3.useEffect(() => {
1603
+ if (!routeKey || lastRouteKey.current === routeKey) return;
1604
+ lastRouteKey.current = routeKey;
1605
+ page(options.name, {
1606
+ ...options.properties,
1607
+ route: routeKey
1608
+ });
1609
+ }, [options.name, options.properties, routeKey]);
1610
+ }
1611
+
1612
+ // src/react/hooks.ts
1613
+ import React4 from "react";
1614
+ function useDmdSDK() {
1615
+ return React4.useContext(DmdContext);
1616
+ }
1617
+ function useTrackEvent() {
1618
+ return React4.useCallback((event, properties, options) => {
1619
+ track(event, properties, options);
1620
+ }, []);
1621
+ }
1622
+ function useIdentify() {
1623
+ return React4.useCallback((userId, traits) => {
1624
+ identify(userId, traits);
1625
+ }, []);
1626
+ }
1627
+ function useDmdConsent() {
1628
+ return React4.useCallback((consent) => {
1629
+ setConsent(consent);
1630
+ }, []);
1631
+ }
1632
+ function useDmdHealth() {
1633
+ const client = useDmdSDK();
1634
+ const [health, setHealth] = React4.useState(() => getDmdHealth());
1635
+ React4.useEffect(() => {
1636
+ setHealth(getDmdHealth());
1637
+ }, [client]);
1638
+ return health;
1639
+ }
1640
+ export {
1641
+ DmdProvider,
1642
+ useDmdAppRouterPageTracking,
1643
+ useDmdConsent,
1644
+ useDmdHealth,
1645
+ useDmdPagesRouterPageTracking,
1646
+ useDmdSDK,
1647
+ useIdentify,
1648
+ useTrackEvent
1649
+ };
1650
+ //# sourceMappingURL=index.js.map