@drivemetadata-ai/sdk 0.1.1-beta.2 → 0.1.1-beta.4

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.
@@ -37,12 +37,62 @@ __export(browser_exports, {
37
37
  });
38
38
  module.exports = __toCommonJS(browser_exports);
39
39
 
40
+ // src/browser/core/browser-config.ts
41
+ function setIfDefined(target, key, value) {
42
+ if (value !== void 0) {
43
+ target[key] = value;
44
+ }
45
+ }
46
+ function normalizeBrowserConfig(input) {
47
+ const normalized = {
48
+ ...input,
49
+ clientId: input.clientId ?? "",
50
+ workspaceId: input.workspaceId ?? "",
51
+ appId: input.appId ?? ""
52
+ };
53
+ setIfDefined(normalized, "apiHost", input.apiHost);
54
+ setIfDefined(normalized, "capturePageview", input.capturePageview);
55
+ setIfDefined(normalized, "capturePageleave", input.capturePageleave);
56
+ setIfDefined(normalized, "captureDeadClicks", input.captureDeadClicks);
57
+ setIfDefined(normalized, "crossSubdomainCookie", input.crossSubdomainCookie);
58
+ setIfDefined(normalized, "sessionIdleTimeoutSeconds", input.sessionIdleTimeoutSeconds);
59
+ setIfDefined(normalized, "schemaValidation", input.schemaValidation);
60
+ setIfDefined(normalized, "beforeSend", input.beforeSend);
61
+ setIfDefined(normalized, "persistence", input.disablePersistence === true ? "none" : input.persistence);
62
+ return normalized;
63
+ }
64
+
65
+ // src/core/uuid.ts
66
+ function createUuid() {
67
+ const cryptoApi = globalThis.crypto;
68
+ if (typeof cryptoApi?.randomUUID === "function") {
69
+ return cryptoApi.randomUUID();
70
+ }
71
+ const bytes = new Uint8Array(16);
72
+ if (typeof cryptoApi?.getRandomValues === "function") {
73
+ cryptoApi.getRandomValues(bytes);
74
+ } else {
75
+ for (let index = 0; index < bytes.length; index += 1) {
76
+ bytes[index] = Math.floor(Math.random() * 256);
77
+ }
78
+ }
79
+ bytes[6] = (bytes[6] ?? 0) & 15 | 64;
80
+ bytes[8] = (bytes[8] ?? 0) & 63 | 128;
81
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
82
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
83
+ }
84
+ function isUuid(value) {
85
+ return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
86
+ }
87
+ function ensureUuid(value) {
88
+ return isUuid(value) ? value : createUuid();
89
+ }
90
+
40
91
  // src/core/backend-payload.ts
41
92
  function formatUtcTimestamp(value) {
42
- if (typeof value !== "string") return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
43
- const date = new Date(value);
44
- if (Number.isNaN(date.getTime())) return value;
45
- return date.toISOString().replace("T", " ").slice(0, 19);
93
+ const date = typeof value === "string" || typeof value === "number" || value instanceof Date ? new Date(value) : /* @__PURE__ */ new Date();
94
+ const safeDate = Number.isNaN(date.getTime()) ? /* @__PURE__ */ new Date() : date;
95
+ return safeDate.toISOString().replace("T", " ").slice(0, 19);
46
96
  }
