@drivemetadata-ai/sdk 0.1.1-beta.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +107 -0
  4. package/dist/angular/index.cjs +954 -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 +928 -0
  9. package/dist/angular/index.js.map +1 -0
  10. package/dist/browser/index.cjs +914 -0
  11. package/dist/browser/index.cjs.map +1 -0
  12. package/dist/browser/index.d.cts +84 -0
  13. package/dist/browser/index.d.ts +84 -0
  14. package/dist/browser/index.js +874 -0
  15. package/dist/browser/index.js.map +1 -0
  16. package/dist/next/index.cjs +971 -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 +928 -0
  21. package/dist/next/index.js.map +1 -0
  22. package/dist/node/index.cjs +239 -0
  23. package/dist/node/index.cjs.map +1 -0
  24. package/dist/node/index.d.cts +85 -0
  25. package/dist/node/index.d.ts +85 -0
  26. package/dist/node/index.js +209 -0
  27. package/dist/node/index.js.map +1 -0
  28. package/dist/react/index.cjs +999 -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 +952 -0
  33. package/dist/react/index.js.map +1 -0
  34. package/dist/types-BwtS0ZDu.d.cts +106 -0
  35. package/dist/types-BwtS0ZDu.d.ts +106 -0
  36. package/docs/angular-integration.md +106 -0
  37. package/docs/index.md +19 -0
  38. package/docs/migration-cdn-to-npm.md +99 -0
  39. package/docs/node-server-integration.md +138 -0
  40. package/docs/npm-browser-sdk.md +143 -0
  41. package/docs/react-next-integration.md +168 -0
  42. package/docs/security-privacy.md +128 -0
  43. package/package.json +100 -0
