@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.
- package/dist/angular/index.cjs +780 -94
- package/dist/angular/index.cjs.map +1 -1
- package/dist/angular/index.d.cts +2 -2
- package/dist/angular/index.d.ts +2 -2
- package/dist/angular/index.js +780 -94
- package/dist/angular/index.js.map +1 -1
- package/dist/browser/index.cjs +781 -95
- package/dist/browser/index.cjs.map +1 -1
- package/dist/browser/index.d.cts +2 -2
- package/dist/browser/index.d.ts +2 -2
- package/dist/browser/index.js +781 -95
- package/dist/browser/index.js.map +1 -1
- package/dist/next/index.cjs +780 -94
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +1 -1
- package/dist/next/index.d.ts +1 -1
- package/dist/next/index.js +780 -94
- package/dist/next/index.js.map +1 -1
- package/dist/node/index.cjs +81 -9
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.js +81 -9
- package/dist/node/index.js.map +1 -1
- package/dist/react/index.cjs +780 -94
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +780 -94
- package/dist/react/index.js.map +1 -1
- package/dist/{types--V8TVIqT.d.cts → types-mgbdL1V7.d.cts} +17 -4
- package/dist/{types--V8TVIqT.d.ts → types-mgbdL1V7.d.ts} +17 -4
- package/docs/architecture.md +109 -0
- package/docs/index.md +1 -0
- package/docs/integration.md +26 -0
- package/docs/npm-browser-sdk.md +4 -0
- package/package.json +3 -2
package/dist/browser/index.js
CHANGED
|
@@ -1,9 +1,59 @@
|
|
|
1
|
+
// src/browser/core/browser-config.ts
|
|
2
|
+
function setIfDefined(target, key, value) {
|
|
3
|
+
if (value !== void 0) {
|
|
4
|
+
target[key] = value;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
function normalizeBrowserConfig(input) {
|
|
8
|
+
const normalized = {
|
|
9
|
+
...input,
|
|
10
|
+
clientId: input.clientId ?? "",
|
|
11
|
+
workspaceId: input.workspaceId ?? "",
|
|
12
|
+
appId: input.appId ?? ""
|
|
13
|
+
};
|
|
14
|
+
setIfDefined(normalized, "apiHost", input.apiHost);
|
|
15
|
+
setIfDefined(normalized, "capturePageview", input.capturePageview);
|
|
16
|
+
setIfDefined(normalized, "capturePageleave", input.capturePageleave);
|
|
17
|
+
setIfDefined(normalized, "captureDeadClicks", input.captureDeadClicks);
|
|
18
|
+
setIfDefined(normalized, "crossSubdomainCookie", input.crossSubdomainCookie);
|
|
19
|
+
setIfDefined(normalized, "sessionIdleTimeoutSeconds", input.sessionIdleTimeoutSeconds);
|
|
20
|
+
setIfDefined(normalized, "schemaValidation", input.schemaValidation);
|
|
21
|
+
setIfDefined(normalized, "beforeSend", input.beforeSend);
|
|
22
|
+
setIfDefined(normalized, "persistence", input.disablePersistence === true ? "none" : input.persistence);
|
|
23
|
+
return normalized;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/core/uuid.ts
|
|
27
|
+
function createUuid() {
|
|
28
|
+
const cryptoApi = globalThis.crypto;
|
|
29
|
+
if (typeof cryptoApi?.randomUUID === "function") {
|
|
30
|
+
return cryptoApi.randomUUID();
|
|
31
|
+
}
|
|
32
|
+
const bytes = new Uint8Array(16);
|
|
33
|
+
if (typeof cryptoApi?.getRandomValues === "function") {
|
|
34
|
+
cryptoApi.getRandomValues(bytes);
|
|
35
|
+
} else {
|
|
36
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
37
|
+
bytes[index] = Math.floor(Math.random() * 256);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
bytes[6] = (bytes[6] ?? 0) & 15 | 64;
|
|
41
|
+
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
42
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
43
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
44
|
+
}
|
|
45
|
+
function isUuid(value) {
|
|
46
|
+
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);
|
|
47
|
+
}
|
|
48
|
+
function ensureUuid(value) {
|
|
49
|
+
return isUuid(value) ? value : createUuid();
|
|
50
|
+
}
|
|
51
|
+
|
|
1
52
|
// src/core/backend-payload.ts
|
|
2
53
|
function formatUtcTimestamp(value) {
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
return date.toISOString().replace("T", " ").slice(0, 19);
|
|
54
|
+
const date = typeof value === "string" || typeof value === "number" || value instanceof Date ? new Date(value) : /* @__PURE__ */ new Date();
|
|
55
|
+
const safeDate = Number.isNaN(date.getTime()) ? /* @__PURE__ */ new Date() : date;
|
|
56
|
+
return safeDate.toISOString().replace("T", " ").slice(0, 19);
|
|
7
57
|
}
|
|
8
58
|
function cleanObject(value) {
|
|
9
59
|
if (Array.isArray(value)) {
|
|
@@ -34,15 +84,19 @@ function createBackendCollectorPayload(input) {
|
|
|
34
84
|
};
|
|
35
85
|
const metaData = {
|
|
36
86
|
...normalizedEventData,
|
|
37
|
-
requestId: input.requestId,
|
|
87
|
+
requestId: ensureUuid(typeof input.requestId === "string" ? input.requestId : void 0),
|
|
38
88
|
timestamp,
|
|
39
89
|
eventType: input.eventType,
|
|
40
90
|
requestFrom: input.requestFrom ?? "3",
|
|
41
91
|
clientId: input.clientId,
|
|
42
92
|
workspaceId: input.workspaceId,
|
|
43
93
|
token: input.token,
|
|
44
|
-
anonymousId:
|
|
45
|
-
|
|
94
|
+
anonymousId: ensureUuid(
|
|
95
|
+
typeof eventData.anonymousId === "string" ? eventData.anonymousId : typeof input.anonymousId === "string" ? input.anonymousId : void 0
|
|
96
|
+
),
|
|
97
|
+
sessionId: ensureUuid(
|
|
98
|
+
typeof eventData.sessionId === "string" ? eventData.sessionId : typeof input.sessionId === "string" ? input.sessionId : void 0
|
|
99
|
+
),
|
|
46
100
|
ua: input.ua,
|
|
47
101
|
appDetails: { app_id: input.appId },
|
|
48
102
|
page: { ...page2, url: page2.url ?? input.pageUrl },
|
|
@@ -53,12 +107,45 @@ function createBackendCollectorPayload(input) {
|
|
|
53
107
|
return cleanObject(payload);
|
|
54
108
|
}
|
|
55
109
|
|
|
56
|
-
// src/core/
|
|
57
|
-
|
|
58
|
-
|
|
110
|
+
// src/core/backend-schema.ts
|
|
111
|
+
var requiredMetaDataFields = [
|
|
112
|
+
"requestId",
|
|
113
|
+
"timestamp",
|
|
114
|
+
"eventType",
|
|
115
|
+
"requestFrom",
|
|
116
|
+
"clientId",
|
|
117
|
+
"workspaceId",
|
|
118
|
+
"anonymousId",
|
|
119
|
+
"sessionId"
|
|
120
|
+
];
|
|
121
|
+
function isPresent(value) {
|
|
122
|
+
return value !== null && value !== void 0 && value !== "";
|
|
123
|
+
}
|
|
124
|
+
function validateBackendCollectorPayload(payload) {
|
|
125
|
+
const errors = [];
|
|
126
|
+
const metaData = payload.metaData;
|
|
127
|
+
if (!metaData || typeof metaData !== "object" || Array.isArray(metaData)) {
|
|
128
|
+
return {
|
|
129
|
+
ok: false,
|
|
130
|
+
errors: ["metaData is required"]
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const metadataRecord = metaData;
|
|
134
|
+
for (const field of requiredMetaDataFields) {
|
|
135
|
+
if (!isPresent(metadataRecord[field])) {
|
|
136
|
+
errors.push(`metaData.${field} is required`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (isPresent(metadataRecord.eventType) && typeof metadataRecord.eventType !== "string") {
|
|
140
|
+
errors.push("metaData.eventType must be a string");
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
ok: errors.length === 0,
|
|
144
|
+
errors
|
|
145
|
+
};
|
|
59
146
|
}
|
|
60
147
|
|
|
61
|
-
// src/
|
|
148
|
+
// src/core/consent.ts
|
|
62
149
|
var purposes = [
|
|
63
150
|
"analytics",
|
|
64
151
|
"advertising",
|
|
@@ -98,6 +185,335 @@ function canCollectPurpose(consent2, purpose) {
|
|
|
98
185
|
return consent2[purpose] === "granted";
|
|
99
186
|
}
|
|
100
187
|
|
|
188
|
+
// src/core/event-schema.ts
|
|
189
|
+
var reservedKeys = /* @__PURE__ */ new Set(["messageId", "timestamp", "type", "event", "anonymousId", "userId", "context"]);
|
|
190
|
+
function validateEventEnvelope(envelope, options = {}) {
|
|
191
|
+
if (options.mode === "off") return { ok: true, errors: [] };
|
|
192
|
+
const errors = [];
|
|
193
|
+
if (typeof envelope.type !== "string") errors.push("type must be a string");
|
|
194
|
+
if (envelope.type === "track" && typeof envelope.event !== "string") {
|
|
195
|
+
errors.push("track event must include event name");
|
|
196
|
+
}
|
|
197
|
+
if (typeof envelope.messageId !== "string") errors.push("messageId must be a string");
|
|
198
|
+
if (typeof envelope.timestamp !== "string") errors.push("timestamp must be an ISO string");
|
|
199
|
+
for (const key of Object.keys(envelope.properties ?? {})) {
|
|
200
|
+
if (reservedKeys.has(key)) errors.push(`properties.${key} is reserved`);
|
|
201
|
+
}
|
|
202
|
+
return { ok: errors.length === 0, errors };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/core/environment.ts
|
|
206
|
+
function getBrowserWindow() {
|
|
207
|
+
return typeof window === "undefined" ? void 0 : window;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/core/privacy.ts
|
|
211
|
+
var sensitiveKeys = /* @__PURE__ */ new Set([
|
|
212
|
+
"email",
|
|
213
|
+
"phone",
|
|
214
|
+
"mobile",
|
|
215
|
+
"address",
|
|
216
|
+
"address1",
|
|
217
|
+
"address2",
|
|
218
|
+
"first_name",
|
|
219
|
+
"last_name",
|
|
220
|
+
"name",
|
|
221
|
+
"token",
|
|
222
|
+
"secret",
|
|
223
|
+
"password",
|
|
224
|
+
"session",
|
|
225
|
+
"cookie"
|
|
226
|
+
]);
|
|
227
|
+
function sanitizeValue(value, allow, path = []) {
|
|
228
|
+
if (Array.isArray(value)) {
|
|
229
|
+
return value.map((item) => sanitizeValue(item, allow, path));
|
|
230
|
+
}
|
|
231
|
+
if (value && typeof value === "object") {
|
|
232
|
+
return Object.fromEntries(
|
|
233
|
+
Object.entries(value).filter(([key]) => {
|
|
234
|
+
const lowerKey = key.toLowerCase();
|
|
235
|
+
const lowerPath = path.map((item) => item.toLowerCase());
|
|
236
|
+
const isEcommerceItemName = lowerKey === "name" && lowerPath.includes("ecommerce") && lowerPath.includes("items");
|
|
237
|
+
return isEcommerceItemName || !sensitiveKeys.has(lowerKey) || allow.has(lowerKey);
|
|
238
|
+
}).map(([key, nestedValue]) => [key, sanitizeValue(nestedValue, allow, [...path, key])])
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
return value;
|
|
242
|
+
}
|
|
243
|
+
function sanitizeProperties(input, allowRawKeys = []) {
|
|
244
|
+
const allow = new Set(allowRawKeys.map((key) => key.toLowerCase()));
|
|
245
|
+
return sanitizeValue(input, allow);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/core/attribution.ts
|
|
249
|
+
var dmdUtmKeys = [
|
|
250
|
+
"utm_source",
|
|
251
|
+
"utm_medium",
|
|
252
|
+
"utm_campaign",
|
|
253
|
+
"utm_term",
|
|
254
|
+
"utm_content",
|
|
255
|
+
"utm_id"
|
|
256
|
+
];
|
|
257
|
+
var dmdCampaignKeys = ["campaign_id", "ad_id"];
|
|
258
|
+
var dmdClickIdSources = [
|
|
259
|
+
["gclid", 2],
|
|
260
|
+
["fbclid", 3],
|
|
261
|
+
["ScCid", 1],
|
|
262
|
+
["li_fat_id", 4]
|
|
263
|
+
];
|
|
264
|
+
function cleanAttributionRecord(record) {
|
|
265
|
+
const cleaned = Object.fromEntries(
|
|
266
|
+
Object.entries(record).filter(([, value]) => value !== null && value !== void 0 && value !== "")
|
|
267
|
+
);
|
|
268
|
+
return Object.keys(cleaned).length > 0 ? cleaned : void 0;
|
|
269
|
+
}
|
|
270
|
+
function objectValue(value) {
|
|
271
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
272
|
+
}
|
|
273
|
+
function mergeAttributionRecords(explicitProperties, stored) {
|
|
274
|
+
const attributionData = objectValue(explicitProperties.attributionData);
|
|
275
|
+
const utmParameter = objectValue(explicitProperties.utmParameter);
|
|
276
|
+
return {
|
|
277
|
+
...explicitProperties,
|
|
278
|
+
...stored.attributionData || attributionData ? { attributionData: { ...stored.attributionData ?? {}, ...attributionData ?? {} } } : {},
|
|
279
|
+
...stored.utmParameter || utmParameter ? { utmParameter: { ...stored.utmParameter ?? {}, ...utmParameter ?? {} } } : {}
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// src/browser/core/attribution.ts
|
|
284
|
+
function getCurrentUrl() {
|
|
285
|
+
return getBrowserWindow()?.location?.href;
|
|
286
|
+
}
|
|
287
|
+
function getCookie(name) {
|
|
288
|
+
const cookie = getBrowserWindow()?.document?.cookie;
|
|
289
|
+
if (!cookie) return void 0;
|
|
290
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
291
|
+
const match = new RegExp(`(?:^|; )${escapedName}=([^;]*)`).exec(cookie);
|
|
292
|
+
return match ? decodeURIComponent(match[1] ?? "") : void 0;
|
|
293
|
+
}
|
|
294
|
+
function getParams(url) {
|
|
295
|
+
return new URL(url, "https://placeholder.local").searchParams;
|
|
296
|
+
}
|
|
297
|
+
function setIfPresent(persistence, key, value) {
|
|
298
|
+
if (value !== null && value !== "") {
|
|
299
|
+
persistence.setItem(key, value);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function getStoredString(persistence, key) {
|
|
303
|
+
const value = persistence.getItem(key);
|
|
304
|
+
return value === null || value === "" ? void 0 : value;
|
|
305
|
+
}
|
|
306
|
+
function getStoredNumberOrString(persistence, key) {
|
|
307
|
+
const value = getStoredString(persistence, key);
|
|
308
|
+
if (value === void 0) return void 0;
|
|
309
|
+
const parsed = Number.parseInt(value, 10);
|
|
310
|
+
return Number.isNaN(parsed) ? value : parsed;
|
|
311
|
+
}
|
|
312
|
+
function captureAttributionFromUrl(persistence, url = getCurrentUrl()) {
|
|
313
|
+
if (!url) return;
|
|
314
|
+
const params = getParams(url);
|
|
315
|
+
for (const key of dmdUtmKeys) {
|
|
316
|
+
setIfPresent(persistence, key, params.get(key));
|
|
317
|
+
}
|
|
318
|
+
for (const key of dmdCampaignKeys) {
|
|
319
|
+
setIfPresent(persistence, key, params.get(key));
|
|
320
|
+
}
|
|
321
|
+
for (const [param, sdkPubId] of dmdClickIdSources) {
|
|
322
|
+
const value = params.get(param);
|
|
323
|
+
if (value) {
|
|
324
|
+
persistence.setItem("unique_id", value);
|
|
325
|
+
persistence.setItem("sdk_pub_id", String(sdkPubId));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function getStoredAttributionData(persistence) {
|
|
330
|
+
return cleanAttributionRecord({
|
|
331
|
+
unique_id: getStoredString(persistence, "unique_id"),
|
|
332
|
+
sdk_pub_id: getStoredNumberOrString(persistence, "sdk_pub_id"),
|
|
333
|
+
fbc: getStoredString(persistence, "fbc") ?? getCookie("_fbc"),
|
|
334
|
+
fbp: getStoredString(persistence, "fbp") ?? getCookie("_fbp"),
|
|
335
|
+
campaign_id: getStoredString(persistence, "campaign_id"),
|
|
336
|
+
ad_id: getStoredString(persistence, "ad_id")
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
function getStoredUtmParameter(persistence) {
|
|
340
|
+
return cleanAttributionRecord(Object.fromEntries(dmdUtmKeys.map((key) => [key, getStoredString(persistence, key)])));
|
|
341
|
+
}
|
|
342
|
+
function mergeStoredAttribution(properties, persistence) {
|
|
343
|
+
const stored = {};
|
|
344
|
+
const attributionData = getStoredAttributionData(persistence);
|
|
345
|
+
const utmParameter = getStoredUtmParameter(persistence);
|
|
346
|
+
if (attributionData !== void 0) stored.attributionData = attributionData;
|
|
347
|
+
if (utmParameter !== void 0) stored.utmParameter = utmParameter;
|
|
348
|
+
return mergeAttributionRecords(properties, stored);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/browser/core/autocapture.ts
|
|
352
|
+
function installAutocapture(config) {
|
|
353
|
+
const trackedPageUrls = /* @__PURE__ */ new Set();
|
|
354
|
+
const timers = /* @__PURE__ */ new Set();
|
|
355
|
+
const pageviewDelayMs = config.pageviewDelayMs ?? 500;
|
|
356
|
+
const history = config.browserWindow.history;
|
|
357
|
+
const originalPushState = history?.pushState;
|
|
358
|
+
const originalReplaceState = history?.replaceState;
|
|
359
|
+
function clearTimer(timer) {
|
|
360
|
+
timers.delete(timer);
|
|
361
|
+
clearTimeout(timer);
|
|
362
|
+
}
|
|
363
|
+
function canonicalUrl() {
|
|
364
|
+
return config.browserWindow.location.href;
|
|
365
|
+
}
|
|
366
|
+
function trackCurrentPageview() {
|
|
367
|
+
if (!config.capturePageview) return;
|
|
368
|
+
const url = canonicalUrl();
|
|
369
|
+
if (trackedPageUrls.has(url)) return;
|
|
370
|
+
trackedPageUrls.add(url);
|
|
371
|
+
config.onPageView();
|
|
372
|
+
}
|
|
373
|
+
function schedulePageview(delayMs) {
|
|
374
|
+
if (!config.capturePageview) return;
|
|
375
|
+
const timer = setTimeout(() => {
|
|
376
|
+
timers.delete(timer);
|
|
377
|
+
trackCurrentPageview();
|
|
378
|
+
}, delayMs);
|
|
379
|
+
timers.add(timer);
|
|
380
|
+
}
|
|
381
|
+
function handleRouteChange() {
|
|
382
|
+
config.onRouteChange?.();
|
|
383
|
+
trackCurrentPageview();
|
|
384
|
+
}
|
|
385
|
+
function wrapHistoryMethod(method) {
|
|
386
|
+
if (!history) return;
|
|
387
|
+
const original = history[method];
|
|
388
|
+
if (typeof original !== "function") return;
|
|
389
|
+
history[method] = function wrappedHistoryMethod(...args) {
|
|
390
|
+
const result = original.apply(this, args);
|
|
391
|
+
const timer = setTimeout(() => {
|
|
392
|
+
timers.delete(timer);
|
|
393
|
+
handleRouteChange();
|
|
394
|
+
}, 0);
|
|
395
|
+
timers.add(timer);
|
|
396
|
+
return result;
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function handlePopOrHashChange() {
|
|
400
|
+
handleRouteChange();
|
|
401
|
+
}
|
|
402
|
+
function handlePageLeave() {
|
|
403
|
+
if (config.capturePageleave) {
|
|
404
|
+
config.onPageLeave();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
schedulePageview(pageviewDelayMs);
|
|
408
|
+
wrapHistoryMethod("pushState");
|
|
409
|
+
wrapHistoryMethod("replaceState");
|
|
410
|
+
const canListen = typeof config.browserWindow.addEventListener === "function" && typeof config.browserWindow.removeEventListener === "function";
|
|
411
|
+
if (canListen) {
|
|
412
|
+
config.browserWindow.addEventListener("popstate", handlePopOrHashChange);
|
|
413
|
+
config.browserWindow.addEventListener("hashchange", handlePopOrHashChange);
|
|
414
|
+
config.browserWindow.addEventListener("beforeunload", handlePageLeave);
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
cleanup() {
|
|
418
|
+
for (const timer of Array.from(timers)) {
|
|
419
|
+
clearTimer(timer);
|
|
420
|
+
}
|
|
421
|
+
if (history && originalPushState) history.pushState = originalPushState;
|
|
422
|
+
if (history && originalReplaceState) history.replaceState = originalReplaceState;
|
|
423
|
+
if (canListen) {
|
|
424
|
+
config.browserWindow.removeEventListener("popstate", handlePopOrHashChange);
|
|
425
|
+
config.browserWindow.removeEventListener("hashchange", handlePopOrHashChange);
|
|
426
|
+
config.browserWindow.removeEventListener("beforeunload", handlePageLeave);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/core/payload-size.ts
|
|
433
|
+
var preserveStringKeys = /* @__PURE__ */ new Set([
|
|
434
|
+
"requestId",
|
|
435
|
+
"eventType",
|
|
436
|
+
"requestFrom",
|
|
437
|
+
"clientId",
|
|
438
|
+
"workspaceId",
|
|
439
|
+
"anonymousId",
|
|
440
|
+
"sessionId",
|
|
441
|
+
"token"
|
|
442
|
+
]);
|
|
443
|
+
function payloadByteLength(payload) {
|
|
444
|
+
const serialized = JSON.stringify(payload);
|
|
445
|
+
if (typeof TextEncoder !== "undefined") {
|
|
446
|
+
return new TextEncoder().encode(serialized).length;
|
|
447
|
+
}
|
|
448
|
+
return serialized.length;
|
|
449
|
+
}
|
|
450
|
+
function clonePayload(payload) {
|
|
451
|
+
try {
|
|
452
|
+
return JSON.parse(JSON.stringify(payload));
|
|
453
|
+
} catch {
|
|
454
|
+
return void 0;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function truncateValue(value, truncateStringLength, key) {
|
|
458
|
+
if (typeof value === "string") {
|
|
459
|
+
if (key && preserveStringKeys.has(key)) return value;
|
|
460
|
+
if (value.length <= truncateStringLength) return value;
|
|
461
|
+
return `${value.slice(0, truncateStringLength)}...[TRUNCATED]`;
|
|
462
|
+
}
|
|
463
|
+
if (Array.isArray(value)) {
|
|
464
|
+
return value.map((item) => truncateValue(item, truncateStringLength));
|
|
465
|
+
}
|
|
466
|
+
if (value && typeof value === "object") {
|
|
467
|
+
return Object.fromEntries(
|
|
468
|
+
Object.entries(value).map(([childKey, childValue]) => [
|
|
469
|
+
childKey,
|
|
470
|
+
truncateValue(childValue, truncateStringLength, childKey)
|
|
471
|
+
])
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
return value;
|
|
475
|
+
}
|
|
476
|
+
function markPayloadTruncated(payload) {
|
|
477
|
+
if (payload.metaData && typeof payload.metaData === "object" && !Array.isArray(payload.metaData)) {
|
|
478
|
+
return {
|
|
479
|
+
...payload,
|
|
480
|
+
metaData: {
|
|
481
|
+
...payload.metaData,
|
|
482
|
+
payloadTruncated: true
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
return {
|
|
487
|
+
...payload,
|
|
488
|
+
payloadTruncated: true
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
function truncatePayload(payload, truncateStringLength) {
|
|
492
|
+
const cloned = clonePayload(payload);
|
|
493
|
+
if (!cloned) return void 0;
|
|
494
|
+
return markPayloadTruncated(truncateValue(cloned, truncateStringLength));
|
|
495
|
+
}
|
|
496
|
+
function getPayloadMessageId(payload) {
|
|
497
|
+
const value = payload.metaData?.requestId ?? payload.messageId;
|
|
498
|
+
return value === void 0 ? void 0 : String(value);
|
|
499
|
+
}
|
|
500
|
+
function preparePayloadForSizePolicy(payload, options = {}) {
|
|
501
|
+
const maxPayloadBytes = options.maxPayloadBytes ?? 64e3;
|
|
502
|
+
const payloadSizePolicy = options.payloadSizePolicy ?? "drop";
|
|
503
|
+
const payloadTruncateStringLength = options.payloadTruncateStringLength ?? 1024;
|
|
504
|
+
if (payloadByteLength(payload) <= maxPayloadBytes) {
|
|
505
|
+
return { ok: true, payload, truncated: false };
|
|
506
|
+
}
|
|
507
|
+
if (payloadSizePolicy === "truncate") {
|
|
508
|
+
const truncatedPayload = truncatePayload(payload, payloadTruncateStringLength);
|
|
509
|
+
if (truncatedPayload && payloadByteLength(truncatedPayload) <= maxPayloadBytes) {
|
|
510
|
+
return { ok: true, payload: truncatedPayload, truncated: true };
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const messageId = getPayloadMessageId(payload);
|
|
514
|
+
return messageId === void 0 ? { ok: false, reason: "payload_too_large" } : { ok: false, reason: "payload_too_large", messageId };
|
|
515
|
+
}
|
|
516
|
+
|
|
101
517
|
// src/browser/core/delivery.ts
|
|
102
518
|
function createId(prefix) {
|
|
103
519
|
return `${prefix}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
|
|
@@ -129,7 +545,6 @@ function createDeliveryManager(config) {
|
|
|
129
545
|
const lockTtlMs = config.lockTtlMs ?? 5e3;
|
|
130
546
|
const tabId = config.tabId ?? createId("tab");
|
|
131
547
|
const batchSize = config.batchSize ?? 25;
|
|
132
|
-
const maxPayloadBytes = config.maxPayloadBytes ?? 64e3;
|
|
133
548
|
function recordDrop(event) {
|
|
134
549
|
diagnostics.dropped.push(event);
|
|
135
550
|
config.onDrop?.(event);
|
|
@@ -138,12 +553,22 @@ function createDeliveryManager(config) {
|
|
|
138
553
|
diagnostics.lastError = error.message;
|
|
139
554
|
config.onError?.(error);
|
|
140
555
|
}
|
|
141
|
-
function
|
|
142
|
-
const
|
|
143
|
-
if (
|
|
144
|
-
|
|
556
|
+
function preparePayloadForSend(payload) {
|
|
557
|
+
const sizePolicyOptions = {};
|
|
558
|
+
if (config.maxPayloadBytes !== void 0) sizePolicyOptions.maxPayloadBytes = config.maxPayloadBytes;
|
|
559
|
+
if (config.payloadSizePolicy !== void 0) sizePolicyOptions.payloadSizePolicy = config.payloadSizePolicy;
|
|
560
|
+
if (config.payloadTruncateStringLength !== void 0) {
|
|
561
|
+
sizePolicyOptions.payloadTruncateStringLength = config.payloadTruncateStringLength;
|
|
145
562
|
}
|
|
146
|
-
|
|
563
|
+
const prepared = preparePayloadForSizePolicy(payload, sizePolicyOptions);
|
|
564
|
+
if (prepared.ok) return prepared.payload;
|
|
565
|
+
const diagnostic = {
|
|
566
|
+
reason: prepared.reason,
|
|
567
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
568
|
+
};
|
|
569
|
+
if (prepared.messageId !== void 0) diagnostic.messageId = prepared.messageId;
|
|
570
|
+
recordDrop(diagnostic);
|
|
571
|
+
return void 0;
|
|
147
572
|
}
|
|
148
573
|
function recordStorageUnavailable() {
|
|
149
574
|
recordDrop({
|
|
@@ -233,7 +658,19 @@ function createDeliveryManager(config) {
|
|
|
233
658
|
idempotencyKey: String(payload.idempotencyKey ?? createIdempotencyKey(payload, messageId))
|
|
234
659
|
};
|
|
235
660
|
}
|
|
661
|
+
function tryBeacon(body) {
|
|
662
|
+
if (!config.useBeacon) return false;
|
|
663
|
+
const sendBeacon = globalThis.navigator?.sendBeacon;
|
|
664
|
+
if (typeof sendBeacon !== "function") return false;
|
|
665
|
+
try {
|
|
666
|
+
const blob = new Blob([JSON.stringify(body)], { type: "application/json" });
|
|
667
|
+
return sendBeacon.call(globalThis.navigator, config.endpoint, blob);
|
|
668
|
+
} catch {
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
236
672
|
async function deliver(body) {
|
|
673
|
+
if (tryBeacon(body)) return;
|
|
237
674
|
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
238
675
|
if (typeof fetchImpl !== "function") {
|
|
239
676
|
throw new Error("fetch_unavailable");
|
|
@@ -250,26 +687,22 @@ function createDeliveryManager(config) {
|
|
|
250
687
|
return {
|
|
251
688
|
async send(payload) {
|
|
252
689
|
const body = withEnvelope(payload);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
messageId: String(body.metaData?.requestId ?? body.messageId),
|
|
256
|
-
reason: "payload_too_large",
|
|
257
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
258
|
-
});
|
|
690
|
+
const preparedBody = preparePayloadForSend(body);
|
|
691
|
+
if (!preparedBody) {
|
|
259
692
|
return;
|
|
260
693
|
}
|
|
261
694
|
diagnostics.inFlight += 1;
|
|
262
695
|
try {
|
|
263
|
-
await deliver(
|
|
696
|
+
await deliver(preparedBody);
|
|
264
697
|
} catch (error) {
|
|
265
698
|
const deliveryError = error instanceof Error ? error : new Error(String(error));
|
|
266
699
|
recordError(deliveryError);
|
|
267
700
|
enqueue({
|
|
268
|
-
messageId: String(body
|
|
701
|
+
messageId: String(getPayloadMessageId(body)),
|
|
269
702
|
savedAt: Date.now(),
|
|
270
703
|
attempts: 1,
|
|
271
704
|
lastError: deliveryError.message,
|
|
272
|
-
payload:
|
|
705
|
+
payload: preparedBody
|
|
273
706
|
});
|
|
274
707
|
} finally {
|
|
275
708
|
diagnostics.inFlight -= 1;
|
|
@@ -382,60 +815,268 @@ function createDeliveryManager(config) {
|
|
|
382
815
|
};
|
|
383
816
|
}
|
|
384
817
|
|
|
385
|
-
// src/browser/core/
|
|
386
|
-
var
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
"name",
|
|
396
|
-
"token",
|
|
397
|
-
"secret",
|
|
398
|
-
"password",
|
|
399
|
-
"session",
|
|
400
|
-
"cookie"
|
|
401
|
-
]);
|
|
402
|
-
function sanitizeValue(value, allow) {
|
|
403
|
-
if (Array.isArray(value)) {
|
|
404
|
-
return value.map((item) => sanitizeValue(item, allow));
|
|
818
|
+
// src/browser/core/identity.ts
|
|
819
|
+
var ANONYMOUS_ID_STORAGE_KEY = "dmd_anonymous_id";
|
|
820
|
+
var LEGACY_ANONYMOUS_ID_STORAGE_KEY = "anonymousId";
|
|
821
|
+
function getUrlParam(name) {
|
|
822
|
+
const href = getBrowserWindow()?.location?.href;
|
|
823
|
+
if (!href) return null;
|
|
824
|
+
try {
|
|
825
|
+
return new URL(href).searchParams.get(name);
|
|
826
|
+
} catch {
|
|
827
|
+
return null;
|
|
405
828
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
829
|
+
}
|
|
830
|
+
function resolveAnonymousId(persistence) {
|
|
831
|
+
const urlAnonymousId = getUrlParam("aid");
|
|
832
|
+
if (isUuid(urlAnonymousId)) {
|
|
833
|
+
persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, urlAnonymousId);
|
|
834
|
+
return urlAnonymousId;
|
|
410
835
|
}
|
|
411
|
-
|
|
836
|
+
const storedAnonymousId = persistence.getItem(ANONYMOUS_ID_STORAGE_KEY);
|
|
837
|
+
if (isUuid(storedAnonymousId)) {
|
|
838
|
+
return storedAnonymousId;
|
|
839
|
+
}
|
|
840
|
+
const legacyAnonymousId = persistence.getItem(LEGACY_ANONYMOUS_ID_STORAGE_KEY);
|
|
841
|
+
if (isUuid(legacyAnonymousId)) {
|
|
842
|
+
persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, legacyAnonymousId);
|
|
843
|
+
return legacyAnonymousId;
|
|
844
|
+
}
|
|
845
|
+
const anonymousId = createUuid();
|
|
846
|
+
persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, anonymousId);
|
|
847
|
+
return anonymousId;
|
|
412
848
|
}
|
|
413
|
-
function
|
|
414
|
-
const
|
|
415
|
-
|
|
849
|
+
function resetAnonymousId(persistence) {
|
|
850
|
+
const anonymousId = createUuid();
|
|
851
|
+
persistence.setItem(ANONYMOUS_ID_STORAGE_KEY, anonymousId);
|
|
852
|
+
return anonymousId;
|
|
416
853
|
}
|
|
417
854
|
|
|
418
|
-
// src/browser/core/
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
855
|
+
// src/browser/core/persistence.ts
|
|
856
|
+
function createPersistenceHealth(requested) {
|
|
857
|
+
return {
|
|
858
|
+
requested,
|
|
859
|
+
cookieFallbackUsed: false,
|
|
860
|
+
memoryFallbackUsed: requested === "memory",
|
|
861
|
+
failures: []
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
function recordFailure(health, backend, operation, error) {
|
|
865
|
+
health.failures.push({
|
|
866
|
+
backend,
|
|
867
|
+
operation,
|
|
868
|
+
message: error instanceof Error ? error.message : String(error)
|
|
869
|
+
});
|
|
870
|
+
if (health.failures.length > 25) {
|
|
871
|
+
health.failures.shift();
|
|
426
872
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
873
|
+
}
|
|
874
|
+
function createMemoryPersistence(health = createPersistenceHealth("memory")) {
|
|
875
|
+
const memory = {};
|
|
876
|
+
return {
|
|
877
|
+
getItem(key) {
|
|
878
|
+
return Object.prototype.hasOwnProperty.call(memory, key) ? memory[key] ?? null : null;
|
|
879
|
+
},
|
|
880
|
+
setItem(key, value) {
|
|
881
|
+
memory[key] = value;
|
|
882
|
+
return true;
|
|
883
|
+
},
|
|
884
|
+
removeItem(key) {
|
|
885
|
+
delete memory[key];
|
|
886
|
+
},
|
|
887
|
+
getHealth() {
|
|
888
|
+
return {
|
|
889
|
+
...health,
|
|
890
|
+
failures: health.failures.map((failure) => ({ ...failure }))
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
function getCookie2(name) {
|
|
896
|
+
const browserWindow = getBrowserWindow();
|
|
897
|
+
const cookie = browserWindow?.document?.cookie;
|
|
898
|
+
if (!cookie) return null;
|
|
899
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
900
|
+
const match = new RegExp(`(?:^|; )${escapedName}=([^;]*)`).exec(cookie);
|
|
901
|
+
return match ? decodeURIComponent(match[1] ?? "") : null;
|
|
902
|
+
}
|
|
903
|
+
function setCookie(name, value) {
|
|
904
|
+
const browserWindow = getBrowserWindow();
|
|
905
|
+
if (!browserWindow?.document) return false;
|
|
906
|
+
try {
|
|
907
|
+
const expires = new Date(Date.now() + 365 * 24 * 60 * 60 * 1e3).toUTCString();
|
|
908
|
+
const secure = browserWindow.location?.protocol === "https:" ? "; Secure" : "";
|
|
909
|
+
browserWindow.document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/${secure}; SameSite=Lax`;
|
|
910
|
+
return true;
|
|
911
|
+
} catch {
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
function removeCookie(name) {
|
|
916
|
+
const browserWindow = getBrowserWindow();
|
|
917
|
+
if (!browserWindow?.document) return;
|
|
918
|
+
browserWindow.document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
|
|
919
|
+
}
|
|
920
|
+
function getLocalStorage() {
|
|
921
|
+
const browserWindow = getBrowserWindow();
|
|
922
|
+
try {
|
|
923
|
+
return browserWindow?.localStorage;
|
|
924
|
+
} catch {
|
|
925
|
+
return void 0;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
function getSessionStorage() {
|
|
929
|
+
const browserWindow = getBrowserWindow();
|
|
930
|
+
try {
|
|
931
|
+
return browserWindow?.sessionStorage;
|
|
932
|
+
} catch {
|
|
933
|
+
return void 0;
|
|
431
934
|
}
|
|
432
|
-
|
|
935
|
+
}
|
|
936
|
+
function createBrowserPersistence(mode = "localStorage+cookie") {
|
|
937
|
+
const health = createPersistenceHealth(mode);
|
|
938
|
+
const memory = createMemoryPersistence(health);
|
|
939
|
+
if (mode === "none") {
|
|
940
|
+
return {
|
|
941
|
+
getItem() {
|
|
942
|
+
return null;
|
|
943
|
+
},
|
|
944
|
+
setItem() {
|
|
945
|
+
return false;
|
|
946
|
+
},
|
|
947
|
+
removeItem() {
|
|
948
|
+
},
|
|
949
|
+
getHealth() {
|
|
950
|
+
return {
|
|
951
|
+
...health,
|
|
952
|
+
failures: health.failures.map((failure) => ({ ...failure }))
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
if (mode === "memory") return memory;
|
|
958
|
+
const primary = mode === "sessionStorage" ? getSessionStorage() : getLocalStorage();
|
|
959
|
+
const useCookie = mode === "cookie" || mode === "localStorage+cookie" || !primary;
|
|
960
|
+
return {
|
|
961
|
+
getItem(key) {
|
|
962
|
+
try {
|
|
963
|
+
const stored = primary?.getItem(key);
|
|
964
|
+
if (stored) return stored;
|
|
965
|
+
} catch (error) {
|
|
966
|
+
recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "get", error);
|
|
967
|
+
}
|
|
968
|
+
if (useCookie) {
|
|
969
|
+
const cookie = getCookie2(key);
|
|
970
|
+
if (cookie) {
|
|
971
|
+
health.cookieFallbackUsed = true;
|
|
972
|
+
return cookie;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
const value = memory.getItem(key);
|
|
976
|
+
if (value !== null) health.memoryFallbackUsed = true;
|
|
977
|
+
return value;
|
|
978
|
+
},
|
|
979
|
+
setItem(key, value) {
|
|
980
|
+
let wrote = false;
|
|
981
|
+
try {
|
|
982
|
+
primary?.setItem(key, value);
|
|
983
|
+
wrote = primary !== void 0;
|
|
984
|
+
} catch (error) {
|
|
985
|
+
recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "set", error);
|
|
986
|
+
wrote = false;
|
|
987
|
+
}
|
|
988
|
+
if (useCookie) {
|
|
989
|
+
const cookieWrote = setCookie(key, value);
|
|
990
|
+
if (cookieWrote) health.cookieFallbackUsed = true;
|
|
991
|
+
wrote = cookieWrote || wrote;
|
|
992
|
+
}
|
|
993
|
+
if (!wrote) {
|
|
994
|
+
health.memoryFallbackUsed = true;
|
|
995
|
+
return memory.setItem(key, value);
|
|
996
|
+
}
|
|
997
|
+
if (mode === "localStorage+cookie") {
|
|
998
|
+
memory.setItem(key, value);
|
|
999
|
+
}
|
|
1000
|
+
return true;
|
|
1001
|
+
},
|
|
1002
|
+
removeItem(key) {
|
|
1003
|
+
try {
|
|
1004
|
+
primary?.removeItem(key);
|
|
1005
|
+
} catch (error) {
|
|
1006
|
+
recordFailure(health, mode === "sessionStorage" ? "sessionStorage" : "localStorage", "remove", error);
|
|
1007
|
+
}
|
|
1008
|
+
if (useCookie) removeCookie(key);
|
|
1009
|
+
memory.removeItem(key);
|
|
1010
|
+
},
|
|
1011
|
+
getHealth() {
|
|
1012
|
+
return {
|
|
1013
|
+
...health,
|
|
1014
|
+
failures: health.failures.map((failure) => ({ ...failure }))
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
};
|
|
433
1018
|
}
|
|
434
1019
|
|
|
435
|
-
// src/browser/core/
|
|
436
|
-
|
|
437
|
-
|
|
1020
|
+
// src/browser/core/session.ts
|
|
1021
|
+
var SESSION_STORAGE_KEY = "sessionData";
|
|
1022
|
+
function getUrlParam2(name) {
|
|
1023
|
+
const href = getBrowserWindow()?.location?.href;
|
|
1024
|
+
if (!href) return null;
|
|
1025
|
+
try {
|
|
1026
|
+
return new URL(href).searchParams.get(name);
|
|
1027
|
+
} catch {
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
438
1030
|
}
|
|
1031
|
+
function readStoredSession(persistence) {
|
|
1032
|
+
const rawSession = persistence.getItem(SESSION_STORAGE_KEY);
|
|
1033
|
+
if (!rawSession) return void 0;
|
|
1034
|
+
try {
|
|
1035
|
+
const parsed = JSON.parse(rawSession);
|
|
1036
|
+
if (isUuid(parsed.sessionId) && typeof parsed.timestamp === "number") {
|
|
1037
|
+
return {
|
|
1038
|
+
sessionId: parsed.sessionId,
|
|
1039
|
+
timestamp: parsed.timestamp
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
} catch {
|
|
1043
|
+
persistence.removeItem(SESSION_STORAGE_KEY);
|
|
1044
|
+
}
|
|
1045
|
+
return void 0;
|
|
1046
|
+
}
|
|
1047
|
+
function writeStoredSession(persistence, sessionId, timestamp) {
|
|
1048
|
+
persistence.setItem(SESSION_STORAGE_KEY, JSON.stringify({ sessionId, timestamp }));
|
|
1049
|
+
}
|
|
1050
|
+
function createSessionManager(persistence, idleTimeoutSeconds = 30 * 60) {
|
|
1051
|
+
function resolveSessionId() {
|
|
1052
|
+
const now = Date.now();
|
|
1053
|
+
const urlSessionId = getUrlParam2("sid") ?? getUrlParam2("session_id");
|
|
1054
|
+
if (isUuid(urlSessionId)) {
|
|
1055
|
+
writeStoredSession(persistence, urlSessionId, now);
|
|
1056
|
+
return urlSessionId;
|
|
1057
|
+
}
|
|
1058
|
+
const storedSession = readStoredSession(persistence);
|
|
1059
|
+
if (storedSession && now - storedSession.timestamp <= idleTimeoutSeconds * 1e3) {
|
|
1060
|
+
writeStoredSession(persistence, storedSession.sessionId, now);
|
|
1061
|
+
return storedSession.sessionId;
|
|
1062
|
+
}
|
|
1063
|
+
const sessionId = createUuid();
|
|
1064
|
+
writeStoredSession(persistence, sessionId, now);
|
|
1065
|
+
return sessionId;
|
|
1066
|
+
}
|
|
1067
|
+
return {
|
|
1068
|
+
getSessionId() {
|
|
1069
|
+
return resolveSessionId();
|
|
1070
|
+
},
|
|
1071
|
+
reset() {
|
|
1072
|
+
const sessionId = createUuid();
|
|
1073
|
+
writeStoredSession(persistence, sessionId, Date.now());
|
|
1074
|
+
return sessionId;
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// src/browser/core/DriveMetaDataSDK.ts
|
|
439
1080
|
function endpointFromConfig(config) {
|
|
440
1081
|
const host = config.apiHost ?? "https://sdk.drivemetadata.com/v2";
|
|
441
1082
|
return `${host.replace(/\/$/, "")}/data-collector`;
|
|
@@ -446,14 +1087,6 @@ function requireConfigString(value, field) {
|
|
|
446
1087
|
}
|
|
447
1088
|
return value;
|
|
448
1089
|
}
|
|
449
|
-
function getBrowserStorage() {
|
|
450
|
-
const browserWindow = getBrowserWindow();
|
|
451
|
-
try {
|
|
452
|
-
return browserWindow?.localStorage;
|
|
453
|
-
} catch {
|
|
454
|
-
return void 0;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
1090
|
var DriveMetaDataSDK = class {
|
|
458
1091
|
constructor(config) {
|
|
459
1092
|
this.initialized = true;
|
|
@@ -466,30 +1099,35 @@ var DriveMetaDataSDK = class {
|
|
|
466
1099
|
requireConfigString(config.writeKey || config.token, "writeKey or token");
|
|
467
1100
|
this.config = config;
|
|
468
1101
|
this.endpoint = endpointFromConfig(config);
|
|
469
|
-
|
|
1102
|
+
this.persistence = createBrowserPersistence(config.persistence);
|
|
1103
|
+
this.session = createSessionManager(this.persistence, config.sessionIdleTimeoutSeconds);
|
|
470
1104
|
const deliveryConfig = {
|
|
471
|
-
endpoint: this.endpoint
|
|
1105
|
+
endpoint: this.endpoint,
|
|
1106
|
+
storage: this.persistence
|
|
472
1107
|
};
|
|
473
1108
|
if (config.delivery?.maxQueueSize !== void 0) deliveryConfig.maxQueueSize = config.delivery.maxQueueSize;
|
|
474
1109
|
if (config.delivery?.queueTtlMs !== void 0) deliveryConfig.queueTtlMs = config.delivery.queueTtlMs;
|
|
475
1110
|
if (config.delivery?.retryDelayMs !== void 0) deliveryConfig.retryDelayMs = config.delivery.retryDelayMs;
|
|
476
1111
|
if (config.delivery?.maxRetryDelayMs !== void 0) deliveryConfig.maxRetryDelayMs = config.delivery.maxRetryDelayMs;
|
|
477
1112
|
if (config.delivery?.maxPayloadBytes !== void 0) deliveryConfig.maxPayloadBytes = config.delivery.maxPayloadBytes;
|
|
1113
|
+
if (config.delivery?.payloadSizePolicy !== void 0) deliveryConfig.payloadSizePolicy = config.delivery.payloadSizePolicy;
|
|
1114
|
+
if (config.delivery?.payloadTruncateStringLength !== void 0) {
|
|
1115
|
+
deliveryConfig.payloadTruncateStringLength = config.delivery.payloadTruncateStringLength;
|
|
1116
|
+
}
|
|
1117
|
+
if (config.delivery?.useBeacon !== void 0) deliveryConfig.useBeacon = config.delivery.useBeacon;
|
|
478
1118
|
if (config.delivery?.batchSize !== void 0) deliveryConfig.batchSize = config.delivery.batchSize;
|
|
479
1119
|
if (config.onDrop !== void 0) deliveryConfig.onDrop = config.onDrop;
|
|
480
1120
|
if (config.onError !== void 0) deliveryConfig.onError = config.onError;
|
|
481
|
-
this.delivery = createDeliveryManager(
|
|
482
|
-
...deliveryConfig,
|
|
483
|
-
storage
|
|
484
|
-
} : deliveryConfig);
|
|
1121
|
+
this.delivery = createDeliveryManager(deliveryConfig);
|
|
485
1122
|
this.initialRetryDelayMs = config.delivery?.retryDelayMs ?? 1e3;
|
|
486
1123
|
this.retryDelayMs = this.initialRetryDelayMs;
|
|
487
1124
|
this.maxRetryDelayMs = config.delivery?.maxRetryDelayMs ?? 3e4;
|
|
488
1125
|
this.writeKey = config.writeKey || config.token || "";
|
|
489
|
-
this.identity = { anonymousId:
|
|
490
|
-
this.
|
|
1126
|
+
this.identity = { anonymousId: resolveAnonymousId(this.persistence) };
|
|
1127
|
+
captureAttributionFromUrl(this.persistence);
|
|
491
1128
|
this.consentState = normalizeConsent(config.gdprConsent ?? config.consent);
|
|
492
1129
|
this.gdprConsent = this.consentState.analytics;
|
|
1130
|
+
this.installAutocapture();
|
|
493
1131
|
if (!config.delivery?.disableLifecycleFlush) {
|
|
494
1132
|
this.installLifecycleFlush();
|
|
495
1133
|
}
|
|
@@ -529,7 +1167,8 @@ var DriveMetaDataSDK = class {
|
|
|
529
1167
|
}
|
|
530
1168
|
}
|
|
531
1169
|
reset() {
|
|
532
|
-
this.identity = { anonymousId:
|
|
1170
|
+
this.identity = { anonymousId: resetAnonymousId(this.persistence) };
|
|
1171
|
+
this.session.reset();
|
|
533
1172
|
this.queue = [];
|
|
534
1173
|
this.offline = false;
|
|
535
1174
|
if (this.retryTimer !== void 0) {
|
|
@@ -538,8 +1177,20 @@ var DriveMetaDataSDK = class {
|
|
|
538
1177
|
}
|
|
539
1178
|
this.lifecycleCleanup?.();
|
|
540
1179
|
this.lifecycleCleanup = void 0;
|
|
1180
|
+
this.autocaptureCleanup?.();
|
|
1181
|
+
this.autocaptureCleanup = void 0;
|
|
541
1182
|
this.delivery.clearQueue("manual_clear");
|
|
542
1183
|
}
|
|
1184
|
+
disposeForTests() {
|
|
1185
|
+
if (this.retryTimer !== void 0) {
|
|
1186
|
+
clearTimeout(this.retryTimer);
|
|
1187
|
+
this.retryTimer = void 0;
|
|
1188
|
+
}
|
|
1189
|
+
this.lifecycleCleanup?.();
|
|
1190
|
+
this.lifecycleCleanup = void 0;
|
|
1191
|
+
this.autocaptureCleanup?.();
|
|
1192
|
+
this.autocaptureCleanup = void 0;
|
|
1193
|
+
}
|
|
543
1194
|
setConsent(consent2) {
|
|
544
1195
|
this.consentState = typeof consent2 === "object" ? mergeConsent(this.consentState, consent2) : normalizeConsent(consent2);
|
|
545
1196
|
this.gdprConsent = this.consentState.analytics;
|
|
@@ -554,6 +1205,7 @@ var DriveMetaDataSDK = class {
|
|
|
554
1205
|
initialized: this.initialized,
|
|
555
1206
|
consent: this.gdprConsent,
|
|
556
1207
|
consentPurposes: this.consentState,
|
|
1208
|
+
persistence: this.persistence.getHealth(),
|
|
557
1209
|
queueSize: deliveryDiagnostics.queued,
|
|
558
1210
|
offline: this.offline || deliveryDiagnostics.queued > 0,
|
|
559
1211
|
droppedEvents: this.droppedEvents + deliveryDiagnostics.dropped.length,
|
|
@@ -568,7 +1220,9 @@ var DriveMetaDataSDK = class {
|
|
|
568
1220
|
void this.delivery.send(payload).then(() => {
|
|
569
1221
|
const diagnostics = this.delivery.getDiagnostics();
|
|
570
1222
|
this.offline = diagnostics.queued > 0;
|
|
571
|
-
|
|
1223
|
+
if (diagnostics.lastError !== void 0) {
|
|
1224
|
+
this.lastError = diagnostics.lastError;
|
|
1225
|
+
}
|
|
572
1226
|
if (diagnostics.queued > 0) {
|
|
573
1227
|
this.scheduleRetryFlush();
|
|
574
1228
|
}
|
|
@@ -600,6 +1254,28 @@ var DriveMetaDataSDK = class {
|
|
|
600
1254
|
}
|
|
601
1255
|
};
|
|
602
1256
|
}
|
|
1257
|
+
installAutocapture() {
|
|
1258
|
+
const browserWindow = getBrowserWindow();
|
|
1259
|
+
if (!browserWindow) return;
|
|
1260
|
+
if (this.config.autocapture === false) return;
|
|
1261
|
+
const capturePageview = this.config.capturePageview !== false;
|
|
1262
|
+
const capturePageleave = this.config.capturePageleave !== false;
|
|
1263
|
+
const controller = installAutocapture({
|
|
1264
|
+
browserWindow,
|
|
1265
|
+
capturePageview,
|
|
1266
|
+
capturePageleave,
|
|
1267
|
+
onPageView: () => {
|
|
1268
|
+
this.page();
|
|
1269
|
+
},
|
|
1270
|
+
onPageLeave: () => {
|
|
1271
|
+
this.trackEvent("page_leave", { url: browserWindow.location.href });
|
|
1272
|
+
},
|
|
1273
|
+
onRouteChange: () => {
|
|
1274
|
+
captureAttributionFromUrl(this.persistence);
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
this.autocaptureCleanup = controller.cleanup;
|
|
1278
|
+
}
|
|
603
1279
|
scheduleRetryFlush() {
|
|
604
1280
|
if (this.retryTimer !== void 0) return;
|
|
605
1281
|
const delay = Math.min(this.retryDelayMs, this.maxRetryDelayMs);
|
|
@@ -625,7 +1301,7 @@ var DriveMetaDataSDK = class {
|
|
|
625
1301
|
type,
|
|
626
1302
|
event,
|
|
627
1303
|
properties: sanitizeProperties(properties),
|
|
628
|
-
messageId: options.messageId
|
|
1304
|
+
messageId: ensureUuid(options.messageId),
|
|
629
1305
|
timestamp: options.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
630
1306
|
context: options.context ?? {},
|
|
631
1307
|
anonymousId: this.identity.anonymousId,
|
|
@@ -634,7 +1310,7 @@ var DriveMetaDataSDK = class {
|
|
|
634
1310
|
appId: this.config.appId,
|
|
635
1311
|
writeKey: this.writeKey,
|
|
636
1312
|
consent: this.consentState,
|
|
637
|
-
sessionId: this.
|
|
1313
|
+
sessionId: this.session.getSessionId()
|
|
638
1314
|
};
|
|
639
1315
|
if (this.identity.userId !== void 0) prepared.userId = this.identity.userId;
|
|
640
1316
|
if (this.identity.groupId !== void 0) prepared.groupId = this.identity.groupId;
|
|
@@ -658,7 +1334,17 @@ var DriveMetaDataSDK = class {
|
|
|
658
1334
|
if (!validation.ok) {
|
|
659
1335
|
this.lastError = `DMD SDK schema warning: ${validation.errors.join(", ")}`;
|
|
660
1336
|
}
|
|
661
|
-
this.
|
|
1337
|
+
const collectorPayload = this.toCollectorPayload(prepared);
|
|
1338
|
+
const backendValidation = validateBackendCollectorPayload(collectorPayload);
|
|
1339
|
+
if (!backendValidation.ok && this.config.schemaValidation === "strict") {
|
|
1340
|
+
this.lastError = `DMD SDK backend schema warning: ${backendValidation.errors.join(", ")}`;
|
|
1341
|
+
this.recordDrop(type, "invalid_payload", event);
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
if (!backendValidation.ok) {
|
|
1345
|
+
this.lastError = `DMD SDK backend schema warning: ${backendValidation.errors.join(", ")}`;
|
|
1346
|
+
}
|
|
1347
|
+
this.sendEvent(collectorPayload);
|
|
662
1348
|
}
|
|
663
1349
|
toCollectorPayload(prepared) {
|
|
664
1350
|
const browserWindow = getBrowserWindow();
|
|
@@ -674,14 +1360,14 @@ var DriveMetaDataSDK = class {
|
|
|
674
1360
|
ua: browserWindow?.navigator?.userAgent,
|
|
675
1361
|
appId: prepared.appId,
|
|
676
1362
|
pageUrl: browserWindow?.location?.href,
|
|
677
|
-
eventData: {
|
|
1363
|
+
eventData: mergeStoredAttribution({
|
|
678
1364
|
...prepared.properties,
|
|
679
1365
|
anonymousId: prepared.anonymousId,
|
|
680
1366
|
sessionId: prepared.sessionId,
|
|
681
1367
|
timestamp: prepared.timestamp,
|
|
682
1368
|
requestSentAt: prepared.timestamp,
|
|
683
1369
|
requestReceivedAt: prepared.timestamp
|
|
684
|
-
}
|
|
1370
|
+
}, this.persistence)
|
|
685
1371
|
});
|
|
686
1372
|
}
|
|
687
1373
|
recordDrop(type, reason, event) {
|
|
@@ -757,7 +1443,7 @@ function initDmdSDK(config) {
|
|
|
757
1443
|
return publicSingleton;
|
|
758
1444
|
}
|
|
759
1445
|
try {
|
|
760
|
-
const instance = new DriveMetaDataSDK(config);
|
|
1446
|
+
const instance = new DriveMetaDataSDK(normalizeBrowserConfig(config));
|
|
761
1447
|
return setSingleton(instance);
|
|
762
1448
|
} catch (error) {
|
|
763
1449
|
lastError = error instanceof Error ? error.message : String(error);
|
|
@@ -851,7 +1537,7 @@ function getDmdHealth() {
|
|
|
851
1537
|
return health;
|
|
852
1538
|
}
|
|
853
1539
|
function resetDmdSDKForTests() {
|
|
854
|
-
singleton?.
|
|
1540
|
+
singleton?.disposeForTests();
|
|
855
1541
|
singleton = void 0;
|
|
856
1542
|
publicSingleton = void 0;
|
|
857
1543
|
droppedEvents = 0;
|