@snovasys/usage-analytics-sdk 1.0.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/README.md +638 -0
- package/dist/clarity.cjs +91 -0
- package/dist/clarity.cjs.map +1 -0
- package/dist/clarity.d.cts +19 -0
- package/dist/clarity.d.ts +19 -0
- package/dist/clarity.js +64 -0
- package/dist/clarity.js.map +1 -0
- package/dist/custom-api.cjs +98 -0
- package/dist/custom-api.cjs.map +1 -0
- package/dist/custom-api.d.cts +20 -0
- package/dist/custom-api.d.ts +20 -0
- package/dist/custom-api.js +71 -0
- package/dist/custom-api.js.map +1 -0
- package/dist/ga.cjs +96 -0
- package/dist/ga.cjs.map +1 -0
- package/dist/ga.d.cts +20 -0
- package/dist/ga.d.ts +20 -0
- package/dist/ga.js +69 -0
- package/dist/ga.js.map +1 -0
- package/dist/index.cjs +729 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +93 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +697 -0
- package/dist/index.js.map +1 -0
- package/dist/listener-CrNaKh1a.d.cts +58 -0
- package/dist/listener-CrNaKh1a.d.ts +58 -0
- package/dist/noop.cjs +43 -0
- package/dist/noop.cjs.map +1 -0
- package/dist/noop.d.cts +12 -0
- package/dist/noop.d.ts +12 -0
- package/dist/noop.js +15 -0
- package/dist/noop.js.map +1 -0
- package/dist/usage-analytics.min.js +2 -0
- package/dist/usage-analytics.min.js.map +1 -0
- package/package.json +59 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
flush: () => flush,
|
|
24
|
+
identify: () => identify,
|
|
25
|
+
init: () => init,
|
|
26
|
+
isInitialized: () => isInitialized,
|
|
27
|
+
reset: () => reset,
|
|
28
|
+
track: () => track
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(src_exports);
|
|
31
|
+
|
|
32
|
+
// src/buffer/pre-init-buffer.ts
|
|
33
|
+
var PreInitBuffer = class {
|
|
34
|
+
constructor(options) {
|
|
35
|
+
this.queue = [];
|
|
36
|
+
this.options = options;
|
|
37
|
+
this.isBrowser = options.isBrowserOverride !== void 0 ? options.isBrowserOverride : typeof window !== "undefined";
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Push a track call. No-op if mode is drop or not browser (SSR).
|
|
41
|
+
*/
|
|
42
|
+
pushTrack(name, properties, timestamp) {
|
|
43
|
+
if (this.options.mode === "drop" || !this.isBrowser) return;
|
|
44
|
+
const item = {
|
|
45
|
+
type: "track",
|
|
46
|
+
name,
|
|
47
|
+
properties,
|
|
48
|
+
timestamp,
|
|
49
|
+
createdAt: Date.now()
|
|
50
|
+
};
|
|
51
|
+
this.push(item);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Push an identify call. No-op if mode is drop or not browser (SSR).
|
|
55
|
+
*/
|
|
56
|
+
pushIdentify(userId, traits) {
|
|
57
|
+
if (this.options.mode === "drop" || !this.isBrowser) return;
|
|
58
|
+
const item = {
|
|
59
|
+
type: "identify",
|
|
60
|
+
userId,
|
|
61
|
+
traits,
|
|
62
|
+
createdAt: Date.now()
|
|
63
|
+
};
|
|
64
|
+
this.push(item);
|
|
65
|
+
}
|
|
66
|
+
push(item) {
|
|
67
|
+
while (this.queue.length >= this.options.maxSize && this.queue.length > 0) {
|
|
68
|
+
this.queue.shift();
|
|
69
|
+
}
|
|
70
|
+
this.queue.push(item);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Replay queued items: all identify first (order), then all track (order).
|
|
74
|
+
* Items older than ttlMs are dropped. Then clear the queue.
|
|
75
|
+
*/
|
|
76
|
+
flush(onTrack, onIdentify) {
|
|
77
|
+
if (!this.isBrowser || this.queue.length === 0) {
|
|
78
|
+
this.queue.length = 0;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
const cutoff = now - this.options.ttlMs;
|
|
83
|
+
const identifyItems = this.queue.filter(
|
|
84
|
+
(item) => item.type === "identify" && item.createdAt >= cutoff
|
|
85
|
+
);
|
|
86
|
+
const trackItems = this.queue.filter(
|
|
87
|
+
(item) => item.type === "track" && item.createdAt >= cutoff
|
|
88
|
+
);
|
|
89
|
+
for (const item of identifyItems) {
|
|
90
|
+
onIdentify(item.userId, item.traits);
|
|
91
|
+
}
|
|
92
|
+
for (const item of trackItems) {
|
|
93
|
+
onTrack({
|
|
94
|
+
name: item.name,
|
|
95
|
+
properties: item.properties,
|
|
96
|
+
timestamp: item.timestamp
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
this.queue.length = 0;
|
|
100
|
+
}
|
|
101
|
+
/** Number of items currently queued (for tests). */
|
|
102
|
+
get length() {
|
|
103
|
+
return this.queue.length;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// src/dispatcher/registry.ts
|
|
108
|
+
var ListenerRegistry = class {
|
|
109
|
+
constructor() {
|
|
110
|
+
this.listeners = [];
|
|
111
|
+
}
|
|
112
|
+
add(id, listener) {
|
|
113
|
+
this.listeners.push({ id, listener });
|
|
114
|
+
}
|
|
115
|
+
getAll() {
|
|
116
|
+
return [...this.listeners];
|
|
117
|
+
}
|
|
118
|
+
clear() {
|
|
119
|
+
this.listeners.length = 0;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// src/enricher/session.ts
|
|
124
|
+
var STORAGE_KEY_PREFIX = "snovasys_usage_analytics_session_";
|
|
125
|
+
function generateId() {
|
|
126
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
127
|
+
return crypto.randomUUID();
|
|
128
|
+
}
|
|
129
|
+
return fallbackUuidV4();
|
|
130
|
+
}
|
|
131
|
+
function fallbackUuidV4() {
|
|
132
|
+
const hex = "0123456789abcdef";
|
|
133
|
+
let result = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
|
|
134
|
+
result = result.replace(/[xy]/g, (c) => {
|
|
135
|
+
const r = Math.random() * 16 | 0;
|
|
136
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
137
|
+
return hex[v];
|
|
138
|
+
});
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
function getOrCreateSessionId(appId, providedSessionId) {
|
|
142
|
+
if (providedSessionId) return providedSessionId;
|
|
143
|
+
const key = `${STORAGE_KEY_PREFIX}${appId}`;
|
|
144
|
+
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
145
|
+
try {
|
|
146
|
+
const existing = sessionStorage.getItem(key);
|
|
147
|
+
if (existing) return existing;
|
|
148
|
+
const newId = generateId();
|
|
149
|
+
sessionStorage.setItem(key, newId);
|
|
150
|
+
return newId;
|
|
151
|
+
} catch {
|
|
152
|
+
return generateId();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return generateId();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/enricher/enricher.ts
|
|
159
|
+
var ENVELOPE_VERSION = 1;
|
|
160
|
+
function createEnricherState(appId, environment, sdkVersion, sessionId) {
|
|
161
|
+
const resolvedSessionId = getOrCreateSessionId(appId, sessionId);
|
|
162
|
+
return {
|
|
163
|
+
appId,
|
|
164
|
+
environment,
|
|
165
|
+
sdkVersion,
|
|
166
|
+
sessionId,
|
|
167
|
+
resolvedSessionId
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function enrich(payload, state) {
|
|
171
|
+
const timestamp = payload.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
172
|
+
const envelope = {
|
|
173
|
+
name: payload.name,
|
|
174
|
+
timestamp,
|
|
175
|
+
eventId: generateId(),
|
|
176
|
+
appId: state.appId,
|
|
177
|
+
environment: state.environment,
|
|
178
|
+
sdkVersion: state.sdkVersion,
|
|
179
|
+
version: ENVELOPE_VERSION
|
|
180
|
+
};
|
|
181
|
+
if (payload.properties != null) envelope.properties = payload.properties;
|
|
182
|
+
if (state.resolvedSessionId) envelope.sessionId = state.resolvedSessionId;
|
|
183
|
+
if (state.userId) envelope.userId = state.userId;
|
|
184
|
+
return envelope;
|
|
185
|
+
}
|
|
186
|
+
function setUserId(state, userId) {
|
|
187
|
+
state.userId = userId;
|
|
188
|
+
}
|
|
189
|
+
function clearUserId(state) {
|
|
190
|
+
state.userId = void 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/dispatcher/dispatcher.ts
|
|
194
|
+
function dispatchTrack(deps, payload) {
|
|
195
|
+
const state = deps.getEnricherState();
|
|
196
|
+
const envelope = enrich(payload, state);
|
|
197
|
+
dispatchEnvelope(deps, envelope, "track");
|
|
198
|
+
}
|
|
199
|
+
function dispatchEnvelope(deps, envelope, kind) {
|
|
200
|
+
const { registry: registry2, onError: onError2 } = deps;
|
|
201
|
+
for (const { id, listener } of registry2.getAll()) {
|
|
202
|
+
try {
|
|
203
|
+
listener.track(envelope);
|
|
204
|
+
} catch (err) {
|
|
205
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
206
|
+
onError2?.(error, { listenerId: id });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function dispatchIdentify(deps, userId, traits) {
|
|
211
|
+
const { registry: registry2, onError: onError2 } = deps;
|
|
212
|
+
for (const { id, listener } of registry2.getAll()) {
|
|
213
|
+
if (typeof listener.identify !== "function") continue;
|
|
214
|
+
try {
|
|
215
|
+
listener.identify(userId, traits);
|
|
216
|
+
} catch (err) {
|
|
217
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
218
|
+
onError2?.(error, { listenerId: id });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/listeners/noop.ts
|
|
224
|
+
function createNoopListener() {
|
|
225
|
+
return {
|
|
226
|
+
init(_config) {
|
|
227
|
+
},
|
|
228
|
+
track(_envelope) {
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
var noopListener = createNoopListener();
|
|
233
|
+
|
|
234
|
+
// src/listeners/clarity.ts
|
|
235
|
+
var CLARITY_SCRIPT_BASE = "https://www.clarity.ms/tag/";
|
|
236
|
+
function isBrowser() {
|
|
237
|
+
return typeof window !== "undefined";
|
|
238
|
+
}
|
|
239
|
+
function loadClarityScript(projectId) {
|
|
240
|
+
return new Promise((resolve) => {
|
|
241
|
+
if (!isBrowser()) {
|
|
242
|
+
resolve();
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (typeof window.clarity === "function") {
|
|
246
|
+
resolve();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const script = document.createElement("script");
|
|
250
|
+
script.async = true;
|
|
251
|
+
script.src = `${CLARITY_SCRIPT_BASE}${encodeURIComponent(projectId)}`;
|
|
252
|
+
script.onload = () => resolve();
|
|
253
|
+
script.onerror = () => resolve();
|
|
254
|
+
document.head.appendChild(script);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function createClarityListener(config) {
|
|
258
|
+
const projectId = config.projectId;
|
|
259
|
+
let ready = false;
|
|
260
|
+
return {
|
|
261
|
+
async init(cfg) {
|
|
262
|
+
try {
|
|
263
|
+
const c = cfg ?? config;
|
|
264
|
+
const id = c.projectId;
|
|
265
|
+
if (!id || !isBrowser()) return;
|
|
266
|
+
await loadClarityScript(id);
|
|
267
|
+
ready = true;
|
|
268
|
+
} catch {
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
track(envelope) {
|
|
272
|
+
try {
|
|
273
|
+
if (!isBrowser() || !ready || typeof window.clarity !== "function") return;
|
|
274
|
+
window.clarity("event", envelope.name);
|
|
275
|
+
if (envelope.properties && typeof window.clarity === "function") {
|
|
276
|
+
for (const [key, value] of Object.entries(envelope.properties)) {
|
|
277
|
+
if (typeof value === "string" || Array.isArray(value)) {
|
|
278
|
+
window.clarity("set", key, value);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
} catch {
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
identify(userId, _traits) {
|
|
286
|
+
try {
|
|
287
|
+
if (!isBrowser() || !ready || typeof window.clarity !== "function") return;
|
|
288
|
+
window.clarity("identify", userId, void 0, void 0, void 0);
|
|
289
|
+
} catch {
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// src/listeners/ga.ts
|
|
296
|
+
var GTAG_SCRIPT_URL = "https://www.googletagmanager.com/gtag/js";
|
|
297
|
+
function isBrowser2() {
|
|
298
|
+
return typeof window !== "undefined";
|
|
299
|
+
}
|
|
300
|
+
function loadGtagScript(measurementId) {
|
|
301
|
+
return new Promise((resolve) => {
|
|
302
|
+
if (!isBrowser2()) {
|
|
303
|
+
resolve();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (window.gtag) {
|
|
307
|
+
window.gtag("config", measurementId);
|
|
308
|
+
resolve();
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
window.dataLayer = window.dataLayer ?? [];
|
|
312
|
+
const gtag = (...args) => {
|
|
313
|
+
window.dataLayer.push(args);
|
|
314
|
+
};
|
|
315
|
+
window.gtag = gtag;
|
|
316
|
+
gtag("js", /* @__PURE__ */ new Date());
|
|
317
|
+
const script = document.createElement("script");
|
|
318
|
+
script.async = true;
|
|
319
|
+
script.src = `${GTAG_SCRIPT_URL}?id=${encodeURIComponent(measurementId)}`;
|
|
320
|
+
script.onload = () => {
|
|
321
|
+
gtag("config", measurementId);
|
|
322
|
+
resolve();
|
|
323
|
+
};
|
|
324
|
+
script.onerror = () => resolve();
|
|
325
|
+
document.head.appendChild(script);
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
function createGAListener(config) {
|
|
329
|
+
const measurementId = config.measurementId;
|
|
330
|
+
let ready = false;
|
|
331
|
+
return {
|
|
332
|
+
async init(cfg) {
|
|
333
|
+
try {
|
|
334
|
+
const c = cfg ?? config;
|
|
335
|
+
const id = c.measurementId;
|
|
336
|
+
if (!id || !isBrowser2()) return;
|
|
337
|
+
await loadGtagScript(id);
|
|
338
|
+
ready = true;
|
|
339
|
+
} catch {
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
track(envelope) {
|
|
343
|
+
try {
|
|
344
|
+
if (!isBrowser2() || !ready || typeof window.gtag !== "function") return;
|
|
345
|
+
const params = envelope.properties ?? {};
|
|
346
|
+
if (envelope.userId) params["user_id"] = envelope.userId;
|
|
347
|
+
window.gtag("event", envelope.name, params);
|
|
348
|
+
} catch {
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
identify(userId, traits) {
|
|
352
|
+
try {
|
|
353
|
+
if (!isBrowser2() || typeof window.gtag !== "function") return;
|
|
354
|
+
window.gtag("set", "user_properties", { user_id: userId, ...traits });
|
|
355
|
+
} catch {
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/listeners/custom-api.ts
|
|
362
|
+
var DEFAULT_BATCH_SIZE = 1;
|
|
363
|
+
var DEFAULT_FLUSH_INTERVAL_MS = 5e3;
|
|
364
|
+
function createCustomApiListener(config) {
|
|
365
|
+
const endpoint = config.endpoint;
|
|
366
|
+
const apiKey = config.apiKey;
|
|
367
|
+
const batchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
368
|
+
const flushIntervalMs = config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
|
|
369
|
+
let batch = [];
|
|
370
|
+
let flushTimer = null;
|
|
371
|
+
function scheduleFlush() {
|
|
372
|
+
if (flushTimer != null) return;
|
|
373
|
+
flushTimer = setTimeout(() => {
|
|
374
|
+
flushTimer = null;
|
|
375
|
+
sendBatch();
|
|
376
|
+
}, flushIntervalMs);
|
|
377
|
+
}
|
|
378
|
+
function sendBatch() {
|
|
379
|
+
if (batch.length === 0) return;
|
|
380
|
+
const toSend = [...batch];
|
|
381
|
+
batch = [];
|
|
382
|
+
const body = JSON.stringify(toSend.length === 1 ? toSend[0] : { events: toSend });
|
|
383
|
+
const headers = {
|
|
384
|
+
"Content-Type": "application/json"
|
|
385
|
+
};
|
|
386
|
+
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
|
|
387
|
+
if (typeof fetch !== "undefined") {
|
|
388
|
+
fetch(endpoint, {
|
|
389
|
+
method: "POST",
|
|
390
|
+
headers,
|
|
391
|
+
body,
|
|
392
|
+
keepalive: true
|
|
393
|
+
}).catch(() => {
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
init(_config) {
|
|
399
|
+
},
|
|
400
|
+
track(envelope) {
|
|
401
|
+
try {
|
|
402
|
+
batch.push(envelope);
|
|
403
|
+
if (batch.length >= batchSize) {
|
|
404
|
+
sendBatch();
|
|
405
|
+
} else if (flushIntervalMs > 0) {
|
|
406
|
+
scheduleFlush();
|
|
407
|
+
}
|
|
408
|
+
} catch {
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
flush() {
|
|
412
|
+
if (flushTimer != null) {
|
|
413
|
+
clearTimeout(flushTimer);
|
|
414
|
+
flushTimer = null;
|
|
415
|
+
}
|
|
416
|
+
sendBatch();
|
|
417
|
+
return Promise.resolve();
|
|
418
|
+
},
|
|
419
|
+
teardown() {
|
|
420
|
+
if (flushTimer != null) {
|
|
421
|
+
clearTimeout(flushTimer);
|
|
422
|
+
flushTimer = null;
|
|
423
|
+
}
|
|
424
|
+
batch.length = 0;
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/listeners/registry.ts
|
|
430
|
+
var BUILT_IN_FACTORIES = {
|
|
431
|
+
noop: () => createNoopListener(),
|
|
432
|
+
clarity: (config) => createClarityListener(config),
|
|
433
|
+
ga: (config) => createGAListener(config),
|
|
434
|
+
"custom-api": (config) => createCustomApiListener(config)
|
|
435
|
+
};
|
|
436
|
+
function buildListenersFromRemoteConfig(remote, options) {
|
|
437
|
+
const factories = options?.factories ?? BUILT_IN_FACTORIES;
|
|
438
|
+
const onError2 = options?.onError;
|
|
439
|
+
const entries = [];
|
|
440
|
+
if (!Array.isArray(remote.listeners)) {
|
|
441
|
+
return entries;
|
|
442
|
+
}
|
|
443
|
+
for (const entry of remote.listeners) {
|
|
444
|
+
if (entry.enabled === false) continue;
|
|
445
|
+
const id = entry.id;
|
|
446
|
+
if (id == null || typeof id !== "string") continue;
|
|
447
|
+
const factory = factories[id];
|
|
448
|
+
if (factory == null) {
|
|
449
|
+
onError2?.(new Error(`Unknown listener id: ${id}`), { listenerId: id });
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
let listener;
|
|
453
|
+
try {
|
|
454
|
+
listener = factory(entry.config);
|
|
455
|
+
} catch (err) {
|
|
456
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
457
|
+
onError2?.(error, { listenerId: id });
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
entries.push({
|
|
461
|
+
id,
|
|
462
|
+
enabled: entry.enabled,
|
|
463
|
+
listener,
|
|
464
|
+
config: entry.config
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
return entries;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// src/validate-config.ts
|
|
471
|
+
var VALID_PRE_INIT_BEHAVIORS = ["drop", "queue"];
|
|
472
|
+
function isRemoteListenerEntry(entry) {
|
|
473
|
+
if (entry == null || typeof entry !== "object") return false;
|
|
474
|
+
const o = entry;
|
|
475
|
+
return typeof o.id === "string" && o.id.trim() !== "" && "config" in o && !("listener" in o && o.listener != null);
|
|
476
|
+
}
|
|
477
|
+
function validateConfig(options) {
|
|
478
|
+
const useRemoteConfig = Boolean(options.configUrl ?? options.getConfig);
|
|
479
|
+
if (typeof options.appId !== "string" || options.appId.trim() === "") {
|
|
480
|
+
throw new Error("Analytics init failed: appId is required and must be a non-empty string (e.g. from environment variables).");
|
|
481
|
+
}
|
|
482
|
+
if (options.listeners != null && !Array.isArray(options.listeners)) {
|
|
483
|
+
throw new Error("Analytics init failed: listeners must be an array.");
|
|
484
|
+
}
|
|
485
|
+
const behavior = options.preInitBehavior ?? "drop";
|
|
486
|
+
if (!VALID_PRE_INIT_BEHAVIORS.includes(behavior)) {
|
|
487
|
+
throw new Error(
|
|
488
|
+
`Analytics init failed: preInitBehavior must be 'queue' or 'drop', got '${behavior}'.`
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
if (behavior === "queue") {
|
|
492
|
+
const maxSize = options.queueMaxSize ?? 100;
|
|
493
|
+
const ttlMs = options.queueTtlMs ?? 5e3;
|
|
494
|
+
if (typeof maxSize !== "number" || maxSize <= 0) {
|
|
495
|
+
throw new Error("Analytics init failed: queueMaxSize must be a positive number.");
|
|
496
|
+
}
|
|
497
|
+
if (typeof ttlMs !== "number" || ttlMs <= 0) {
|
|
498
|
+
throw new Error("Analytics init failed: queueTtlMs must be a positive number.");
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (useRemoteConfig) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const listeners = options.listeners ?? [];
|
|
505
|
+
const isRemoteShape = listeners.length > 0 && listeners.every(isRemoteListenerEntry);
|
|
506
|
+
if (isRemoteShape) {
|
|
507
|
+
for (let i = 0; i < listeners.length; i++) {
|
|
508
|
+
const entry = listeners[i];
|
|
509
|
+
if (entry.enabled === false) continue;
|
|
510
|
+
if (typeof entry.id !== "string" || entry.id.trim() === "") {
|
|
511
|
+
throw new Error(`Analytics init failed: listeners[${i}].id is required and must be a non-empty string.`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
for (let i = 0; i < listeners.length; i++) {
|
|
517
|
+
const entry = listeners[i];
|
|
518
|
+
if (entry.enabled === false) continue;
|
|
519
|
+
if (entry.listener == null || typeof entry.listener !== "object") {
|
|
520
|
+
throw new Error(`Analytics init failed: listeners[${i}].listener is required.`);
|
|
521
|
+
}
|
|
522
|
+
const listener = entry.listener;
|
|
523
|
+
if (typeof listener.track !== "function") {
|
|
524
|
+
throw new Error(`Analytics init failed: listeners[${i}].listener must have a track method.`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// src/version.ts
|
|
530
|
+
var SDK_VERSION = "1.0.0";
|
|
531
|
+
|
|
532
|
+
// src/facade.ts
|
|
533
|
+
var enricherState = null;
|
|
534
|
+
var registry = null;
|
|
535
|
+
var preInitBuffer = null;
|
|
536
|
+
var onError;
|
|
537
|
+
var initialized = false;
|
|
538
|
+
function getOrCreatePreInitBuffer() {
|
|
539
|
+
if (preInitBuffer == null) {
|
|
540
|
+
preInitBuffer = new PreInitBuffer({
|
|
541
|
+
mode: "queue",
|
|
542
|
+
maxSize: 100,
|
|
543
|
+
ttlMs: 5e3
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
return preInitBuffer;
|
|
547
|
+
}
|
|
548
|
+
function getEnricherState() {
|
|
549
|
+
if (enricherState == null) {
|
|
550
|
+
throw new Error("Analytics not initialized.");
|
|
551
|
+
}
|
|
552
|
+
return enricherState;
|
|
553
|
+
}
|
|
554
|
+
async function fetchRemoteConfig(options) {
|
|
555
|
+
if (options.getConfig) {
|
|
556
|
+
return options.getConfig();
|
|
557
|
+
}
|
|
558
|
+
if (options.configUrl) {
|
|
559
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
560
|
+
const res = await fetchFn(options.configUrl);
|
|
561
|
+
if (!res.ok) {
|
|
562
|
+
throw new Error(`Analytics config fetch failed: ${res.status} ${res.statusText}`);
|
|
563
|
+
}
|
|
564
|
+
const json = await res.json();
|
|
565
|
+
if (json == null || typeof json !== "object") {
|
|
566
|
+
throw new Error("Analytics config response must be a JSON object.");
|
|
567
|
+
}
|
|
568
|
+
return json;
|
|
569
|
+
}
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
function isInlineRemoteShape(listeners) {
|
|
573
|
+
if (!Array.isArray(listeners) || listeners.length === 0) return false;
|
|
574
|
+
return listeners.every(
|
|
575
|
+
(entry) => entry != null && typeof entry === "object" && typeof entry.id === "string" && "config" in entry && !("listener" in entry && entry.listener != null)
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
async function init(options) {
|
|
579
|
+
validateConfig(options);
|
|
580
|
+
let appId;
|
|
581
|
+
let environment;
|
|
582
|
+
let listeners;
|
|
583
|
+
const remote = await fetchRemoteConfig(options);
|
|
584
|
+
if (remote != null) {
|
|
585
|
+
appId = (options.appId ?? "").toString().trim();
|
|
586
|
+
environment = options.environment ?? "production";
|
|
587
|
+
if (!appId) {
|
|
588
|
+
throw new Error("Analytics init failed: appId is required (set in init options, e.g. from environment variables).");
|
|
589
|
+
}
|
|
590
|
+
listeners = buildListenersFromRemoteConfig(remote, {
|
|
591
|
+
factories: options.listenerFactories,
|
|
592
|
+
onError: options.onError
|
|
593
|
+
});
|
|
594
|
+
} else {
|
|
595
|
+
appId = options.appId.trim();
|
|
596
|
+
environment = options.environment ?? "production";
|
|
597
|
+
const rawListeners = options.listeners ?? [];
|
|
598
|
+
if (isInlineRemoteShape(rawListeners)) {
|
|
599
|
+
listeners = buildListenersFromRemoteConfig(
|
|
600
|
+
{ listeners: rawListeners },
|
|
601
|
+
{
|
|
602
|
+
factories: options.listenerFactories,
|
|
603
|
+
onError: options.onError
|
|
604
|
+
}
|
|
605
|
+
);
|
|
606
|
+
} else {
|
|
607
|
+
listeners = rawListeners;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
const preInitBehavior = options.preInitBehavior ?? "drop";
|
|
611
|
+
const queueMaxSize = options.queueMaxSize ?? 100;
|
|
612
|
+
const queueTtlMs = options.queueTtlMs ?? 5e3;
|
|
613
|
+
onError = options.onError;
|
|
614
|
+
const mode = preInitBehavior === "queue" ? "queue" : "drop";
|
|
615
|
+
preInitBuffer = new PreInitBuffer({ mode, maxSize: queueMaxSize, ttlMs: queueTtlMs });
|
|
616
|
+
enricherState = createEnricherState(appId, environment, SDK_VERSION, options.sessionId);
|
|
617
|
+
registry = new ListenerRegistry();
|
|
618
|
+
for (const entry of listeners) {
|
|
619
|
+
if (entry.enabled === false) continue;
|
|
620
|
+
try {
|
|
621
|
+
const initResult = entry.listener.init(entry.config);
|
|
622
|
+
if (initResult instanceof Promise) {
|
|
623
|
+
await initResult;
|
|
624
|
+
}
|
|
625
|
+
registry.add(entry.id, entry.listener);
|
|
626
|
+
} catch (err) {
|
|
627
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
628
|
+
onError?.(error, { listenerId: entry.id });
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
const deps = {
|
|
632
|
+
registry,
|
|
633
|
+
getEnricherState,
|
|
634
|
+
onError
|
|
635
|
+
};
|
|
636
|
+
if (preInitBuffer) {
|
|
637
|
+
if (preInitBehavior === "queue") {
|
|
638
|
+
preInitBuffer.flush(
|
|
639
|
+
(payload) => dispatchTrack(deps, payload),
|
|
640
|
+
(userId, traits) => {
|
|
641
|
+
setUserId(enricherState, userId);
|
|
642
|
+
dispatchIdentify(deps, userId, traits);
|
|
643
|
+
}
|
|
644
|
+
);
|
|
645
|
+
} else {
|
|
646
|
+
preInitBuffer.flush(() => {
|
|
647
|
+
}, () => {
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
preInitBuffer = null;
|
|
651
|
+
}
|
|
652
|
+
initialized = true;
|
|
653
|
+
}
|
|
654
|
+
function track(event) {
|
|
655
|
+
if (!initialized) {
|
|
656
|
+
getOrCreatePreInitBuffer().pushTrack(event.name, event.properties, event.timestamp);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
const deps = {
|
|
660
|
+
registry,
|
|
661
|
+
getEnricherState,
|
|
662
|
+
onError
|
|
663
|
+
};
|
|
664
|
+
dispatchTrack(deps, event);
|
|
665
|
+
}
|
|
666
|
+
function identify(userId, traits) {
|
|
667
|
+
if (!initialized) {
|
|
668
|
+
getOrCreatePreInitBuffer().pushIdentify(userId, traits);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
if (enricherState) setUserId(enricherState, userId);
|
|
672
|
+
const deps = {
|
|
673
|
+
registry,
|
|
674
|
+
getEnricherState,
|
|
675
|
+
onError
|
|
676
|
+
};
|
|
677
|
+
dispatchIdentify(deps, userId, traits);
|
|
678
|
+
}
|
|
679
|
+
async function flush() {
|
|
680
|
+
if (!initialized || registry == null) return;
|
|
681
|
+
const promises = [];
|
|
682
|
+
for (const { listener } of registry.getAll()) {
|
|
683
|
+
if (typeof listener.flush === "function") {
|
|
684
|
+
const result = listener.flush();
|
|
685
|
+
if (result instanceof Promise) promises.push(result);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
await Promise.all(promises);
|
|
689
|
+
}
|
|
690
|
+
async function reset() {
|
|
691
|
+
if (preInitBuffer) {
|
|
692
|
+
preInitBuffer.flush(() => {
|
|
693
|
+
}, () => {
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
if (enricherState) clearUserId(enricherState);
|
|
697
|
+
if (registry) {
|
|
698
|
+
for (const { listener } of registry.getAll()) {
|
|
699
|
+
if (typeof listener.teardown === "function") {
|
|
700
|
+
try {
|
|
701
|
+
const result = listener.teardown();
|
|
702
|
+
if (result instanceof Promise) await result;
|
|
703
|
+
} catch (err) {
|
|
704
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
705
|
+
onError?.(error, {});
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
registry.clear();
|
|
710
|
+
}
|
|
711
|
+
preInitBuffer = null;
|
|
712
|
+
enricherState = null;
|
|
713
|
+
registry = null;
|
|
714
|
+
onError = void 0;
|
|
715
|
+
initialized = false;
|
|
716
|
+
}
|
|
717
|
+
function isInitialized() {
|
|
718
|
+
return initialized;
|
|
719
|
+
}
|
|
720
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
721
|
+
0 && (module.exports = {
|
|
722
|
+
flush,
|
|
723
|
+
identify,
|
|
724
|
+
init,
|
|
725
|
+
isInitialized,
|
|
726
|
+
reset,
|
|
727
|
+
track
|
|
728
|
+
});
|
|
729
|
+
//# sourceMappingURL=index.cjs.map
|