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