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