47
97
  function cleanObject(value) {
48
98
  if (Array.isArray(value)) {
@@ -73,15 +123,19 @@ function createBackendCollectorPayload(input) {
73
123
  };
74
124
  const metaData = {
75
125
  ...normalizedEventData,
76
- requestId: input.requestId,
126
+ requestId: ensureUuid(typeof input.requestId === "string" ? input.requestId : void 0),
77
127
  timestamp,
78
128
  eventType: input.eventType,
79
129
  requestFrom: input.requestFrom ?? "3",
80
130
  clientId: input.clientId,
81
131
  workspaceId: input.workspaceId,
82
132
  token: input.token,
83
- anonymousId: eventData.anonymousId ?? input.anonymousId,
84
- sessionId: eventData.sessionId ?? input.sessionId,
133
+ anonymousId: ensureUuid(
134
+ typeof eventData.anonymousId === "string" ? eventData.anonymousId : typeof input.anonymousId === "string" ? input.anonymousId : void 0
135
+ ),
136
+ sessionId: ensureUuid(
137
+ typeof eventData.sessionId === "string" ? eventData.sessionId : typeof input.sessionId === "string" ? input.sessionId : void 0
138
+ ),
85
139
  ua: input.ua,
86
140
  appDetails: { app_id: input.appId },
87
141
  page: { ...page2, url: page2.url ?? input.pageUrl },
@@ -92,12 +146,45 @@ function createBackendCollectorPayload(input) {
92
146
  return cleanObject(payload);
93
147
  }
94
148
 
95
- // src/core/environment.ts
96
- function getBrowserWindow() {
97
- return typeof window === "undefined" ? void 0 : window;
149
+ // src/core/backend-schema.ts
150
+ var requiredMetaDataFields = [
151
+ "requestId",
152
+ "timestamp",
153
+ "eventType",
154
+ "requestFrom",
155
+ "clientId",
156
+ "workspaceId",
157
+ "anonymousId",
158
+ "sessionId"
159
+ ];
160
+ function isPresent(value) {
161
+ return value !== null && value !== void 0 && value !== "";
162
+ }
163
+ function validateBackendCollectorPayload(payload) {
164
+ const errors = [];
165
+ const metaData = payload.metaData;
166
+ if (!metaData || typeof metaData !== "object" || Array.isArray(metaData)) {
167
+ return {
168
+ ok: false,
169
+ errors: ["metaData is required"]
170
+ };
171
+ }
172
+ const metadataRecord = metaData;
173
+ for (const field of requiredMetaDataFields) {
174
+ if (!isPresent(metadataRecord[field])) {
175
+ errors.push(`metaData.${field} is required`);
176
+ }
177
+ }
178
+ if (isPresent(metadataRecord.eventType) && typeof metadataRecord.eventType !== "string") {
179
+ errors.push("metaData.eventType must be a string");
180
+ }
181
+ return {
182
+ ok: errors.length === 0,
183
+ errors
184
+ };
98
185
  }
99
186
 
100
- // src/browser/core/consent.ts
187
+ // src/core/consent.ts
101
188
  var purposes = [
102
189
  "analytics",
103
190
  "advertising",
@@ -137,6 +224,335 @@ function canCollectPurpose(consent2, purpose) {
137
224
  return consent2[purpose] === "granted";
138
225
  }
139
226
 
227
+ // src/core/event-schema.ts
228
+ var reservedKeys = /* @__PURE__ */ new Set(["messageId", "timestamp", "type", "event", "anonymousId", "userId", "context"]);
229
+ function validateEventEnvelope(envelope, options = {}) {
230
+ if (options.mode === "off") return { ok: true, errors: [] };
231
+ const errors = [];
232
+ if (typeof envelope.type !== "string") errors.push("type must be a string");
233
+ if (envelope.type === "track" && typeof envelope.event !== "string") {
234
+ errors.push("track event must include event name");
235
+ }
236
+ if (typeof envelope.messageId !== "string") errors.push("messageId must be a string");
237
+ if (typeof envelope.timestamp !== "string") errors.push("timestamp must be an ISO string");
238
+ for (const key of Object.keys(envelope.properties ?? {})) {
239
+ if (reservedKeys.has(key)) errors.push(`properties.${key} is reserved`);
240
+ }
241
+ return { ok: errors.length === 0, errors };
242
+ }
243
+
244
+ // src/core/environment.ts
245
+ function getBrowserWindow() {
246
+ return typeof window === "undefined" ? void 0 : window;
247
+ }
248
+
249
+ // src/core/privacy.ts
250
+ var sensitiveKeys = /* @__PURE__ */ new Set([
251
+ "email",
252
+ "phone",
253
+ "mobile",
254
+ "address",
255
+ "address1",
256
+ "address2",
257
+ "first_name",
258
+ "last_name",
259
+ "name",
260
+ "token",
261
+ "secret",
262
+ "password",
263
+ "session",
264
+ "cookie"
265
+ ]);
266
+ function sanitizeValue(value, allow, path = []) {
267
+ if (Array.isArray(value)) {
268
+ return value.map((item) => sanitizeValue(item, allow, path));
269
+ }
270
+ if (value && typeof value === "object") {
271
+ return Object.fromEntries(
272
+ Object.entries(value).filter(([key]) => {
273
+ const lowerKey = key.toLowerCase();
274
+ const lowerPath = path.map((item) => item.toLowerCase());
275
+ const isEcommerceItemName = lowerKey === "name" && lowerPath.includes("ecommerce") && lowerPath.includes("items");
276
+ return isEcommerceItemName || !sensitiveKeys.has(lowerKey) || allow.has(lowerKey);
277
+ }).map(([key, nestedValue]) => [key, sanitizeValue(nestedValue, allow, [...path, key])])
278
+ );
279
+ }
280
+ return value;
281
+ }
282
+ function sanitizeProperties(input, allowRawKeys = []) {
283
+ const allow = new Set(allowRawKeys.map((key) => key.toLowerCase()));
284
+ return sanitizeValue(input, allow);
285
+ }
286
+
287
+ // src/core/attribution.ts
288
+ var dmdUtmKeys = [
289
+ "utm_source",
290
+ "utm_medium",
291
+ "utm_campaign",
292
+ "utm_term",
293
+ "utm_content",
294
+ "utm_id"
295
+ ];
296
+ var dmdCampaignKeys = ["campaign_id", "ad_id"];
297
+ var dmdClickIdSources = [
298
+ ["gclid", 2],
299
+ ["fbclid", 3],
300
+ ["ScCid", 1],
301
+ ["li_fat_id", 4]
302
+ ];
303
+ function cleanAttributionRecord(record) {
304
+ const cleaned = Object.fromEntries(
305
+ Object.entries(record).filter(([, value]) => value !== null && value !== void 0 && value !== "")
306
+ );
307
+ return Object.keys(cleaned).length > 0 ? cleaned : void 0;
308
+ }
309
+ function objectValue(value) {
310
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
311
+ }
312
+ function mergeAttributionRecords(explicitProperties, stored) {
313
+ const attributionData = objectValue(explicitProperties.attributionData);
314
+ const utmParameter = objectValue(explicitProperties.utmParameter);
315
+ return {
316
+ ...explicitProperties,
317
+ ...stored.attributionData || attributionData ? { attributionData: { ...stored.attributionData ?? {}, ...attributionData ?? {} } } : {},
318
+ ...stored.utmParameter || utmParameter ? { utmParameter: { ...stored.utmParameter ?? {}, ...utmParameter ?? {} } } : {}
319
+ };
320
+ }
321
+
322
+ // src/browser/core/attribution.ts
323
+ function getCurrentUrl() {
324
+ return getBrowserWindow()?.location?.href;
325
+ }
326
+ function getCookie(name) {
327
+ const cookie = getBrowserWindow()?.document?.cookie;
328
+ if (!cookie) return void 0;
329
+ const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
330
+ const match = new RegExp(`(?:^|; )${escapedName}=([^;]*)`).exec(cookie);
331
+ return match ? decodeURIComponent(match[1] ?? "") : void 0;
332
+ }
333
+ function getParams(url) {
334
+ return new URL(url, "https://placeholder.local").searchParams;
335
+ }
336
+ function setIfPresent(persistence, key, value) {
337
+ if (value !== null && value !== "") {
338
+ persistence.setItem(key, value);
339
+ }
340
+ }
341
+ function getStoredString(persistence, key) {
342
+ const value = persistence.getItem(key);
343
+ return value === null || value === "" ? void 0 : value;
344
+ }
345
+ function getStoredNumberOrString(persistence, key) {
346
+ const value = getStoredString(persistence, key);
347
+ if (value === void 0) return void 0;
348
+ const parsed = Number.parseInt(value, 10);
349
+ return Number.isNaN(parsed) ? value : parsed;
350
+ }
351
+ function captureAttributionFromUrl(persistence, url = getCurrentUrl()) {
352
+ if (!url) return;
353
+ const params = getParams(url);
354
+ for (const key of dmdUtmKeys) {
355
+ setIfPresent(persistence, key, params.get(key));
356
+ }
357
+ for (const key of dmdCampaignKeys) {
358
+ setIfPresent(persistence, key, params.get(key));
359
+ }
360
+ for (const [param, sdkPubId] of dmdClickIdSources) {
361
+ const value = params.get(param);
362
+ if (value) {
363
+ persistence.setItem("unique_id", value);
364
+ persistence.setItem("sdk_pub_id", String(sdkPubId));
365
+ }
366
+ }
367
+ }
368
+ function getStoredAttributionData(persistence) {
369
+ return cleanAttributionRecord({
370
+ unique_id: getStoredString(persistence, "unique_id"),
371
+ sdk_pub_id: getStoredNumberOrString(persistence, "sdk_pub_id"),
372
+ fbc: getStoredString(persistence, "fbc") ?? getCookie("_fbc"),
373
+ fbp: getStoredString(persistence, "fbp") ?? getCookie("_fbp"),
374
+ campaign_id: getStoredString(persistence, "campaign_id"),
375
+ ad_id: getStoredString(persistence, "ad_id")
376
+ });
377
+ }
378
+ function getStoredUtmParameter(persistence) {
379
+ return cleanAttributionRecord(Object.fromEntries(dmdUtmKeys.map((key) => [key, getStoredString(persistence, key)])));
380
+ }
381
+ function mergeStoredAttribution(properties, persistence) {
382
+ const stored = {};
383
+ const attributionData = getStoredAttributionData(persistence);
384
+ const utmParameter = getStoredUtmParameter(persistence);
385
+ if (attributionData !== void 0) stored.attributionData = attributionData;
386
+ if (utmParameter !== void 0) stored.utmParameter = utmParameter;
387
+ return mergeAttributionRecords(properties, stored);
388
+ }
389
+
390
+ // src/browser/core/autocapture.ts
391
+ function installAutocapture(config) {
392
+ const trackedPageUrls = /* @__PURE__ */ new Set();
393
+ const timers = /* @__PURE__ */ new Set();
394
+ const pageviewDelayMs = config.pageviewDelayMs ?? 500;
395
+ const history = config.browserWindow.history;
396
+ const originalPushState = history?.pushState;
397
+ const originalReplaceState = history?.replaceState;
398
+ function clearTimer(timer) {
399
+ timers.delete(timer);
400
+ clearTimeout(timer);
401
+ }
402
+ function canonicalUrl() {
403
+ return config.browserWindow.location.href;
404
+ }
405
+ function trackCurrentPageview() {
406
+ if (!config.capturePageview) return;
407
+ const url = canonicalUrl();
408
+ if (trackedPageUrls.has(url)) return;
409
+ trackedPageUrls.add(url);
410
+ config.onPageView();
411
+ }
412
+ function schedulePageview(delayMs) {
413
+ if (!config.capturePageview) return;
414
+ const timer = setTimeout(() => {
415
+ timers.delete(timer);
416
+ trackCurrentPageview();
417
+ }, delayMs);
418
+ timers.add(timer);
419
+ }
420
+ function handleRouteChange() {
421
+ config.onRouteChange?.();
422
+ trackCurrentPageview();
423
+ }
424
+ function wrapHistoryMethod(method) {
425
+ if (!history) return;
426
+ const original = history[method];
427
+ if (typeof original !== "function") return;
428
+ history[method] = function wrappedHistoryMethod(...args) {
429
+ const result = original.apply(this, args);
430
+ const timer = setTimeout(() => {
431
+ timers.delete(timer);
432
+ handleRouteChange();
433
+ }, 0);
434
+ timers.add(timer);
435
+ return result;
436
+ };
437
+ }
438
+ function handlePopOrHashChange() {
439
+ handleRouteChange();
440
+ }
441
+ function handlePageLeave() {
442
+ if (config.capturePageleave) {
443
+ config.onPageLeave();
444
+ }
445
+ }
446
+ schedulePageview(pageviewDelayMs);
447
+ wrapHistoryMethod("pushState");
448
+ wrapHistoryMethod("replaceState");
449
+ const canListen = typeof config.browserWindow.addEventListener === "function" && typeof config.browserWindow.removeEventListener === "function";
450
+ if (canListen) {
451
+ config.browserWindow.addEventListener("popstate", handlePopOrHashChange);
452
+ config.browserWindow.addEventListener("hashchange", handlePopOrHashChange);
453
+ config.browserWindow.addEventListener("beforeunload", handlePageLeave);
454
+ }
455
+ return {
456
+ cleanup() {
457
+ for (const timer of Array.from(timers)) {
458
+ clearTimer(timer);
459
+ }
460
+ if (history && originalPushState) history.pushState = originalPushState;
461
+ if (history && originalReplaceState) history.replaceState = originalReplaceState;
462
+ if (canListen) {
463
+ config.browserWindow.removeEventListener("popstate", handlePopOrHashChange);
464
+ config.browserWindow.removeEventListener("hashchange", handlePopOrHashChange);
465
+ config.browserWindow.removeEventListener("beforeunload", handlePageLeave);
466
+ }
467
+ }
468
+ };
469
+ }
470
+
471
+ // src/core/payload-size.ts
472
+ var preserveStringKeys = /* @__PURE__ */ new Set([
473
+ "requestId",
474
+ "eventType",
475
+ "requestFrom",
476
+ "clientId",
477
+ "workspaceId",
478
+ "anonymousId",
479
+ "sessionId",
480
+ "token"
481
+ ]);
482
+ function payloadByteLength(payload) {
483
+ const serialized = JSON.stringify(payload);
484
+ if (typeof TextEncoder !== "undefined") {
485
+ return new TextEncoder().encode(serialized).length;
486
+ }
487
+ return serialized.length;
488
+ }
489
+ function clonePayload(payload) {
490
+ try {
491
+ return JSON.parse(JSON.stringify(payload));
492
+ } catch {
493
+ return void 0;
494
+ }
495
+ }
496
+ function truncateValue(value, truncateStringLength, key) {
497
+ if (typeof value === "string") {
498
+ if (key && preserveStringKeys.has(key)) return value;
499
+ if (value.length <= truncateStringLength) return value;
500
+ return `${value.slice(0, truncateStringLength)}...[TRUNCATED]`;
501
+ }
502
+ if (Array.isArray(value)) {
503
+ return value.map((item) => truncateValue(item, truncateStringLength));
504
+ }
505
+ if (value && typeof value === "object") {
506
+ return Object.fromEntries(
507
+ Object.entries(value).map(([childKey, childValue]) => [
508
+ childKey,
509
+ truncateValue(childValue, truncateStringLength, childKey)
510
+ ])
511
+ );
512
+ }
513
+ return value;
514
+ }
515
+ function markPayloadTruncated(payload) {
516
+ if (payload.metaData && typeof payload.metaData === "object" && !Array.isArray(payload.metaData)) {
517
+ return {
518
+ ...payload,
519
+ metaData: {
520
+ ...payload.metaData,
521
+ payloadTruncated: true
522
+ }
523
+ };
524
+ }
525
+ return {
526
+ ...payload,
527
+ payloadTruncated: true
528
+ };
529
+ }
530
+ function truncatePayload(payload, truncateStringLength) {
531
+ const cloned = clonePayload(payload);
532
+ if (!cloned) return void 0;
533
+ return markPayloadTruncated(truncateValue(cloned, truncateStringLength));
534
+ }
535
+ function getPayloadMessageId(payload) {
536
+ const value = payload.metaData?.requestId ?? payload.messageId;
537
+ return value === void 0 ? void 0 : String(value);
538
+ }
539
+ function preparePayloadForSizePolicy(payload, options = {}) {
540
+ const maxPayloadBytes = options.maxPayloadBytes ?? 64e3;
541
+ const payloadSizePolicy = options.payloadSizePolicy ?? "drop";
542
+ const payloadTruncateStringLength = options.payloadTruncateStringLength ?? 1024;
543
+ if (payloadByteLength(payload) <= maxPayloadBytes) {
544
+ return { ok: true, payload, truncated: false };
545
+ }
546
+ if (payloadSizePolicy === "truncate") {
547
+ const truncatedPayload = truncatePayload(payload, payloadTruncateStringLength);
548
+ if (truncatedPayload && payloadByteLength(truncatedPayload) <= maxPayloadBytes) {
549
+ return { ok: true, payload: truncatedPayload, truncated: true };
550
+ }
551
+ }
552
+ const messageId = getPayloadMessageId(payload);
553
+ return messageId === void 0 ? { ok: false, reason: "payload_too_large" } : { ok: false, reason: "payload_too_large", messageId };
554
+ }
555
+
140
556
  // src/browser/core/delivery.ts
141
557
  function createId(prefix) {
142
558
  return `${prefix}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
@@ -168,7 +584,6 @@ function createDeliveryManager(config) {
168
584
  const lockTtlMs = config.lockTtlMs ?? 5e3;
169
585
  const tabId = config.tabId ?? createId("tab");
170
586
  const batchSize = config.batchSize ?? 25;
171
- const maxPayloadBytes = config.maxPayloadBytes ?? 64e3;
172
587
  function recordDrop(event) {
173
588
  diagnostics.dropped.push(event);
174
589
  config.onDrop?.(event);
@@ -177,12 +592,22 @@ function createDeliveryManager(config) {
177
592
  diagnostics.lastError = error.message;
178
593
  config.onError?.(error);
179
594
  }
180
- function payloadByteLength(payload) {
181
- const serialized = JSON.stringify(payload);
182
- if (typeof TextEncoder !== "undefined") {
183
- return new TextEncoder().encode(serialized).length;
595
+ function preparePayloadForSend(payload) {
596
+ const sizePolicyOptions = {};
597
+ if (config.maxPayloadBytes !== void 0) sizePolicyOptions.maxPayloadBytes = config.maxPayloadBytes;
598
+ if (config.payloadSizePolicy !== void 0) sizePolicyOptions.payloadSizePolicy = config.payloadSizePolicy;
599
+ if (config.payloadTruncateStringLength !== void 0) {
600
+ sizePolicyOptions.payloadTruncateStringLength = config.payloadTruncateStringLength;
184
601
  }
185
- return serialized.length;
602
+ const prepared = preparePayloadForSizePolicy(payload, sizePolicyOptions);
603
+ if (prepared.ok) return prepared.payload;
604
+ const diagnostic = {
605
+ reason: prepared.reason,
606
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
607
+ };
608
+ if (prepared.messageId !== void 0) diagnostic.messageId = prepared.messageId;
609
+ recordDrop(diagnostic);
610
+ return void 0;
186
611
  }
187
612
  function recordStorageUnavailable() {
188
613
  recordDrop({
@@ -272,7 +697,19 @@ function createDeliveryManager(config) {
272
697
  idempotencyKey: String(payload.idempotencyKey ?? createIdempotencyKey(payload, messageId))
273
698
  };
274
699
  }
700
+ function tryBeacon(body) {
701
+ if (!config.useBeacon) return false;
702
+ const sendBeacon = globalThis.navigator?.sendBeacon;
703
+ if (typeof sendBeacon !== "function") return false;
704
+ try {
705
+ const blob = new Blob([JSON.stringify(body)], { type: "application/json" });
706
+ return sendBeacon.call(globalThis.navigator, config.endpoint, blob);
707
+ } catch {
708
+ return false;
709
+ }
710
+ }
275
711
  async function deliver(body) {
712
+ if (tryBeacon(body)) return;
276
713
  const fetchImpl = config.fetch ?? globalThis.fetch;
277
714
  if (typeof fetchImpl !== "function") {
278
715
  throw new Error("fetch_unavailable");
@@ -289,26 +726,22 @@ function createDeliveryManager(config) {
289
726
  return {
290
727
  async send(payload) {
291
728
  const body = withEnvelope(payload);
292
- if (payloadByteLength(body) > maxPayloadBytes) {
293
- recordDrop({
294
- messageId: String(body.metaData?.requestId ?? body.messageId),
295
- reason: "payload_too_large",
296
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
297
- });
729
+ const preparedBody = preparePayloadForSend(body);
730
+ if (!preparedBody) {
298
731
  return;
299
732
  }
300
733
  diagnostics.inFlight += 1;
301
734
  try {
302
- await deliver(body);
735
+ await deliver(preparedBody);
303
736
  } catch (error) {
304
737
  const deliveryError = error instanceof Error ? error : new Error(String(error));
305
738
  recordError(deliveryError);
306
739
  enqueue({
307
- messageId: String(body.metaData?.requestId ?? body.messageId),
740
+ messageId: String(getPayloadMessageId(body)),
308
741
  savedAt: Date.now(),
309
742
  attempts: 1,
310
743
  lastError: deliveryError.message,
311
- payload: body
744
+ payload: preparedBody
312
745
  });
313
746
  } finally {
314
747
  diagnostics.inFlight -= 1;
@@ -421,60 +854,268 @@ function createDeliveryManager(config) {
421
854
  };
422
855
  }
423
856
 
424
- // src/browser/core/privacy.ts
425
- var sensitiveKeys = /* @__PURE__ */ new Set([
426
- "email",
427
- "phone",
428
- "mobile",
429
- "address",
430
- "address1",
431
- "address2",
432
- "first_name",
433
- "last_name",
434
- "name",
435
- "token",
436
- "secret",
437
- "password",
438
- "session",
439
- "cookie"
440
- ]);
441
- function sanitizeValue(value, allow) {
442
- if (Array.isArray(value)) {
443
- return value.map((item) => sanitizeValue(item, allow));
857
+ // src/browser/core/identity.ts
858
+ var ANONYMOUS_ID_STORAGE_KEY = "dmd_anonymous_id";
859
+ var LEGACY_ANONYMOUS_ID_STORAGE_KEY = "anonymousId";
860
+ function getUrlParam(name) {
861
+ const href = getBrowserWindow()?.location?.href;
862
+ if (!href) return null;
863
+ try {
864
+ return new URL(href).searchParams.get(name);
865
+ } catch {
866
+ return null;
444
867
  }
445
- if (value && typeof value === "object") {
446
- return Object.fromEntries(
447
- Object.entries(value).filter(([key]) => !sensitiveKeys.has(key.toLowerCase()) || allow.has(key.toLowerCase())).map(([key, nestedValue]) => [key, sanitizeValue(nestedValue, allow)])
448
- );
868
+ }
869
+ function resolveAnonymousId(persistence) {
870
+ const urlAnonymousId = getUrlParam("aid");
871
+ if (isUuid(urlAnonymousId)) {
872
+ persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, urlAnonymousId);
873
+ return urlAnonymousId;
449
874
  }
450
- return value;
875
+ const storedAnonymousId = persistence.getItem(ANONYMOUS_ID_STORAGE_KEY);
876
+ if (isUuid(storedAnonymousId)) {
877
+ return storedAnonymousId;
878
+ }
879
+ const legacyAnonymousId = persistence.getItem(LEGACY_ANONYMOUS_ID_STORAGE_KEY);
880
+ if (isUuid(legacyAnonymousId)) {
881
+ persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, legacyAnonymousId);
882
+ return legacyAnonymousId;
883
+ }
884
+ const anonymousId = createUuid();
885
+ persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, anonymousId);
886
+ return anonymousId;
451
887
  }
452
- function sanitizeProperties(input, allowRawKeys = []) {
453
- const allow = new Set(allowRawKeys.map((key) => key.toLowerCase()));
454
- return sanitizeValue(input, allow);
888
+ function resetAnonymousId(persistence) {
889
+ const anonymousId = createUuid();
890
+ persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, anonymousId);
891
+ return anonymousId;
455
892
  }
456
893
 
457
- // src/browser/core/schema.ts
458
- var reservedKeys = /* @__PURE__ */ new Set(["messageId", "timestamp", "type", "event", "anonymousId", "userId", "context"]);
459
- function validateEventEnvelope(envelope, options = {}) {
460
- if (options.mode === "off") return { ok: true, errors: [] };
461
- const errors = [];
462
- if (typeof envelope.type !== "string") errors.push("type must be a string");
463
- if (envelope.type === "track" && typeof envelope.event !== "string") {
464
- errors.push("track event must include event name");
894
+ // src/browser/core/persistence.ts
895
+ function createPersistenceHealth(requested) {
896
+ return {
897
+ requested,
898
+ cookieFallbackUsed: false,
899
+ memoryFallbackUsed: requested === "memory",
900
+ failures: []
901
+ };
902
+ }
903
+ function recordFailure(health, backend, operation, error) {
904
+ health.failures.push({
905
+ backend,
906
+ operation,
907
+ message: error instanceof Error ? error.message : String(error)
908
+ });
909
+ if (health.failures.length > 25) {
910
+ health.failures.shift();
465
911
  }
466
- if (typeof envelope.messageId !== "string") errors.push("messageId must be a string");
467
- if (typeof envelope.timestamp !== "string") errors.push("timestamp must be an ISO string");
468
- for (const key of Object.keys(envelope.properties ?? {})) {
469
- if (reservedKeys.has(key)) errors.push(`properties.${key} is reserved`);
912
+ }
913
+ function createMemoryPersistence(health = createPersistenceHealth("memory")) {
914
+ const memory = {};
915
+ return {
916
+ getItem(key) {
917
+ return Object.prototype.hasOwnProperty.call(memory, key) ? memory[key] ?? null : null;
918
+ },
919
+ setItem(key, value) {
920
+ memory[key] = value;
921
+ return true;
922
+ },
923
+ removeItem(key) {
924
+ delete memory[key];
925
+ },
926
+ getHealth() {
927
+ return {
928
+ ...health,
929
+ failures: health.failures.map((failure) => ({ ...failure }))
930
+ };
931
+ }
932
+ };
933
+ }
934
+ function getCookie2(name) {
935
+ const browserWindow = getBrowserWindow();
936
+ const cookie = browserWindow?.document?.cookie;
937
+ if (!cookie) return null;
938
+ const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
939
+ const match = new RegExp(`(?:^|; )${escapedName}=([^;]*)`).exec(cookie);
940
+ return match ? decodeURIComponent(match[1] ?? "") : null;
941
+ }
942
+ function setCookie(name, value) {
943
+ const browserWindow = getBrowserWindow();
944
+ if (!browserWindow?.document) return false;
945
+ try {
946
+ const expires = new Date(Date.now() + 365 * 24 * 60 * 60 * 1e3).toUTCString();
947
+ const secure = browserWindow.location?.protocol === "https:" ? "; Secure" : "";
948
+ browserWindow.document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/${secure}; SameSite=Lax`;
949
+ return true;
950
+ } catch {
951
+ return false;
952
+ }
953
+ }
954
+ function removeCookie(name) {
955
+ const browserWindow = getBrowserWindow();
956
+ if (!browserWindow?.document) return;
957
+ browserWindow.document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
958
+ }
959
+ function getLocalStorage() {
960
+ const browserWindow = getBrowserWindow();
961
+ try {
962
+ return browserWindow?.localStorage;
963
+ } catch {
964
+ return void 0;
965
+ }
966
+ }
967
+ function getSessionStorage() {
968
+ const browserWindow = getBrowserWindow();
969
+ try {
970
+ return browserWindow?.sessionStorage;
971
+ } catch {
972
+ return void 0;
470
973
  }
471
- return { ok: errors.length === 0, errors };
974
+ }
975
+ function createBrowserPersistence(mode = "localStorage+cookie") {
976
+ const health = createPersistenceHealth(mode);
977
+ const memory = createMemoryPersistence(health);
978
+ if (mode === "none") {
979
+ return {
980
+ getItem() {
981
+ return null;
982
+ },
983
+ setItem() {
984
+ return false;
985
+ },
986
+ removeItem() {
987
+ },
988
+ getHealth() {
989
+ return {
990
+ ...health,
991
+ failures: health.failures.map((failure) => ({ ...failure }))
992
+ };
993
+ }
994
+ };
995
+ }
996
+ if (mode === "memory") return memory;
997
+ const primary = mode === "sessionStorage" ? getSessionStorage() : getLocalStorage();
998
+ const useCookie = mode === "cookie" || mode === "localStorage+cookie" || !primary;
999
+ return {
1000
+ getItem(key) {
1001
+ try {
1002
+ const stored = primary?.getItem(key);
1003
+ if (stored) return stored;
1004
+ } catch (error) {
1005
+ recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "get", error);
1006
+ }
1007
+ if (useCookie) {
1008
+ const cookie = getCookie2(key);
1009
+ if (cookie) {
1010
+ health.cookieFallbackUsed = true;
1011
+ return cookie;
1012
+ }
1013
+ }
1014
+ const value = memory.getItem(key);
1015
+ if (value !== null) health.memoryFallbackUsed = true;
1016
+ return value;
1017
+ },
1018
+ setItem(key, value) {
1019
+ let wrote = false;
1020
+ try {
1021
+ primary?.setItem(key, value);
1022
+ wrote = primary !== void 0;
1023
+ } catch (error) {
1024
+ recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "set", error);
1025
+ wrote = false;
1026
+ }
1027
+ if (useCookie) {
1028
+ const cookieWrote = setCookie(key, value);
1029
+ if (cookieWrote) health.cookieFallbackUsed = true;
1030
+ wrote = cookieWrote || wrote;
1031
+ }
1032
+ if (!wrote) {
1033
+ health.memoryFallbackUsed = true;
1034
+ return memory.setItem(key, value);
1035
+ }
1036
+ if (mode === "localStorage+cookie") {
1037
+ memory.setItem(key, value);
1038
+ }
1039
+ return true;
1040
+ },
1041
+ removeItem(key) {
1042
+ try {
1043
+ primary?.removeItem(key);
1044
+ } catch (error) {
1045
+ recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "remove", error);
1046
+ }
1047
+ if (useCookie) removeCookie(key);
1048
+ memory.removeItem(key);
1049
+ },
1050
+ getHealth() {
1051
+ return {
1052
+ ...health,
1053
+ failures: health.failures.map((failure) => ({ ...failure }))
1054
+ };
1055
+ }
1056
+ };
472
1057
  }
473
1058
 
474
- // src/browser/core/DriveMetaDataSDK.ts
475
- function createId2(prefix) {
476
- return `${prefix}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
1059
+ // src/browser/core/session.ts
1060
+ var SESSION_STORAGE_KEY = "sessionData";
1061
+ function getUrlParam2(name) {
1062
+ const href = getBrowserWindow()?.location?.href;
1063
+ if (!href) return null;
1064
+ try {
1065
+ return new URL(href).searchParams.get(name);
1066
+ } catch {
1067
+ return null;
1068
+ }
477
1069
  }
1070
+ function readStoredSession(persistence) {
1071
+ const rawSession = persistence.getItem(SESSION_STORAGE_KEY);
1072
+ if (!rawSession) return void 0;
1073
+ try {
1074
+ const parsed = JSON.parse(rawSession);
1075
+ if (isUuid(parsed.sessionId) && typeof parsed.timestamp === "number") {
1076
+ return {
1077
+ sessionId: parsed.sessionId,
1078
+ timestamp: parsed.timestamp
1079
+ };
1080
+ }
1081
+ } catch {
1082
+ persistence.removeItem(SESSION_STORAGE_KEY);
1083
+ }
1084
+ return void 0;
1085
+ }
1086
+ function writeStoredSession(persistence, sessionId, timestamp) {
1087
+ persistence.setItem(SESSION_STORAGE_KEY, JSON.stringify({ sessionId, timestamp }));
1088
+ }
1089
+ function createSessionManager(persistence, idleTimeoutSeconds = 30 * 60) {
1090
+ function resolveSessionId() {
1091
+ const now = Date.now();
1092
+ const urlSessionId = getUrlParam2("sid") ?? getUrlParam2("session_id");
1093
+ if (isUuid(urlSessionId)) {
1094
+ writeStoredSession(persistence, urlSessionId, now);
1095
+ return urlSessionId;
1096
+ }
1097
+ const storedSession = readStoredSession(persistence);
1098
+ if (storedSession && now - storedSession.timestamp <= idleTimeoutSeconds * 1e3) {
1099
+ writeStoredSession(persistence, storedSession.sessionId, now);
1100
+ return storedSession.sessionId;
1101
+ }
1102
+ const sessionId = createUuid();
1103
+ writeStoredSession(persistence, sessionId, now);
1104
+ return sessionId;
1105
+ }
1106
+ return {
1107
+ getSessionId() {
1108
+ return resolveSessionId();
1109
+ },
1110
+ reset() {
1111
+ const sessionId = createUuid();
1112
+ writeStoredSession(persistence, sessionId, Date.now());
1113
+ return sessionId;
1114
+ }
1115
+ };
1116
+ }
1117
+
1118
+ // src/browser/core/DriveMetaDataSDK.ts
478
1119
  function endpointFromConfig(config) {
479
1120
  const host = config.apiHost ?? "https://sdk.drivemetadata.com/v2";
480
1121
  return `${host.replace(/\/$/, "")}/data-collector`;
@@ -485,14 +1126,6 @@ function requireConfigString(value, field) {
485
1126
  }
486
1127
  return value;
487
1128
  }
488
- function getBrowserStorage() {
489
- const browserWindow = getBrowserWindow();
490
- try {
491
- return browserWindow?.localStorage;
492
- } catch {
493
- return void 0;
494
- }
495
- }
496
1129
  var DriveMetaDataSDK = class {
497
1130
  constructor(config) {
498
1131
  this.initialized = true;
@@ -505,30 +1138,35 @@ var DriveMetaDataSDK = class {
505
1138
  requireConfigString(config.writeKey || config.token, "writeKey or token");
506
1139
  this.config = config;
507
1140
  this.endpoint = endpointFromConfig(config);
508
- const storage = getBrowserStorage();
1141
+ this.persistence = createBrowserPersistence(config.persistence);
1142
+ this.session = createSessionManager(this.persistence, config.sessionIdleTimeoutSeconds);
509
1143
  const deliveryConfig = {
510
- endpoint: this.endpoint
1144
+ endpoint: this.endpoint,
1145
+ storage: this.persistence
511
1146
  };
512
1147
  if (config.delivery?.maxQueueSize !== void 0) deliveryConfig.maxQueueSize = config.delivery.maxQueueSize;
513
1148
  if (config.delivery?.queueTtlMs !== void 0) deliveryConfig.queueTtlMs = config.delivery.queueTtlMs;
514
1149
  if (config.delivery?.retryDelayMs !== void 0) deliveryConfig.retryDelayMs = config.delivery.retryDelayMs;
515
1150
  if (config.delivery?.maxRetryDelayMs !== void 0) deliveryConfig.maxRetryDelayMs = config.delivery.maxRetryDelayMs;
516
1151
  if (config.delivery?.maxPayloadBytes !== void 0) deliveryConfig.maxPayloadBytes = config.delivery.maxPayloadBytes;
1152
+ if (config.delivery?.payloadSizePolicy !== void 0) deliveryConfig.payloadSizePolicy = config.delivery.payloadSizePolicy;
1153
+ if (config.delivery?.payloadTruncateStringLength !== void 0) {
1154
+ deliveryConfig.payloadTruncateStringLength = config.delivery.payloadTruncateStringLength;
1155
+ }
1156
+ if (config.delivery?.useBeacon !== void 0) deliveryConfig.useBeacon = config.delivery.useBeacon;
517
1157
  if (config.delivery?.batchSize !== void 0) deliveryConfig.batchSize = config.delivery.batchSize;
518
1158
  if (config.onDrop !== void 0) deliveryConfig.onDrop = config.onDrop;
519
1159
  if (config.onError !== void 0) deliveryConfig.onError = config.onError;
520
- this.delivery = createDeliveryManager(storage ? {
521
- ...deliveryConfig,
522
- storage
523
- } : deliveryConfig);
1160
+ this.delivery = createDeliveryManager(deliveryConfig);
524
1161
  this.initialRetryDelayMs = config.delivery?.retryDelayMs ?? 1e3;
525
1162
  this.retryDelayMs = this.initialRetryDelayMs;
526
1163
  this.maxRetryDelayMs = config.delivery?.maxRetryDelayMs ?? 3e4;
527
1164
  this.writeKey = config.writeKey || config.token || "";
528
- this.identity = { anonymousId: createId2("anon") };
529
- this.sessionId = createId2("session");
1165
+ this.identity = { anonymousId: resolveAnonymousId(this.persistence) };
1166
+ captureAttributionFromUrl(this.persistence);
530
1167
  this.consentState = normalizeConsent(config.gdprConsent ?? config.consent);
531
1168
  this.gdprConsent = this.consentState.analytics;
1169
+ this.installAutocapture();
532
1170
  if (!config.delivery?.disableLifecycleFlush) {
533
1171
  this.installLifecycleFlush();
534
1172
  }
@@ -568,7 +1206,8 @@ var DriveMetaDataSDK = class {
568
1206
  }
569
1207
  }
570
1208
  reset() {
571
- this.identity = { anonymousId: createId2("anon") };
1209
+ this.identity = { anonymousId: resetAnonymousId(this.persistence) };
1210
+ this.session.reset();
572
1211
  this.queue = [];
573
1212
  this.offline = false;
574
1213
  if (this.retryTimer !== void 0) {
@@ -577,8 +1216,20 @@ var DriveMetaDataSDK = class {
577
1216
  }
578
1217
  this.lifecycleCleanup?.();
579
1218
  this.lifecycleCleanup = void 0;
1219
+ this.autocaptureCleanup?.();
1220
+ this.autocaptureCleanup = void 0;
580
1221
  this.delivery.clearQueue("manual_clear");
581
1222
  }
1223
+ disposeForTests() {
1224
+ if (this.retryTimer !== void 0) {
1225
+ clearTimeout(this.retryTimer);
1226
+ this.retryTimer = void 0;
1227
+ }
1228
+ this.lifecycleCleanup?.();
1229
+ this.lifecycleCleanup = void 0;
1230
+ this.autocaptureCleanup?.();
1231
+ this.autocaptureCleanup = void 0;
1232
+ }
582
1233
  setConsent(consent2) {
583
1234
  this.consentState = typeof consent2 === "object" ? mergeConsent(this.consentState, consent2) : normalizeConsent(consent2);
584
1235
  this.gdprConsent = this.consentState.analytics;
@@ -593,6 +1244,7 @@ var DriveMetaDataSDK = class {
593
1244
  initialized: this.initialized,
594
1245
  consent: this.gdprConsent,
595
1246
  consentPurposes: this.consentState,
1247
+ persistence: this.persistence.getHealth(),
596
1248
  queueSize: deliveryDiagnostics.queued,
597
1249
  offline: this.offline || deliveryDiagnostics.queued > 0,
598
1250
  droppedEvents: this.droppedEvents + deliveryDiagnostics.dropped.length,
@@ -607,7 +1259,9 @@ var DriveMetaDataSDK = class {
607
1259
  void this.delivery.send(payload).then(() => {
608
1260
  const diagnostics = this.delivery.getDiagnostics();
609
1261
  this.offline = diagnostics.queued > 0;
610
- this.lastError = diagnostics.lastError;
1262
+ if (diagnostics.lastError !== void 0) {
1263
+ this.lastError = diagnostics.lastError;
1264
+ }
611
1265
  if (diagnostics.queued > 0) {
612
1266
  this.scheduleRetryFlush();
613
1267
  }
@@ -639,6 +1293,28 @@ var DriveMetaDataSDK = class {
639
1293
  }
640
1294
  };
641
1295
  }
1296
+ installAutocapture() {
1297
+ const browserWindow = getBrowserWindow();
1298
+ if (!browserWindow) return;
1299
+ if (this.config.autocapture === false) return;
1300
+ const capturePageview = this.config.capturePageview !== false;
1301
+ const capturePageleave = this.config.capturePageleave !== false;
1302
+ const controller = installAutocapture({
1303
+ browserWindow,
1304
+ capturePageview,
1305
+ capturePageleave,
1306
+ onPageView: () => {
1307
+ this.page();
1308
+ },
1309
+ onPageLeave: () => {
1310
+ this.trackEvent("page_leave", { url: browserWindow.location.href });
1311
+ },
1312
+ onRouteChange: () => {
1313
+ captureAttributionFromUrl(this.persistence);
1314
+ }
1315
+ });
1316
+ this.autocaptureCleanup = controller.cleanup;
1317
+ }
642
1318
  scheduleRetryFlush() {
643
1319
  if (this.retryTimer !== void 0) return;
644
1320
  const delay = Math.min(this.retryDelayMs, this.maxRetryDelayMs);
@@ -664,7 +1340,7 @@ var DriveMetaDataSDK = class {
664
1340
  type,
665
1341
  event,
666
1342
  properties: sanitizeProperties(properties),
667
- messageId: options.messageId ?? createId2("msg"),
1343
+ messageId: ensureUuid(options.messageId),
668
1344
  timestamp: options.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
669
1345
  context: options.context ?? {},
670
1346
  anonymousId: this.identity.anonymousId,
@@ -673,7 +1349,7 @@ var DriveMetaDataSDK = class {
673
1349
  appId: this.config.appId,
674
1350
  writeKey: this.writeKey,
675
1351
  consent: this.consentState,
676
- sessionId: this.sessionId
1352
+ sessionId: this.session.getSessionId()
677
1353
  };
678
1354
  if (this.identity.userId !== void 0) prepared.userId = this.identity.userId;
679
1355
  if (this.identity.groupId !== void 0) prepared.groupId = this.identity.groupId;
@@ -697,7 +1373,17 @@ var DriveMetaDataSDK = class {
697
1373
  if (!validation.ok) {
698
1374
  this.lastError = `DMD SDK schema warning: ${validation.errors.join(", ")}`;
699
1375
  }
700
- this.sendEvent(this.toCollectorPayload(prepared));
1376
+ const collectorPayload = this.toCollectorPayload(prepared);
1377
+ const backendValidation = validateBackendCollectorPayload(collectorPayload);
1378
+ if (!backendValidation.ok && this.config.schemaValidation === "strict") {
1379
+ this.lastError = `DMD SDK backend schema warning: ${backendValidation.errors.join(", ")}`;
1380
+ this.recordDrop(type, "invalid_payload", event);
1381
+ return;
1382
+ }
1383
+ if (!backendValidation.ok) {
1384
+ this.lastError = `DMD SDK backend schema warning: ${backendValidation.errors.join(", ")}`;
1385
+ }
1386
+ this.sendEvent(collectorPayload);
701
1387
  }
702
1388
  toCollectorPayload(prepared) {
703
1389
  const browserWindow = getBrowserWindow();
@@ -713,14 +1399,14 @@ var DriveMetaDataSDK = class {
713
1399
  ua: browserWindow?.navigator?.userAgent,
714
1400
  appId: prepared.appId,
715
1401
  pageUrl: browserWindow?.location?.href,
716
- eventData: {
1402
+ eventData: mergeStoredAttribution({
717
1403
  ...prepared.properties,
718
1404
  anonymousId: prepared.anonymousId,
719
1405
  sessionId: prepared.sessionId,
720
1406
  timestamp: prepared.timestamp,
721
1407
  requestSentAt: prepared.timestamp,
722
1408
  requestReceivedAt: prepared.timestamp
723
- }
1409
+ }, this.persistence)
724
1410
  });
725
1411
  }
726
1412
  recordDrop(type, reason, event) {
@@ -796,7 +1482,7 @@ function initDmdSDK(config) {
796
1482
  return publicSingleton;
797
1483
  }
798
1484
  try {
799
- const instance = new DriveMetaDataSDK(config);
1485
+ const instance = new DriveMetaDataSDK(normalizeBrowserConfig(config));
800
1486
  return setSingleton(instance);
801
1487
  } catch (error) {
802
1488
  lastError = error instanceof Error ? error.message : String(error);
@@ -890,7 +1576,7 @@ function getDmdHealth() {
890
1576
  return health;
891
1577
  }
892
1578
  function resetDmdSDKForTests() {
893
- singleton?.reset();
1579
+ singleton?.disposeForTests();
894
1580
  singleton = void 0;
895
1581
  publicSingleton = void 0;
896
1582
  droppedEvents = 0;