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