@@ -0,0 +1,971 @@
1
+ "use strict";
2
+ "use client";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/next/index.ts
32
+ var next_exports = {};
33
+ __export(next_exports, {
34
+ DmdProvider: () => DmdProvider,
35
+ useDmdAppRouterPageTracking: () => useDmdAppRouterPageTracking,
36
+ useDmdConsent: () => useDmdConsent,
37
+ useDmdHealth: () => useDmdHealth,
38
+ useDmdPagesRouterPageTracking: () => useDmdPagesRouterPageTracking,
39
+ useDmdSDK: () => useDmdSDK,
40
+ useIdentify: () => useIdentify,
41
+ useTrackEvent: () => useTrackEvent
42
+ });
43
+ module.exports = __toCommonJS(next_exports);
44
+
45
+ // src/react/DmdProvider.tsx
46
+ var import_react = __toESM(require("react"), 1);
47
+
48
+ // src/core/config.ts
49
+ function requireString(value, field) {
50
+ if (typeof value !== "string" || value.trim() === "") {
51
+ throw new Error(`DMD SDK config ${field} is required`);
52
+ }
53
+ return value;
54
+ }
55
+ function normalizeBrowserConfig(config) {
56
+ const writeKey = config.writeKey || config.token;
57
+ const legacyConfig = {
58
+ client_id: requireString(config.clientId, "clientId"),
59
+ workspace_id: requireString(config.workspaceId, "workspaceId"),
60
+ app_id: requireString(config.appId, "appId"),
61
+ token: requireString(writeKey, "writeKey")
62
+ };
63
+ if (config.apiHost !== void 0) legacyConfig.api_host = config.apiHost;
64
+ if (config.uiHost !== void 0) legacyConfig.ui_host = config.uiHost;
65
+ if (config.deeplink !== void 0) legacyConfig.deeplink = config.deeplink;
66
+ if (config.debug !== void 0) legacyConfig.debug = config.debug;
67
+ if (config.consent !== void 0) legacyConfig.consent = config.consent;
68
+ if (config.gdprConsent !== void 0) legacyConfig.gdprConsent = config.gdprConsent;
69
+ if (config.autocapture !== void 0) legacyConfig.autocapture = config.autocapture;
70
+ if (config.capturePageview !== void 0) legacyConfig.capture_pageview = config.capturePageview;
71
+ if (config.capturePageleave !== void 0) legacyConfig.capture_pageleave = config.capturePageleave;
72
+ if (config.captureDeadClicks !== void 0) legacyConfig.capture_dead_clicks = config.captureDeadClicks;
73
+ if (config.crossSubdomainCookie !== void 0) legacyConfig.cross_subdomain_cookie = config.crossSubdomainCookie;
74
+ if (config.disablePersistence !== void 0) legacyConfig.disable_persistence = config.disablePersistence;
75
+ if (config.disableSurveys !== void 0) legacyConfig.disable_surveys = config.disableSurveys;
76
+ if (config.disableSessionRecording !== void 0) legacyConfig.disable_session_recording = config.disableSessionRecording;
77
+ if (config.enableHeatmaps !== void 0) legacyConfig.enable_heatmaps = config.enableHeatmaps;
78
+ if (config.maskAllText !== void 0) legacyConfig.mask_all_text = config.maskAllText;
79
+ if (config.maskAllElementAttributes !== void 0) {
80
+ legacyConfig.mask_all_element_attributes = config.maskAllElementAttributes;
81
+ }
82
+ if (config.persistence !== void 0) legacyConfig.persistence = config.persistence;
83
+ if (config.propertyDenylist !== void 0) legacyConfig.property_denylist = config.propertyDenylist;
84
+ if (config.sessionIdleTimeoutSeconds !== void 0) {
85
+ legacyConfig.session_idle_timeout_seconds = config.sessionIdleTimeoutSeconds;
86
+ }
87
+ if (config.beforeSend !== void 0) legacyConfig.before_send = config.beforeSend;
88
+ return legacyConfig;
89
+ }
90
+
91
+ // src/core/environment.ts
92
+ function isBrowserRuntime() {
93
+ return typeof window !== "undefined" && typeof document !== "undefined";
94
+ }
95
+ function getBrowserWindow() {
96
+ return typeof window === "undefined" ? void 0 : window;
97
+ }
98
+
99
+ // src/browser/core/consent.ts
100
+ var purposes = [
101
+ "analytics",
102
+ "advertising",
103
+ "personalization",
104
+ "functional",
105
+ "saleOfData"
106
+ ];
107
+ function normalizeConsentValue(value) {
108
+ if (value === true) return "granted";
109
+ if (value === false) return "denied";
110
+ if (value === "granted" || value === "denied" || value === "pending") return value;
111
+ return "pending";
112
+ }
113
+ function normalizeConsent(input) {
114
+ const defaultValue = normalizeConsentValue(input ?? "pending");
115
+ const resolved = Object.fromEntries(
116
+ purposes.map((purpose) => [purpose, defaultValue])
117
+ );
118
+ if (input && typeof input === "object") {
119
+ for (const purpose of purposes) {
120
+ const value = input[purpose];
121
+ if (value !== void 0) {
122
+ resolved[purpose] = normalizeConsentValue(value);
123
+ }
124
+ }
125
+ }
126
+ return resolved;
127
+ }
128
+ function mergeConsent(current, update) {
129
+ const next = { ...current };
130
+ for (const [purpose, value] of Object.entries(update)) {
131
+ next[purpose] = normalizeConsentValue(value);
132
+ }
133
+ return next;
134
+ }
135
+ function canCollectPurpose(consent, purpose) {
136
+ return consent[purpose] === "granted";
137
+ }
138
+
139
+ // src/browser/core/delivery.ts
140
+ function createId(prefix) {
141
+ return `${prefix}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
142
+ }
143
+ function stableStringify(value) {
144
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
145
+ return JSON.stringify(value);
146
+ }
147
+ return JSON.stringify(
148
+ Object.fromEntries(
149
+ Object.entries(value).sort(([left], [right]) => left.localeCompare(right))
150
+ )
151
+ );
152
+ }
153
+ function createIdempotencyKey(payload, messageId) {
154
+ const event = String(payload.event ?? payload.type ?? "event");
155
+ const properties = payload.properties;
156
+ const orderId = properties?.orderId ?? properties?.order_id ?? properties?.transaction_id;
157
+ if (orderId !== void 0) return `${event}:${String(orderId)}`;
158
+ return `${event}:${stableStringify(properties ?? {}) || messageId}`;
159
+ }
160
+ function createDeliveryManager(config) {
161
+ const queue = [];
162
+ const diagnostics = { queued: 0, inFlight: 0, dropped: [] };
163
+ const maxQueueSize = config.maxQueueSize ?? 100;
164
+ const queueKey = config.queueKey ?? "dmd_delivery_queue";
165
+ const lockKey = config.lockKey ?? "dmd_delivery_flush_lock";
166
+ const lockTtlMs = config.lockTtlMs ?? 5e3;
167
+ const tabId = config.tabId ?? createId("tab");
168
+ const batchSize = config.batchSize ?? 25;
169
+ const maxPayloadBytes = config.maxPayloadBytes ?? 64e3;
170
+ function recordDrop(event) {
171
+ diagnostics.dropped.push(event);
172
+ config.onDrop?.(event);
173
+ }
174
+ function recordError(error) {
175
+ diagnostics.lastError = error.message;
176
+ config.onError?.(error);
177
+ }
178
+ function payloadByteLength(payload) {
179
+ const serialized = JSON.stringify(payload);
180
+ if (typeof TextEncoder !== "undefined") {
181
+ return new TextEncoder().encode(serialized).length;
182
+ }
183
+ return serialized.length;
184
+ }
185
+ function recordStorageUnavailable() {
186
+ recordDrop({
187
+ reason: "storage_unavailable",
188
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
189
+ });
190
+ }
191
+ function safeGetItem(key) {
192
+ try {
193
+ return config.storage?.getItem(key) ?? null;
194
+ } catch {
195
+ recordStorageUnavailable();
196
+ return null;
197
+ }
198
+ }
199
+ function safeSetItem(key, value) {
200
+ try {
201
+ config.storage?.setItem(key, value);
202
+ return true;
203
+ } catch {
204
+ recordStorageUnavailable();
205
+ return false;
206
+ }
207
+ }
208
+ function safeRemoveItem(key) {
209
+ try {
210
+ config.storage?.removeItem(key);
211
+ return true;
212
+ } catch {
213
+ recordStorageUnavailable();
214
+ return false;
215
+ }
216
+ }
217
+ function persistQueue() {
218
+ if (!config.storage) return;
219
+ if (queue.length === 0) {
220
+ safeRemoveItem(queueKey);
221
+ return;
222
+ }
223
+ safeSetItem(queueKey, JSON.stringify(queue));
224
+ }
225
+ function loadQueue() {
226
+ if (!config.storage) return;
227
+ const rawQueue = safeGetItem(queueKey);
228
+ if (!rawQueue) return;
229
+ try {
230
+ const records = JSON.parse(rawQueue);
231
+ queue.splice(0, queue.length, ...records.filter((record) => record && typeof record.messageId === "string"));
232
+ diagnostics.queued = queue.length;
233
+ } catch {
234
+ safeRemoveItem(queueKey);
235
+ recordDrop({
236
+ reason: "queue_corrupt",
237
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
238
+ });
239
+ }
240
+ }
241
+ function enqueue(record) {
242
+ queue.push(record);
243
+ while (queue.length > maxQueueSize) {
244
+ const dropped = queue.shift();
245
+ const diagnostic = {
246
+ reason: "queue_limit_exceeded",
247
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
248
+ };
249
+ if (dropped?.messageId !== void 0) diagnostic.messageId = dropped.messageId;
250
+ recordDrop(diagnostic);
251
+ }
252
+ diagnostics.queued = queue.length;
253
+ persistQueue();
254
+ }
255
+ function withEnvelope(payload) {
256
+ const messageId = String(payload.messageId ?? createId("msg"));
257
+ return {
258
+ ...payload,
259
+ messageId,
260
+ idempotencyKey: String(payload.idempotencyKey ?? createIdempotencyKey(payload, messageId))
261
+ };
262
+ }
263
+ async function deliver(body) {
264
+ const fetchImpl = config.fetch ?? globalThis.fetch;
265
+ if (typeof fetchImpl !== "function") {
266
+ throw new Error("fetch_unavailable");
267
+ }
268
+ const response = await fetchImpl(config.endpoint, {
269
+ method: "POST",
270
+ headers: { "Content-Type": "application/json" },
271
+ body: JSON.stringify(body),
272
+ keepalive: true
273
+ });
274
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
275
+ }
276
+ loadQueue();
277
+ return {
278
+ async send(payload) {
279
+ const body = withEnvelope(payload);
280
+ if (payloadByteLength(body) > maxPayloadBytes) {
281
+ recordDrop({
282
+ messageId: String(body.messageId),
283
+ reason: "payload_too_large",
284
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
285
+ });
286
+ return;
287
+ }
288
+ diagnostics.inFlight += 1;
289
+ try {
290
+ await deliver(body);
291
+ } catch (error) {
292
+ const deliveryError = error instanceof Error ? error : new Error(String(error));
293
+ recordError(deliveryError);
294
+ enqueue({
295
+ messageId: String(body.messageId),
296
+ savedAt: Date.now(),
297
+ attempts: 1,
298
+ lastError: deliveryError.message,
299
+ payload: body
300
+ });
301
+ } finally {
302
+ diagnostics.inFlight -= 1;
303
+ diagnostics.queued = queue.length;
304
+ }
305
+ },
306
+ async flushQueue() {
307
+ this.flushExpired();
308
+ if (queue.length === 0 || !this.acquireFlushLease()) return;
309
+ diagnostics.inFlight += 1;
310
+ try {
311
+ let sentInBatch = 0;
312
+ for (let index = 0; index < queue.length && sentInBatch < batchSize; ) {
313
+ const record = queue[index];
314
+ if (!record) {
315
+ index += 1;
316
+ continue;
317
+ }
318
+ try {
319
+ await deliver(record.payload);
320
+ queue.splice(index, 1);
321
+ sentInBatch += 1;
322
+ persistQueue();
323
+ } catch (error) {
324
+ const deliveryError = error instanceof Error ? error : new Error(String(error));
325
+ record.attempts += 1;
326
+ record.lastError = deliveryError.message;
327
+ recordError(deliveryError);
328
+ index += 1;
329
+ persistQueue();
330
+ break;
331
+ }
332
+ }
333
+ } finally {
334
+ diagnostics.inFlight -= 1;
335
+ diagnostics.queued = queue.length;
336
+ this.releaseFlushLease();
337
+ }
338
+ },
339
+ clearQueue(reason) {
340
+ for (const record of queue) {
341
+ recordDrop({
342
+ messageId: record.messageId,
343
+ reason,
344
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
345
+ });
346
+ }
347
+ queue.splice(0, queue.length);
348
+ diagnostics.queued = 0;
349
+ persistQueue();
350
+ },
351
+ enqueueForTests(record) {
352
+ enqueue(record);
353
+ },
354
+ flushExpired() {
355
+ const ttl = config.queueTtlMs ?? 864e5;
356
+ const now = Date.now();
357
+ for (let index = queue.length - 1; index >= 0; index -= 1) {
358
+ const record = queue[index];
359
+ if (record && now - record.savedAt > ttl) {
360
+ recordDrop({
361
+ messageId: record.messageId,
362
+ reason: "queue_ttl_expired",
363
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
364
+ });
365
+ queue.splice(index, 1);
366
+ }
367
+ }
368
+ diagnostics.queued = queue.length;
369
+ persistQueue();
370
+ },
371
+ acquireFlushLease() {
372
+ if (!config.storage) return true;
373
+ const now = Date.now();
374
+ const rawLock = safeGetItem(lockKey);
375
+ let existingLock;
376
+ if (rawLock) {
377
+ try {
378
+ existingLock = JSON.parse(rawLock);
379
+ } catch {
380
+ existingLock = void 0;
381
+ }
382
+ }
383
+ const lockExpired = !existingLock?.expiresAt || existingLock.expiresAt <= now;
384
+ const lockOwnedByThisTab = existingLock?.owner === tabId;
385
+ if (!existingLock || lockExpired || lockOwnedByThisTab) {
386
+ if (!safeSetItem(lockKey, JSON.stringify({ owner: tabId, expiresAt: now + lockTtlMs }))) {
387
+ return true;
388
+ }
389
+ return true;
390
+ }
391
+ return false;
392
+ },
393
+ releaseFlushLease() {
394
+ if (!config.storage) return;
395
+ const rawLock = safeGetItem(lockKey);
396
+ if (!rawLock) return;
397
+ try {
398
+ const existingLock = JSON.parse(rawLock);
399
+ if (existingLock.owner === tabId) {
400
+ safeRemoveItem(lockKey);
401
+ }
402
+ } catch {
403
+ safeRemoveItem(lockKey);
404
+ }
405
+ },
406
+ getDiagnostics() {
407
+ return diagnostics;
408
+ }
409
+ };
410
+ }
411
+
412
+ // src/browser/core/privacy.ts
413
+ var sensitiveKeys = /* @__PURE__ */ new Set([
414
+ "email",
415
+ "phone",
416
+ "mobile",
417
+ "address",
418
+ "address1",
419
+ "address2",
420
+ "first_name",
421
+ "last_name",
422
+ "name",
423
+ "token",
424
+ "secret",
425
+ "password",
426
+ "session",
427
+ "cookie"
428
+ ]);
429
+ function sanitizeValue(value, allow) {
430
+ if (Array.isArray(value)) {
431
+ return value.map((item) => sanitizeValue(item, allow));
432
+ }
433
+ if (value && typeof value === "object") {
434
+ return Object.fromEntries(
435
+ Object.entries(value).filter(([key]) => !sensitiveKeys.has(key.toLowerCase()) || allow.has(key.toLowerCase())).map(([key, nestedValue]) => [key, sanitizeValue(nestedValue, allow)])
436
+ );
437
+ }
438
+ return value;
439
+ }
440
+ function sanitizeProperties(input, allowRawKeys = []) {
441
+ const allow = new Set(allowRawKeys.map((key) => key.toLowerCase()));
442
+ return sanitizeValue(input, allow);
443
+ }
444
+
445
+ // src/browser/core/schema.ts
446
+ var reservedKeys = /* @__PURE__ */ new Set(["messageId", "timestamp", "type", "event", "anonymousId", "userId", "context"]);
447
+ function validateEventEnvelope(envelope, options = {}) {
448
+ if (options.mode === "off") return { ok: true, errors: [] };
449
+ const errors = [];
450
+ if (typeof envelope.type !== "string") errors.push("type must be a string");
451
+ if (envelope.type === "track" && typeof envelope.event !== "string") {
452
+ errors.push("track event must include event name");
453
+ }
454
+ if (typeof envelope.messageId !== "string") errors.push("messageId must be a string");
455
+ if (typeof envelope.timestamp !== "string") errors.push("timestamp must be an ISO string");
456
+ for (const key of Object.keys(envelope.properties ?? {})) {
457
+ if (reservedKeys.has(key)) errors.push(`properties.${key} is reserved`);
458
+ }
459
+ return { ok: errors.length === 0, errors };
460
+ }
461
+
462
+ // src/browser/core/DriveMetaDataSDK.ts
463
+ function createId2(prefix) {
464
+ return `${prefix}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
465
+ }
466
+ function endpointFromConfig(config) {
467
+ const host = config.apiHost ?? "https://sdk.drivemetadata.com/v2";
468
+ return `${host.replace(/\/$/, "")}/data-collector`;
469
+ }
470
+ function getBrowserStorage() {
471
+ const browserWindow = getBrowserWindow();
472
+ try {
473
+ return browserWindow?.localStorage;
474
+ } catch {
475
+ return void 0;
476
+ }
477
+ }
478
+ var DriveMetaDataSDK = class {
479
+ constructor(config) {
480
+ this.initialized = true;
481
+ this.queue = [];
482
+ this.offline = false;
483
+ this.droppedEvents = 0;
484
+ this.config = config;
485
+ this.endpoint = endpointFromConfig(config);
486
+ const storage = getBrowserStorage();
487
+ const deliveryConfig = {
488
+ endpoint: this.endpoint
489
+ };
490
+ if (config.delivery?.maxQueueSize !== void 0) deliveryConfig.maxQueueSize = config.delivery.maxQueueSize;
491
+ if (config.delivery?.queueTtlMs !== void 0) deliveryConfig.queueTtlMs = config.delivery.queueTtlMs;
492
+ if (config.delivery?.retryDelayMs !== void 0) deliveryConfig.retryDelayMs = config.delivery.retryDelayMs;
493
+ if (config.delivery?.maxRetryDelayMs !== void 0) deliveryConfig.maxRetryDelayMs = config.delivery.maxRetryDelayMs;
494
+ if (config.delivery?.maxPayloadBytes !== void 0) deliveryConfig.maxPayloadBytes = config.delivery.maxPayloadBytes;
495
+ if (config.delivery?.batchSize !== void 0) deliveryConfig.batchSize = config.delivery.batchSize;
496
+ if (config.onDrop !== void 0) deliveryConfig.onDrop = config.onDrop;
497
+ if (config.onError !== void 0) deliveryConfig.onError = config.onError;
498
+ this.delivery = createDeliveryManager(storage ? {
499
+ ...deliveryConfig,
500
+ storage
501
+ } : deliveryConfig);
502
+ this.initialRetryDelayMs = config.delivery?.retryDelayMs ?? 1e3;
503
+ this.retryDelayMs = this.initialRetryDelayMs;
504
+ this.maxRetryDelayMs = config.delivery?.maxRetryDelayMs ?? 3e4;
505
+ this.writeKey = config.writeKey || config.token || "";
506
+ this.identity = { anonymousId: createId2("anon") };
507
+ this.consentState = normalizeConsent(config.gdprConsent ?? config.consent);
508
+ this.gdprConsent = this.consentState.analytics;
509
+ if (!config.delivery?.disableLifecycleFlush) {
510
+ this.installLifecycleFlush();
511
+ }
512
+ }
513
+ trackEvent(event, properties = {}, options = {}) {
514
+ this.sendPreparedEvent("track", event, properties, options);
515
+ }
516
+ page(name, properties = {}, options = {}) {
517
+ this.sendPreparedEvent("page", "page_view", { name, ...properties }, options);
518
+ }
519
+ trackPageview() {
520
+ this.page();
521
+ }
522
+ identify(userId, traits = {}, options = {}) {
523
+ this.identity = { ...this.identity, userId, traits };
524
+ this.sendPreparedEvent("identify", "identify", { userId, traits }, options);
525
+ }
526
+ identifyUser(userId, traits = {}) {
527
+ this.identify(userId, traits);
528
+ }
529
+ group(groupId, traits = {}, options = {}) {
530
+ this.identity = { ...this.identity, groupId };
531
+ this.sendPreparedEvent("group", "group", { groupId, traits }, options);
532
+ }
533
+ alias(previousId, userId, options = {}) {
534
+ this.sendPreparedEvent("alias", "alias", { previousId, userId }, options);
535
+ }
536
+ async flush() {
537
+ await this.delivery.flushQueue();
538
+ const diagnostics = this.delivery.getDiagnostics();
539
+ this.offline = diagnostics.queued > 0;
540
+ this.lastError = diagnostics.lastError;
541
+ if (diagnostics.queued > 0) {
542
+ this.scheduleRetryFlush();
543
+ } else {
544
+ this.retryDelayMs = this.initialRetryDelayMs;
545
+ }
546
+ }
547
+ reset() {
548
+ this.identity = { anonymousId: createId2("anon") };
549
+ this.queue = [];
550
+ this.offline = false;
551
+ if (this.retryTimer !== void 0) {
552
+ clearTimeout(this.retryTimer);
553
+ this.retryTimer = void 0;
554
+ }
555
+ this.lifecycleCleanup?.();
556
+ this.lifecycleCleanup = void 0;
557
+ this.delivery.clearQueue("manual_clear");
558
+ }
559
+ setConsent(consent) {
560
+ this.consentState = typeof consent === "object" ? mergeConsent(this.consentState, consent) : normalizeConsent(consent);
561
+ this.gdprConsent = this.consentState.analytics;
562
+ if (!canCollectPurpose(this.consentState, "analytics")) {
563
+ this.queue = [];
564
+ this.delivery.clearQueue("consent_revoked");
565
+ }
566
+ }
567
+ getHealth() {
568
+ const deliveryDiagnostics = this.delivery.getDiagnostics();
569
+ const health = {
570
+ initialized: this.initialized,
571
+ consent: this.gdprConsent,
572
+ consentPurposes: this.consentState,
573
+ queueSize: deliveryDiagnostics.queued,
574
+ offline: this.offline || deliveryDiagnostics.queued > 0,
575
+ droppedEvents: this.droppedEvents + deliveryDiagnostics.dropped.length,
576
+ delivery: deliveryDiagnostics
577
+ };
578
+ const lastError2 = this.lastError ?? deliveryDiagnostics.lastError;
579
+ if (lastError2 !== void 0) health.lastError = lastError2;
580
+ if (this.lastDroppedEvent !== void 0) health.lastDroppedEvent = this.lastDroppedEvent;
581
+ return health;
582
+ }
583
+ sendEvent(payload) {
584
+ void this.delivery.send(payload).then(() => {
585
+ const diagnostics = this.delivery.getDiagnostics();
586
+ this.offline = diagnostics.queued > 0;
587
+ this.lastError = diagnostics.lastError;
588
+ if (diagnostics.queued > 0) {
589
+ this.scheduleRetryFlush();
590
+ }
591
+ });
592
+ }
593
+ installLifecycleFlush() {
594
+ const browserWindow = getBrowserWindow();
595
+ if (!browserWindow) return;
596
+ if (typeof browserWindow.addEventListener !== "function") return;
597
+ if (typeof browserWindow.removeEventListener !== "function") return;
598
+ const flushOnLifecycle = () => {
599
+ void this.flush();
600
+ };
601
+ const flushOnVisibility = () => {
602
+ if (browserWindow.document?.visibilityState === "hidden") {
603
+ void this.flush();
604
+ }
605
+ };
606
+ browserWindow.addEventListener("online", flushOnLifecycle);
607
+ browserWindow.addEventListener("pagehide", flushOnLifecycle);
608
+ if (typeof browserWindow.document?.addEventListener === "function") {
609
+ browserWindow.document.addEventListener("visibilitychange", flushOnVisibility);
610
+ }
611
+ this.lifecycleCleanup = () => {
612
+ browserWindow.removeEventListener("online", flushOnLifecycle);
613
+ browserWindow.removeEventListener("pagehide", flushOnLifecycle);
614
+ if (typeof browserWindow.document?.removeEventListener === "function") {
615
+ browserWindow.document.removeEventListener("visibilitychange", flushOnVisibility);
616
+ }
617
+ };
618
+ }
619
+ scheduleRetryFlush() {
620
+ if (this.retryTimer !== void 0) return;
621
+ const delay = Math.min(this.retryDelayMs, this.maxRetryDelayMs);
622
+ this.retryTimer = setTimeout(() => {
623
+ this.retryTimer = void 0;
624
+ void this.flush().then(() => {
625
+ if (this.delivery.getDiagnostics().queued > 0) {
626
+ this.retryDelayMs = Math.min(this.retryDelayMs * 2, this.maxRetryDelayMs);
627
+ this.scheduleRetryFlush();
628
+ }
629
+ });
630
+ }, delay);
631
+ }
632
+ sendPreparedEvent(type, event, properties, options) {
633
+ void this.prepareAndSendEvent(type, event, properties, options);
634
+ }
635
+ async prepareAndSendEvent(type, event, properties, options) {
636
+ if (!canCollectPurpose(this.consentState, "analytics")) {
637
+ this.recordDrop(type, "consent_denied", event);
638
+ return;
639
+ }
640
+ let prepared = {
641
+ type,
642
+ event,
643
+ properties: sanitizeProperties(properties),
644
+ messageId: options.messageId ?? createId2("msg"),
645
+ timestamp: options.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
646
+ context: options.context ?? {},
647
+ anonymousId: this.identity.anonymousId,
648
+ clientId: this.config.clientId,
649
+ workspaceId: this.config.workspaceId,
650
+ appId: this.config.appId,
651
+ writeKey: this.writeKey,
652
+ consent: this.consentState
653
+ };
654
+ if (this.identity.userId !== void 0) prepared.userId = this.identity.userId;
655
+ if (this.identity.groupId !== void 0) prepared.groupId = this.identity.groupId;
656
+ if (this.config.beforeSend !== void 0) {
657
+ const beforeSendResult = await this.config.beforeSend(prepared);
658
+ if (beforeSendResult === null) {
659
+ this.recordDrop(type, "before_send_dropped", event);
660
+ return;
661
+ }
662
+ prepared = {
663
+ ...prepared,
664
+ ...beforeSendResult,
665
+ properties: sanitizeProperties(beforeSendResult.properties ?? prepared.properties)
666
+ };
667
+ }
668
+ const validation = validateEventEnvelope(prepared, { mode: this.config.schemaValidation ?? "warn" });
669
+ if (!validation.ok && this.config.schemaValidation === "strict") {
670
+ this.recordDrop(type, "invalid_payload", event);
671
+ return;
672
+ }
673
+ if (!validation.ok) {
674
+ this.lastError = `DMD SDK schema warning: ${validation.errors.join(", ")}`;
675
+ }
676
+ this.sendEvent(prepared);
677
+ }
678
+ recordDrop(type, reason, event) {
679
+ this.droppedEvents += 1;
680
+ this.lastDroppedEvent = {
681
+ type,
682
+ reason,
683
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
684
+ };
685
+ if (event !== void 0) this.lastDroppedEvent.event = event;
686
+ this.lastError = `DMD SDK ${type} dropped because ${reason}`;
687
+ }
688
+ };
689
+
690
+ // src/browser/legacy-loader.ts
691
+ function getLegacySdkInstanceFromWindow() {
692
+ const browserWindow = getBrowserWindow();
693
+ return browserWindow?.__DriveMetaDataSDKInstance;
694
+ }
695
+ function ensureLegacySdkLoaded() {
696
+ if (!isBrowserRuntime()) {
697
+ throw new Error("DMD legacy SDK is only available in a browser runtime");
698
+ }
699
+ const browserWindow = getBrowserWindow();
700
+ if (!browserWindow?.DriveMetaDataSDK) {
701
+ throw new Error("DMD legacy SDK constructor is missing from window.DriveMetaDataSDK");
702
+ }
703
+ return browserWindow.DriveMetaDataSDK;
704
+ }
705
+
706
+ // src/browser/client.ts
707
+ var singleton;
708
+ var publicSingleton;
709
+ var droppedEvents = 0;
710
+ var lastError;
711
+ var lastDroppedEvent;
712
+ function createPublicClient(instance) {
713
+ return {
714
+ __legacy: instance,
715
+ track(event, properties, options) {
716
+ if (instance.trackEvent) {
717
+ instance.trackEvent(event, properties, options);
718
+ return;
719
+ }
720
+ instance.sendEvent?.({ eventName: event, event, properties, options });
721
+ },
722
+ identify(userId, traits, options) {
723
+ if (instance.identify) {
724
+ instance.identify(userId, traits, options);
725
+ return;
726
+ }
727
+ instance.identifyUser?.(userId, traits);
728
+ },
729
+ page(name, properties, options) {
730
+ if (instance.page) {
731
+ instance.page(name, properties, options);
732
+ return;
733
+ }
734
+ instance.trackPageview?.();
735
+ },
736
+ group(groupId, traits, options) {
737
+ instance.group?.(groupId, traits, options);
738
+ },
739
+ alias(previousId, userId, options) {
740
+ instance.alias?.(previousId, userId, options);
741
+ },
742
+ async flush() {
743
+ await instance.flush?.();
744
+ },
745
+ reset() {
746
+ instance.reset?.();
747
+ singleton = void 0;
748
+ publicSingleton = void 0;
749
+ },
750
+ setConsent(consent) {
751
+ if (instance.setConsent) {
752
+ instance.setConsent(consent);
753
+ return;
754
+ }
755
+ if (typeof consent === "string") {
756
+ instance.gdprConsent = consent;
757
+ }
758
+ },
759
+ getHealth() {
760
+ if (instance.getHealth) {
761
+ return instance.getHealth();
762
+ }
763
+ return getDmdHealth();
764
+ }
765
+ };
766
+ }
767
+ function setSingleton(instance) {
768
+ singleton = instance;
769
+ publicSingleton = createPublicClient(instance);
770
+ return publicSingleton;
771
+ }
772
+ function recordDroppedEvent(type, event) {
773
+ droppedEvents += 1;
774
+ lastDroppedEvent = {
775
+ type,
776
+ reason: "not_initialized",
777
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
778
+ };
779
+ if (event !== void 0) {
780
+ lastDroppedEvent.event = event;
781
+ }
782
+ lastError = `DMD SDK ${type} called before initialization`;
783
+ }
784
+ function initDmdSDK(config) {
785
+ if (publicSingleton) {
786
+ return publicSingleton;
787
+ }
788
+ const legacyConfig = normalizeBrowserConfig(config);
789
+ const existingInstance = getLegacySdkInstanceFromWindow();
790
+ if (existingInstance) {
791
+ return setSingleton(existingInstance);
792
+ }
793
+ try {
794
+ let instance;
795
+ try {
796
+ const LegacySdk = ensureLegacySdkLoaded();
797
+ instance = new LegacySdk(legacyConfig);
798
+ } catch (error) {
799
+ if (error instanceof Error && error.message.includes("constructor is missing")) {
800
+ instance = new DriveMetaDataSDK(config);
801
+ } else {
802
+ throw error;
803
+ }
804
+ }
805
+ return setSingleton(instance);
806
+ } catch (error) {
807
+ lastError = error instanceof Error ? error.message : String(error);
808
+ throw error;
809
+ }
810
+ }
811
+ function getDmdSDK() {
812
+ return publicSingleton;
813
+ }
814
+ function track(event, properties, options) {
815
+ const sdk = getDmdSDK();
816
+ if (!sdk) {
817
+ recordDroppedEvent("track", event);
818
+ return;
819
+ }
820
+ sdk.track(event, properties, options);
821
+ }
822
+ function identify(userId, traits, options) {
823
+ const sdk = getDmdSDK();
824
+ if (!sdk) {
825
+ recordDroppedEvent("identify");
826
+ return;
827
+ }
828
+ sdk.identify(userId, traits, options);
829
+ }
830
+ function page(name, properties, options) {
831
+ const sdk = getDmdSDK();
832
+ if (!sdk) {
833
+ recordDroppedEvent("page");
834
+ return;
835
+ }
836
+ sdk.page(name, properties, options);
837
+ }
838
+ function setConsent(consent) {
839
+ getDmdSDK()?.setConsent(consent);
840
+ }
841
+ function getDmdHealth() {
842
+ if (singleton?.getHealth) {
843
+ return singleton.getHealth();
844
+ }
845
+ const health = {
846
+ initialized: Boolean(singleton?.initialized ?? singleton),
847
+ consent: singleton?.gdprConsent ?? "pending",
848
+ queueSize: singleton?.queue?.length ?? 0,
849
+ offline: Boolean(singleton?.offline),
850
+ droppedEvents
851
+ };
852
+ if (lastError !== void 0) {
853
+ health.lastError = lastError;
854
+ }
855
+ if (lastDroppedEvent !== void 0) {
856
+ health.lastDroppedEvent = lastDroppedEvent;
857
+ }
858
+ return health;
859
+ }
860
+
861
+ // src/react/DmdProvider.tsx
862
+ var import_jsx_runtime = require("react/jsx-runtime");
863
+ var DmdContext = import_react.default.createContext(void 0);
864
+ function DmdProvider({
865
+ config,
866
+ children,
867
+ enabled = true,
868
+ onReady,
869
+ onError
870
+ }) {
871
+ const [client, setClient] = import_react.default.useState(() => getDmdSDK());
872
+ import_react.default.useEffect(() => {
873
+ if (!enabled) return;
874
+ try {
875
+ const initializedClient = initDmdSDK(config);
876
+ const configuredConsent = config.gdprConsent ?? config.consent;
877
+ if (configuredConsent !== void 0) {
878
+ initializedClient.setConsent(configuredConsent);
879
+ }
880
+ setClient(initializedClient);
881
+ onReady?.(initializedClient);
882
+ } catch (error) {
883
+ const normalizedError = error instanceof Error ? error : new Error(String(error));
884
+ onError?.(normalizedError);
885
+ }
886
+ }, [
887
+ enabled,
888
+ config.clientId,
889
+ config.workspaceId,
890
+ config.appId,
891
+ config.writeKey,
892
+ config.apiHost,
893
+ config.consent,
894
+ config.gdprConsent,
895
+ onReady,
896
+ onError
897
+ ]);
898
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DmdContext.Provider, { value: client, children });
899
+ }
900
+
901
+ // src/next/useDmdAppRouterPageTracking.ts
902
+ var import_react2 = __toESM(require("react"), 1);
903
+ function useDmdAppRouterPageTracking(pathname, search, options = {}) {
904
+ const lastRouteKey = import_react2.default.useRef(void 0);
905
+ const routeKey = pathname ? `${pathname}?${search ?? ""}` : "";
906
+ import_react2.default.useEffect(() => {
907
+ if (!pathname || !routeKey || lastRouteKey.current === routeKey) return;
908
+ lastRouteKey.current = routeKey;
909
+ page(options.name, {
910
+ ...options.properties,
911
+ pathname,
912
+ search: search ?? ""
913
+ });
914
+ }, [options.name, options.properties, pathname, routeKey, search]);
915
+ }
916
+
917
+ // src/next/useDmdPagesRouterPageTracking.ts
918
+ var import_react3 = __toESM(require("react"), 1);
919
+ function useDmdPagesRouterPageTracking(router, options = {}) {
920
+ const lastRouteKey = import_react3.default.useRef(void 0);
921
+ const routeKey = router?.asPath ?? router?.pathname ?? "";
922
+ import_react3.default.useEffect(() => {
923
+ if (!routeKey || lastRouteKey.current === routeKey) return;
924
+ lastRouteKey.current = routeKey;
925
+ page(options.name, {
926
+ ...options.properties,
927
+ route: routeKey
928
+ });
929
+ }, [options.name, options.properties, routeKey]);
930
+ }
931
+
932
+ // src/react/hooks.ts
933
+ var import_react4 = __toESM(require("react"), 1);
934
+ function useDmdSDK() {
935
+ return import_react4.default.useContext(DmdContext);
936
+ }
937
+ function useTrackEvent() {
938
+ return import_react4.default.useCallback((event, properties, options) => {
939
+ track(event, properties, options);
940
+ }, []);
941
+ }
942
+ function useIdentify() {
943
+ return import_react4.default.useCallback((userId, traits) => {
944
+ identify(userId, traits);
945
+ }, []);
946
+ }
947
+ function useDmdConsent() {
948
+ return import_react4.default.useCallback((consent) => {
949
+ setConsent(consent);
950
+ }, []);
951
+ }
952
+ function useDmdHealth() {
953
+ const client = useDmdSDK();
954
+ const [health, setHealth] = import_react4.default.useState(() => getDmdHealth());
955
+ import_react4.default.useEffect(() => {
956
+ setHealth(getDmdHealth());
957
+ }, [client]);
958
+ return health;
959
+ }
960
+ // Annotate the CommonJS export names for ESM import in node:
961
+ 0 && (module.exports = {
962
+ DmdProvider,
963
+ useDmdAppRouterPageTracking,
964
+ useDmdConsent,
965
+ useDmdHealth,
966
+ useDmdPagesRouterPageTracking,
967
+ useDmdSDK,
968
+ useIdentify,
969
+ useTrackEvent
970
+ });
971
+ //# sourceMappingURL=index.cjs.map