@tell-rs/browser 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/README.md +148 -0
- package/dist/index.cjs +1291 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +84 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +1268 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1291 @@
|
|
|
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 index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ClosedError: () => ClosedError,
|
|
24
|
+
ConfigurationError: () => ConfigurationError,
|
|
25
|
+
Events: () => Events,
|
|
26
|
+
NetworkError: () => NetworkError,
|
|
27
|
+
SerializationError: () => SerializationError,
|
|
28
|
+
TellError: () => TellError,
|
|
29
|
+
ValidationError: () => ValidationError,
|
|
30
|
+
default: () => index_default,
|
|
31
|
+
development: () => development,
|
|
32
|
+
production: () => production,
|
|
33
|
+
tell: () => tell
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// ../core/src/constants.ts
|
|
38
|
+
var Events = {
|
|
39
|
+
// User Lifecycle
|
|
40
|
+
UserSignedUp: "User Signed Up",
|
|
41
|
+
UserSignedIn: "User Signed In",
|
|
42
|
+
UserSignedOut: "User Signed Out",
|
|
43
|
+
UserInvited: "User Invited",
|
|
44
|
+
UserOnboarded: "User Onboarded",
|
|
45
|
+
AuthenticationFailed: "Authentication Failed",
|
|
46
|
+
PasswordReset: "Password Reset",
|
|
47
|
+
TwoFactorEnabled: "Two Factor Enabled",
|
|
48
|
+
TwoFactorDisabled: "Two Factor Disabled",
|
|
49
|
+
// Revenue & Billing
|
|
50
|
+
OrderCompleted: "Order Completed",
|
|
51
|
+
OrderRefunded: "Order Refunded",
|
|
52
|
+
OrderCanceled: "Order Canceled",
|
|
53
|
+
PaymentFailed: "Payment Failed",
|
|
54
|
+
PaymentMethodAdded: "Payment Method Added",
|
|
55
|
+
PaymentMethodUpdated: "Payment Method Updated",
|
|
56
|
+
PaymentMethodRemoved: "Payment Method Removed",
|
|
57
|
+
// Subscription
|
|
58
|
+
SubscriptionStarted: "Subscription Started",
|
|
59
|
+
SubscriptionRenewed: "Subscription Renewed",
|
|
60
|
+
SubscriptionPaused: "Subscription Paused",
|
|
61
|
+
SubscriptionResumed: "Subscription Resumed",
|
|
62
|
+
SubscriptionChanged: "Subscription Changed",
|
|
63
|
+
SubscriptionCanceled: "Subscription Canceled",
|
|
64
|
+
// Trial
|
|
65
|
+
TrialStarted: "Trial Started",
|
|
66
|
+
TrialEndingSoon: "Trial Ending Soon",
|
|
67
|
+
TrialEnded: "Trial Ended",
|
|
68
|
+
TrialConverted: "Trial Converted",
|
|
69
|
+
// Shopping
|
|
70
|
+
CartViewed: "Cart Viewed",
|
|
71
|
+
CartUpdated: "Cart Updated",
|
|
72
|
+
CartAbandoned: "Cart Abandoned",
|
|
73
|
+
CheckoutStarted: "Checkout Started",
|
|
74
|
+
CheckoutCompleted: "Checkout Completed",
|
|
75
|
+
// Engagement
|
|
76
|
+
PageViewed: "Page Viewed",
|
|
77
|
+
FeatureUsed: "Feature Used",
|
|
78
|
+
SearchPerformed: "Search Performed",
|
|
79
|
+
FileUploaded: "File Uploaded",
|
|
80
|
+
NotificationSent: "Notification Sent",
|
|
81
|
+
NotificationClicked: "Notification Clicked",
|
|
82
|
+
// Communication
|
|
83
|
+
EmailSent: "Email Sent",
|
|
84
|
+
EmailOpened: "Email Opened",
|
|
85
|
+
EmailClicked: "Email Clicked",
|
|
86
|
+
EmailBounced: "Email Bounced",
|
|
87
|
+
EmailUnsubscribed: "Email Unsubscribed",
|
|
88
|
+
SupportTicketCreated: "Support Ticket Created",
|
|
89
|
+
SupportTicketResolved: "Support Ticket Resolved"
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// ../core/src/errors.ts
|
|
93
|
+
var TellError = class extends Error {
|
|
94
|
+
constructor(message) {
|
|
95
|
+
super(message);
|
|
96
|
+
this.name = "TellError";
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
var ConfigurationError = class extends TellError {
|
|
100
|
+
constructor(message) {
|
|
101
|
+
super(message);
|
|
102
|
+
this.name = "ConfigurationError";
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
var ValidationError = class extends TellError {
|
|
106
|
+
field;
|
|
107
|
+
constructor(field, message) {
|
|
108
|
+
super(`${field}: ${message}`);
|
|
109
|
+
this.name = "ValidationError";
|
|
110
|
+
this.field = field;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
var NetworkError = class extends TellError {
|
|
114
|
+
statusCode;
|
|
115
|
+
constructor(message, statusCode) {
|
|
116
|
+
super(message);
|
|
117
|
+
this.name = "NetworkError";
|
|
118
|
+
this.statusCode = statusCode;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
var ClosedError = class extends TellError {
|
|
122
|
+
constructor() {
|
|
123
|
+
super("Client is closed");
|
|
124
|
+
this.name = "ClosedError";
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
var SerializationError = class extends TellError {
|
|
128
|
+
constructor(message) {
|
|
129
|
+
super(message);
|
|
130
|
+
this.name = "SerializationError";
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// ../core/src/validation.ts
|
|
135
|
+
var HEX_RE = /^[0-9a-fA-F]{32}$/;
|
|
136
|
+
var MAX_EVENT_NAME = 256;
|
|
137
|
+
var MAX_LOG_MESSAGE = 65536;
|
|
138
|
+
function validateApiKey(key) {
|
|
139
|
+
if (!key) {
|
|
140
|
+
throw new ConfigurationError("apiKey is required");
|
|
141
|
+
}
|
|
142
|
+
if (!HEX_RE.test(key)) {
|
|
143
|
+
throw new ConfigurationError(
|
|
144
|
+
"apiKey must be exactly 32 hex characters"
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function validateEventName(name) {
|
|
149
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
150
|
+
throw new ValidationError("eventName", "must be a non-empty string");
|
|
151
|
+
}
|
|
152
|
+
if (name.length > MAX_EVENT_NAME) {
|
|
153
|
+
throw new ValidationError(
|
|
154
|
+
"eventName",
|
|
155
|
+
`must be at most ${MAX_EVENT_NAME} characters, got ${name.length}`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function validateLogMessage(message) {
|
|
160
|
+
if (typeof message !== "string" || message.length === 0) {
|
|
161
|
+
throw new ValidationError("message", "must be a non-empty string");
|
|
162
|
+
}
|
|
163
|
+
if (message.length > MAX_LOG_MESSAGE) {
|
|
164
|
+
throw new ValidationError(
|
|
165
|
+
"message",
|
|
166
|
+
`must be at most ${MAX_LOG_MESSAGE} characters, got ${message.length}`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function validateUserId(id) {
|
|
171
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
172
|
+
throw new ValidationError("userId", "must be a non-empty string");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ../core/src/batcher.ts
|
|
177
|
+
var Batcher = class {
|
|
178
|
+
queue = [];
|
|
179
|
+
timer = null;
|
|
180
|
+
closed = false;
|
|
181
|
+
flushing = null;
|
|
182
|
+
config;
|
|
183
|
+
constructor(config) {
|
|
184
|
+
this.config = config;
|
|
185
|
+
this.timer = setInterval(() => {
|
|
186
|
+
if (this.queue.length > 0) {
|
|
187
|
+
this.flush().catch(() => {
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}, config.interval);
|
|
191
|
+
if (this.timer && typeof this.timer.unref === "function") {
|
|
192
|
+
this.timer.unref();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
add(item) {
|
|
196
|
+
if (this.closed) return;
|
|
197
|
+
if (this.queue.length >= this.config.maxQueueSize) {
|
|
198
|
+
this.queue.shift();
|
|
199
|
+
if (this.config.onOverflow) {
|
|
200
|
+
this.config.onOverflow();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
this.queue.push(item);
|
|
204
|
+
if (this.queue.length >= this.config.size) {
|
|
205
|
+
this.flush().catch(() => {
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async flush() {
|
|
210
|
+
if (this.flushing) {
|
|
211
|
+
return this.flushing;
|
|
212
|
+
}
|
|
213
|
+
this.flushing = this.doFlush();
|
|
214
|
+
try {
|
|
215
|
+
await this.flushing;
|
|
216
|
+
} finally {
|
|
217
|
+
this.flushing = null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async close() {
|
|
221
|
+
this.closed = true;
|
|
222
|
+
if (this.timer !== null) {
|
|
223
|
+
clearInterval(this.timer);
|
|
224
|
+
this.timer = null;
|
|
225
|
+
}
|
|
226
|
+
await this.flush();
|
|
227
|
+
}
|
|
228
|
+
get pending() {
|
|
229
|
+
return this.queue.length;
|
|
230
|
+
}
|
|
231
|
+
drain() {
|
|
232
|
+
const items = this.queue;
|
|
233
|
+
this.queue = [];
|
|
234
|
+
return items;
|
|
235
|
+
}
|
|
236
|
+
halveBatchSize() {
|
|
237
|
+
this.config.size = Math.max(1, Math.floor(this.config.size / 2));
|
|
238
|
+
}
|
|
239
|
+
async doFlush() {
|
|
240
|
+
while (this.queue.length > 0) {
|
|
241
|
+
const batch = this.queue.slice(0, this.config.size);
|
|
242
|
+
try {
|
|
243
|
+
await this.config.send(batch);
|
|
244
|
+
this.queue.splice(0, batch.length);
|
|
245
|
+
} catch {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// ../core/src/before-send.ts
|
|
253
|
+
function runBeforeSend(item, fns) {
|
|
254
|
+
const pipeline = Array.isArray(fns) ? fns : [fns];
|
|
255
|
+
let current = item;
|
|
256
|
+
for (const fn of pipeline) {
|
|
257
|
+
if (current === null) return null;
|
|
258
|
+
current = fn(current);
|
|
259
|
+
}
|
|
260
|
+
return current;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/config.ts
|
|
264
|
+
var DEFAULTS = {
|
|
265
|
+
endpoint: "https://collect.tell.app",
|
|
266
|
+
batchSize: 20,
|
|
267
|
+
flushInterval: 5e3,
|
|
268
|
+
maxRetries: 5,
|
|
269
|
+
closeTimeout: 5e3,
|
|
270
|
+
networkTimeout: 1e4,
|
|
271
|
+
logLevel: "error",
|
|
272
|
+
source: "browser",
|
|
273
|
+
disabled: false,
|
|
274
|
+
maxQueueSize: 1e3,
|
|
275
|
+
sessionTimeout: 18e5,
|
|
276
|
+
// 30 min
|
|
277
|
+
persistence: "localStorage",
|
|
278
|
+
respectDoNotTrack: false,
|
|
279
|
+
botDetection: true,
|
|
280
|
+
captureErrors: false
|
|
281
|
+
};
|
|
282
|
+
function resolveConfig(options) {
|
|
283
|
+
return { ...DEFAULTS, ...options };
|
|
284
|
+
}
|
|
285
|
+
function development(overrides) {
|
|
286
|
+
return {
|
|
287
|
+
endpoint: "http://localhost:8080",
|
|
288
|
+
batchSize: 5,
|
|
289
|
+
flushInterval: 2e3,
|
|
290
|
+
logLevel: "debug",
|
|
291
|
+
...overrides
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function production(overrides) {
|
|
295
|
+
return {
|
|
296
|
+
logLevel: "error",
|
|
297
|
+
...overrides
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/persistence.ts
|
|
302
|
+
var STORAGE_KEYS = {
|
|
303
|
+
DEVICE_ID: "tell_device_id",
|
|
304
|
+
USER_ID: "tell_user_id",
|
|
305
|
+
OPT_OUT: "tell_opt_out",
|
|
306
|
+
SUPER_PROPS: "tell_super_props"
|
|
307
|
+
};
|
|
308
|
+
var LocalStorageStorage = class {
|
|
309
|
+
get(key) {
|
|
310
|
+
try {
|
|
311
|
+
return localStorage.getItem(key);
|
|
312
|
+
} catch {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
set(key, value) {
|
|
317
|
+
try {
|
|
318
|
+
localStorage.setItem(key, value);
|
|
319
|
+
} catch {
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
remove(key) {
|
|
323
|
+
try {
|
|
324
|
+
localStorage.removeItem(key);
|
|
325
|
+
} catch {
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
var MemoryStorage = class {
|
|
330
|
+
data = /* @__PURE__ */ new Map();
|
|
331
|
+
get(key) {
|
|
332
|
+
return this.data.get(key) ?? null;
|
|
333
|
+
}
|
|
334
|
+
set(key, value) {
|
|
335
|
+
this.data.set(key, value);
|
|
336
|
+
}
|
|
337
|
+
remove(key) {
|
|
338
|
+
this.data.delete(key);
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
function createStorage(persistence) {
|
|
342
|
+
if (persistence === "localStorage" && typeof localStorage !== "undefined") {
|
|
343
|
+
try {
|
|
344
|
+
const testKey = "tell_test";
|
|
345
|
+
localStorage.setItem(testKey, "1");
|
|
346
|
+
localStorage.removeItem(testKey);
|
|
347
|
+
return new LocalStorageStorage();
|
|
348
|
+
} catch {
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return new MemoryStorage();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/id.ts
|
|
355
|
+
function generateId() {
|
|
356
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
357
|
+
const bytes = new Uint8Array(16);
|
|
358
|
+
crypto.getRandomValues(bytes);
|
|
359
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
360
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
361
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(
|
|
362
|
+
""
|
|
363
|
+
);
|
|
364
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
365
|
+
}
|
|
366
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
367
|
+
const r = Math.random() * 16 | 0;
|
|
368
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
369
|
+
return v.toString(16);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/bot.ts
|
|
374
|
+
function isBot() {
|
|
375
|
+
if (typeof navigator === "undefined") return false;
|
|
376
|
+
if (navigator.webdriver === true) return true;
|
|
377
|
+
const ua = navigator.userAgent || "";
|
|
378
|
+
return /headless/i.test(ua);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/context.ts
|
|
382
|
+
function captureContext() {
|
|
383
|
+
const ctx = {};
|
|
384
|
+
if (typeof navigator !== "undefined") {
|
|
385
|
+
const ua = navigator.userAgent || "";
|
|
386
|
+
const parsed = parseUA(ua);
|
|
387
|
+
ctx.browser = parsed.browser;
|
|
388
|
+
ctx.browser_version = parsed.browserVersion;
|
|
389
|
+
ctx.os_name = parsed.os;
|
|
390
|
+
ctx.os_version = parsed.osVersion;
|
|
391
|
+
ctx.device_type = inferDeviceType(parsed.os);
|
|
392
|
+
ctx.locale = navigator.language;
|
|
393
|
+
if ("hardwareConcurrency" in navigator) {
|
|
394
|
+
ctx.cpu_cores = navigator.hardwareConcurrency;
|
|
395
|
+
}
|
|
396
|
+
if ("deviceMemory" in navigator) {
|
|
397
|
+
ctx.device_memory = navigator.deviceMemory;
|
|
398
|
+
}
|
|
399
|
+
if ("maxTouchPoints" in navigator) {
|
|
400
|
+
ctx.touch = navigator.maxTouchPoints > 0;
|
|
401
|
+
}
|
|
402
|
+
const conn = navigator.connection;
|
|
403
|
+
if (conn?.effectiveType) {
|
|
404
|
+
ctx.connection_type = conn.effectiveType;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (typeof screen !== "undefined") {
|
|
408
|
+
ctx.screen_width = screen.width;
|
|
409
|
+
ctx.screen_height = screen.height;
|
|
410
|
+
}
|
|
411
|
+
if (typeof window !== "undefined") {
|
|
412
|
+
ctx.viewport_width = window.innerWidth;
|
|
413
|
+
ctx.viewport_height = window.innerHeight;
|
|
414
|
+
if (window.devicePixelRatio) {
|
|
415
|
+
ctx.device_pixel_ratio = window.devicePixelRatio;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (typeof document !== "undefined") {
|
|
419
|
+
ctx.referrer = document.referrer || void 0;
|
|
420
|
+
if (ctx.referrer) {
|
|
421
|
+
try {
|
|
422
|
+
ctx.referrer_domain = new URL(ctx.referrer).hostname;
|
|
423
|
+
} catch {
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
ctx.title = document.title || void 0;
|
|
427
|
+
}
|
|
428
|
+
if (typeof location !== "undefined") {
|
|
429
|
+
ctx.url = location.href;
|
|
430
|
+
}
|
|
431
|
+
try {
|
|
432
|
+
ctx.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
433
|
+
} catch {
|
|
434
|
+
}
|
|
435
|
+
return ctx;
|
|
436
|
+
}
|
|
437
|
+
function inferDeviceType(os) {
|
|
438
|
+
if (os === "iOS" || os === "Android") return "mobile";
|
|
439
|
+
return "desktop";
|
|
440
|
+
}
|
|
441
|
+
function parseUA(ua) {
|
|
442
|
+
const result = {};
|
|
443
|
+
if (/Edg\/(\d+[\d.]*)/.test(ua)) {
|
|
444
|
+
result.browser = "Edge";
|
|
445
|
+
result.browserVersion = RegExp.$1;
|
|
446
|
+
} else if (/OPR\/(\d+[\d.]*)/.test(ua)) {
|
|
447
|
+
result.browser = "Opera";
|
|
448
|
+
result.browserVersion = RegExp.$1;
|
|
449
|
+
} else if (/Chrome\/(\d+[\d.]*)/.test(ua)) {
|
|
450
|
+
result.browser = "Chrome";
|
|
451
|
+
result.browserVersion = RegExp.$1;
|
|
452
|
+
} else if (/Safari\/[\d.]+/.test(ua) && /Version\/(\d+[\d.]*)/.test(ua)) {
|
|
453
|
+
result.browser = "Safari";
|
|
454
|
+
result.browserVersion = RegExp.$1;
|
|
455
|
+
} else if (/Firefox\/(\d+[\d.]*)/.test(ua)) {
|
|
456
|
+
result.browser = "Firefox";
|
|
457
|
+
result.browserVersion = RegExp.$1;
|
|
458
|
+
}
|
|
459
|
+
if (/Windows NT ([\d.]+)/.test(ua)) {
|
|
460
|
+
result.os = "Windows";
|
|
461
|
+
result.osVersion = RegExp.$1;
|
|
462
|
+
} else if (/Mac OS X ([\d_.]+)/.test(ua)) {
|
|
463
|
+
result.os = "macOS";
|
|
464
|
+
result.osVersion = RegExp.$1.replace(/_/g, ".");
|
|
465
|
+
} else if (/iPhone OS ([\d_]+)/.test(ua)) {
|
|
466
|
+
result.os = "iOS";
|
|
467
|
+
result.osVersion = RegExp.$1.replace(/_/g, ".");
|
|
468
|
+
} else if (/Android ([\d.]+)/.test(ua)) {
|
|
469
|
+
result.os = "Android";
|
|
470
|
+
result.osVersion = RegExp.$1;
|
|
471
|
+
} else if (/Linux/.test(ua)) {
|
|
472
|
+
result.os = "Linux";
|
|
473
|
+
}
|
|
474
|
+
return result;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/utm.ts
|
|
478
|
+
var UTM_KEYS = [
|
|
479
|
+
"utm_source",
|
|
480
|
+
"utm_medium",
|
|
481
|
+
"utm_campaign",
|
|
482
|
+
"utm_term",
|
|
483
|
+
"utm_content"
|
|
484
|
+
];
|
|
485
|
+
function captureUtm() {
|
|
486
|
+
if (typeof window === "undefined" || !window.location?.search) return {};
|
|
487
|
+
const params = new URLSearchParams(window.location.search);
|
|
488
|
+
const utm = {};
|
|
489
|
+
for (const key of UTM_KEYS) {
|
|
490
|
+
const value = params.get(key);
|
|
491
|
+
if (value) {
|
|
492
|
+
utm[key] = value;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return utm;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// src/session.ts
|
|
499
|
+
var SessionManager = class {
|
|
500
|
+
_sessionId;
|
|
501
|
+
lastHiddenAt = 0;
|
|
502
|
+
lastActivityAt;
|
|
503
|
+
timeout;
|
|
504
|
+
onNewSession;
|
|
505
|
+
visibilityHandler;
|
|
506
|
+
checkTimer;
|
|
507
|
+
lastContextAt = {};
|
|
508
|
+
constructor(config) {
|
|
509
|
+
this.timeout = config.timeout;
|
|
510
|
+
this.onNewSession = config.onNewSession;
|
|
511
|
+
this._sessionId = generateId();
|
|
512
|
+
this.lastActivityAt = Date.now();
|
|
513
|
+
this.emitContext("session_start");
|
|
514
|
+
this.visibilityHandler = () => this.handleVisibility();
|
|
515
|
+
if (typeof document !== "undefined") {
|
|
516
|
+
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
517
|
+
}
|
|
518
|
+
this.checkTimer = setInterval(() => this.checkTimeout(), 6e4);
|
|
519
|
+
if (typeof this.checkTimer.unref === "function") {
|
|
520
|
+
this.checkTimer.unref();
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
get sessionId() {
|
|
524
|
+
return this._sessionId;
|
|
525
|
+
}
|
|
526
|
+
set sessionId(id) {
|
|
527
|
+
this._sessionId = id;
|
|
528
|
+
}
|
|
529
|
+
touch() {
|
|
530
|
+
this.lastActivityAt = Date.now();
|
|
531
|
+
}
|
|
532
|
+
destroy() {
|
|
533
|
+
if (typeof document !== "undefined") {
|
|
534
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
535
|
+
}
|
|
536
|
+
clearInterval(this.checkTimer);
|
|
537
|
+
}
|
|
538
|
+
handleVisibility() {
|
|
539
|
+
if (document.visibilityState === "hidden") {
|
|
540
|
+
this.lastHiddenAt = Date.now();
|
|
541
|
+
} else if (document.visibilityState === "visible") {
|
|
542
|
+
if (this.lastHiddenAt > 0 && Date.now() - this.lastHiddenAt > this.timeout) {
|
|
543
|
+
this.rotateSession("session_timeout");
|
|
544
|
+
} else if (this.lastHiddenAt > 0) {
|
|
545
|
+
this.emitContext("app_foreground");
|
|
546
|
+
}
|
|
547
|
+
this.lastHiddenAt = 0;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
checkTimeout() {
|
|
551
|
+
if (Date.now() - this.lastActivityAt > this.timeout) {
|
|
552
|
+
this.rotateSession("session_timeout");
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
rotateSession(reason) {
|
|
556
|
+
this._sessionId = generateId();
|
|
557
|
+
this.lastActivityAt = Date.now();
|
|
558
|
+
this.emitContext(reason);
|
|
559
|
+
}
|
|
560
|
+
emitContext(reason) {
|
|
561
|
+
const now = Date.now();
|
|
562
|
+
const last = this.lastContextAt[reason] ?? 0;
|
|
563
|
+
if (now - last < 1e3) return;
|
|
564
|
+
this.lastContextAt[reason] = now;
|
|
565
|
+
this.onNewSession(reason, this._sessionId);
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
// src/transport.ts
|
|
570
|
+
var BrowserTransport = class {
|
|
571
|
+
endpoint;
|
|
572
|
+
apiKey;
|
|
573
|
+
maxRetries;
|
|
574
|
+
networkTimeout;
|
|
575
|
+
onError;
|
|
576
|
+
onPayloadTooLarge;
|
|
577
|
+
constructor(config) {
|
|
578
|
+
this.endpoint = config.endpoint;
|
|
579
|
+
this.apiKey = config.apiKey;
|
|
580
|
+
this.maxRetries = config.maxRetries;
|
|
581
|
+
this.networkTimeout = config.networkTimeout;
|
|
582
|
+
this.onError = config.onError;
|
|
583
|
+
this.onPayloadTooLarge = config.onPayloadTooLarge;
|
|
584
|
+
}
|
|
585
|
+
async sendEvents(events) {
|
|
586
|
+
if (events.length === 0) return;
|
|
587
|
+
const body = events.map((e) => JSON.stringify(e)).join("\n");
|
|
588
|
+
await this.send("/v1/events", body);
|
|
589
|
+
}
|
|
590
|
+
async sendLogs(logs) {
|
|
591
|
+
if (logs.length === 0) return;
|
|
592
|
+
const body = logs.map((l) => JSON.stringify(l)).join("\n");
|
|
593
|
+
await this.send("/v1/logs", body);
|
|
594
|
+
}
|
|
595
|
+
/** Best-effort flush via sendBeacon for page unload. */
|
|
596
|
+
beacon(events, logs) {
|
|
597
|
+
if (typeof navigator === "undefined" || !navigator.sendBeacon) return;
|
|
598
|
+
if (events.length > 0) {
|
|
599
|
+
const body = events.map((e) => JSON.stringify(e)).join("\n");
|
|
600
|
+
const blob = new Blob([body], { type: "application/x-ndjson" });
|
|
601
|
+
const url = `${this.endpoint}/v1/events?token=${encodeURIComponent(this.apiKey)}`;
|
|
602
|
+
navigator.sendBeacon(url, blob);
|
|
603
|
+
}
|
|
604
|
+
if (logs.length > 0) {
|
|
605
|
+
const body = logs.map((l) => JSON.stringify(l)).join("\n");
|
|
606
|
+
const blob = new Blob([body], { type: "application/x-ndjson" });
|
|
607
|
+
const url = `${this.endpoint}/v1/logs?token=${encodeURIComponent(this.apiKey)}`;
|
|
608
|
+
navigator.sendBeacon(url, blob);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
async send(path, body) {
|
|
612
|
+
const url = `${this.endpoint}${path}`;
|
|
613
|
+
const headers = {
|
|
614
|
+
"Content-Type": "application/x-ndjson",
|
|
615
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
616
|
+
};
|
|
617
|
+
let lastError;
|
|
618
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
619
|
+
if (attempt > 0) {
|
|
620
|
+
await this.backoff(attempt);
|
|
621
|
+
}
|
|
622
|
+
if (typeof navigator !== "undefined" && !navigator.onLine) {
|
|
623
|
+
lastError = new NetworkError("Browser is offline");
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
try {
|
|
627
|
+
const controller = new AbortController();
|
|
628
|
+
const timer = setTimeout(
|
|
629
|
+
() => controller.abort(),
|
|
630
|
+
this.networkTimeout
|
|
631
|
+
);
|
|
632
|
+
const response = await fetch(url, {
|
|
633
|
+
method: "POST",
|
|
634
|
+
headers,
|
|
635
|
+
body,
|
|
636
|
+
signal: controller.signal,
|
|
637
|
+
keepalive: true
|
|
638
|
+
});
|
|
639
|
+
clearTimeout(timer);
|
|
640
|
+
if (response.status === 202) {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
if (response.status === 207) {
|
|
644
|
+
if (this.onError) {
|
|
645
|
+
this.onError(
|
|
646
|
+
new NetworkError(
|
|
647
|
+
`Partial success: some items rejected`,
|
|
648
|
+
207
|
|
649
|
+
)
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (response.status === 413) {
|
|
655
|
+
if (this.onPayloadTooLarge) {
|
|
656
|
+
this.onPayloadTooLarge();
|
|
657
|
+
}
|
|
658
|
+
throw new NetworkError("Payload too large", 413);
|
|
659
|
+
}
|
|
660
|
+
if (response.status === 401) {
|
|
661
|
+
throw new NetworkError("Invalid API key", 401);
|
|
662
|
+
}
|
|
663
|
+
if (response.status >= 400 && response.status < 500) {
|
|
664
|
+
throw new NetworkError(
|
|
665
|
+
`HTTP ${response.status}: ${response.statusText}`,
|
|
666
|
+
response.status
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
lastError = new NetworkError(
|
|
670
|
+
`HTTP ${response.status}: ${response.statusText}`,
|
|
671
|
+
response.status
|
|
672
|
+
);
|
|
673
|
+
} catch (err) {
|
|
674
|
+
if (err instanceof NetworkError && err.statusCode === 413) {
|
|
675
|
+
throw err;
|
|
676
|
+
}
|
|
677
|
+
if (err instanceof NetworkError && err.statusCode && err.statusCode < 500) {
|
|
678
|
+
if (this.onError) this.onError(err);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
lastError = err instanceof Error ? err : new NetworkError(String(err));
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if (lastError && this.onError) {
|
|
685
|
+
this.onError(lastError);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
backoff(attempt) {
|
|
689
|
+
const base = 1e3 * Math.pow(1.5, attempt - 1);
|
|
690
|
+
const jitter = base * 0.2 * Math.random();
|
|
691
|
+
const delay = Math.min(base + jitter, 3e4);
|
|
692
|
+
return new Promise((resolve) => setTimeout(resolve, delay));
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
// src/queue.ts
|
|
697
|
+
var PreInitQueue = class {
|
|
698
|
+
items = [];
|
|
699
|
+
maxSize;
|
|
700
|
+
constructor(maxSize = 1e3) {
|
|
701
|
+
this.maxSize = maxSize;
|
|
702
|
+
}
|
|
703
|
+
push(call) {
|
|
704
|
+
if (this.items.length >= this.maxSize) {
|
|
705
|
+
this.items.shift();
|
|
706
|
+
}
|
|
707
|
+
this.items.push(call);
|
|
708
|
+
}
|
|
709
|
+
replay(target) {
|
|
710
|
+
const calls = this.items;
|
|
711
|
+
this.items = [];
|
|
712
|
+
for (const call of calls) {
|
|
713
|
+
try {
|
|
714
|
+
target[call.method](...call.args);
|
|
715
|
+
} catch {
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
get length() {
|
|
720
|
+
return this.items.length;
|
|
721
|
+
}
|
|
722
|
+
clear() {
|
|
723
|
+
this.items = [];
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
// src/index.ts
|
|
728
|
+
var configured = false;
|
|
729
|
+
var closed = false;
|
|
730
|
+
var _disabled = false;
|
|
731
|
+
var _optedOut = false;
|
|
732
|
+
var storage;
|
|
733
|
+
var transport;
|
|
734
|
+
var eventBatcher;
|
|
735
|
+
var logBatcher;
|
|
736
|
+
var sessionManager;
|
|
737
|
+
var resolvedConfig;
|
|
738
|
+
var _apiKey;
|
|
739
|
+
var deviceId;
|
|
740
|
+
var userId;
|
|
741
|
+
var superProperties = {};
|
|
742
|
+
var beforeSend;
|
|
743
|
+
var beforeSendLog;
|
|
744
|
+
var sdkLogLevel;
|
|
745
|
+
var queue = new PreInitQueue(1e3);
|
|
746
|
+
var unloadHandler = null;
|
|
747
|
+
var visibilityUnloadHandler = null;
|
|
748
|
+
var errorHandler = null;
|
|
749
|
+
var rejectionHandler = null;
|
|
750
|
+
var LOG_LEVELS = {
|
|
751
|
+
error: 0,
|
|
752
|
+
warn: 1,
|
|
753
|
+
info: 2,
|
|
754
|
+
debug: 3
|
|
755
|
+
};
|
|
756
|
+
function reportError(err) {
|
|
757
|
+
if (resolvedConfig?.onError && err instanceof Error) {
|
|
758
|
+
resolvedConfig.onError(err);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function sdkDebug(msg) {
|
|
762
|
+
if (sdkLogLevel >= LOG_LEVELS.debug) {
|
|
763
|
+
console.debug(`[Tell] ${msg}`);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
function handleUnload() {
|
|
767
|
+
const events = eventBatcher.drain();
|
|
768
|
+
const logs = logBatcher.drain();
|
|
769
|
+
transport.beacon(events, logs);
|
|
770
|
+
}
|
|
771
|
+
function handleVisibilityUnload() {
|
|
772
|
+
if (document.visibilityState === "hidden") {
|
|
773
|
+
handleUnload();
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function onNewSession(reason, sessionId) {
|
|
777
|
+
if (_disabled || _optedOut || closed) return;
|
|
778
|
+
const ctx = captureContext();
|
|
779
|
+
const event = {
|
|
780
|
+
type: "context",
|
|
781
|
+
device_id: deviceId,
|
|
782
|
+
session_id: sessionId,
|
|
783
|
+
user_id: userId,
|
|
784
|
+
timestamp: Date.now(),
|
|
785
|
+
context: { reason, ...ctx }
|
|
786
|
+
};
|
|
787
|
+
eventBatcher.add(event);
|
|
788
|
+
}
|
|
789
|
+
function persistSuperProps() {
|
|
790
|
+
storage.set(STORAGE_KEYS.SUPER_PROPS, JSON.stringify(superProperties));
|
|
791
|
+
}
|
|
792
|
+
function loadSuperProps() {
|
|
793
|
+
const raw = storage.get(STORAGE_KEYS.SUPER_PROPS);
|
|
794
|
+
if (!raw) return {};
|
|
795
|
+
try {
|
|
796
|
+
return JSON.parse(raw);
|
|
797
|
+
} catch {
|
|
798
|
+
storage.remove(STORAGE_KEYS.SUPER_PROPS);
|
|
799
|
+
return {};
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
var tell = {
|
|
803
|
+
// -----------------------------------------------------------------------
|
|
804
|
+
// Lifecycle
|
|
805
|
+
// -----------------------------------------------------------------------
|
|
806
|
+
configure(apiKey, options) {
|
|
807
|
+
if (configured) {
|
|
808
|
+
reportError(new ConfigurationError("Tell is already configured"));
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
validateApiKey(apiKey);
|
|
812
|
+
_apiKey = apiKey;
|
|
813
|
+
resolvedConfig = resolveConfig(options);
|
|
814
|
+
sdkLogLevel = LOG_LEVELS[resolvedConfig.logLevel] ?? 0;
|
|
815
|
+
beforeSend = resolvedConfig.beforeSend;
|
|
816
|
+
beforeSendLog = resolvedConfig.beforeSendLog;
|
|
817
|
+
_disabled = resolvedConfig.disabled;
|
|
818
|
+
storage = createStorage(resolvedConfig.persistence);
|
|
819
|
+
deviceId = storage.get(STORAGE_KEYS.DEVICE_ID) ?? generateId();
|
|
820
|
+
storage.set(STORAGE_KEYS.DEVICE_ID, deviceId);
|
|
821
|
+
userId = storage.get(STORAGE_KEYS.USER_ID) ?? void 0;
|
|
822
|
+
_optedOut = storage.get(STORAGE_KEYS.OPT_OUT) === "1";
|
|
823
|
+
superProperties = loadSuperProps();
|
|
824
|
+
const utm = captureUtm();
|
|
825
|
+
if (Object.keys(utm).length > 0) {
|
|
826
|
+
Object.assign(superProperties, utm);
|
|
827
|
+
persistSuperProps();
|
|
828
|
+
}
|
|
829
|
+
if (resolvedConfig.botDetection && isBot()) {
|
|
830
|
+
_disabled = true;
|
|
831
|
+
sdkDebug("bot detected, disabling");
|
|
832
|
+
}
|
|
833
|
+
if (resolvedConfig.respectDoNotTrack && typeof navigator !== "undefined" && navigator.doNotTrack === "1") {
|
|
834
|
+
_disabled = true;
|
|
835
|
+
sdkDebug("Do Not Track enabled, disabling");
|
|
836
|
+
}
|
|
837
|
+
transport = new BrowserTransport({
|
|
838
|
+
endpoint: resolvedConfig.endpoint,
|
|
839
|
+
apiKey: _apiKey,
|
|
840
|
+
maxRetries: resolvedConfig.maxRetries,
|
|
841
|
+
networkTimeout: resolvedConfig.networkTimeout,
|
|
842
|
+
onError: resolvedConfig.onError,
|
|
843
|
+
onPayloadTooLarge: () => {
|
|
844
|
+
eventBatcher.halveBatchSize();
|
|
845
|
+
logBatcher.halveBatchSize();
|
|
846
|
+
sdkDebug("413 received, halved batch size");
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
eventBatcher = new Batcher({
|
|
850
|
+
size: resolvedConfig.batchSize,
|
|
851
|
+
interval: resolvedConfig.flushInterval,
|
|
852
|
+
maxQueueSize: resolvedConfig.maxQueueSize,
|
|
853
|
+
send: (items) => transport.sendEvents(items),
|
|
854
|
+
onOverflow: () => sdkDebug("event queue overflow, dropping oldest")
|
|
855
|
+
});
|
|
856
|
+
logBatcher = new Batcher({
|
|
857
|
+
size: resolvedConfig.batchSize,
|
|
858
|
+
interval: resolvedConfig.flushInterval,
|
|
859
|
+
maxQueueSize: resolvedConfig.maxQueueSize,
|
|
860
|
+
send: (items) => transport.sendLogs(items),
|
|
861
|
+
onOverflow: () => sdkDebug("log queue overflow, dropping oldest")
|
|
862
|
+
});
|
|
863
|
+
sessionManager = new SessionManager({
|
|
864
|
+
timeout: resolvedConfig.sessionTimeout,
|
|
865
|
+
onNewSession
|
|
866
|
+
});
|
|
867
|
+
if (typeof window !== "undefined") {
|
|
868
|
+
unloadHandler = handleUnload;
|
|
869
|
+
window.addEventListener("beforeunload", unloadHandler);
|
|
870
|
+
}
|
|
871
|
+
if (typeof document !== "undefined") {
|
|
872
|
+
visibilityUnloadHandler = handleVisibilityUnload;
|
|
873
|
+
document.addEventListener("visibilitychange", visibilityUnloadHandler);
|
|
874
|
+
}
|
|
875
|
+
if (resolvedConfig.captureErrors && typeof window !== "undefined") {
|
|
876
|
+
errorHandler = (event) => {
|
|
877
|
+
if (_disabled || _optedOut || closed) return;
|
|
878
|
+
const msg = event.message || "Unknown error";
|
|
879
|
+
const data = {};
|
|
880
|
+
if (event.filename) data.filename = event.filename;
|
|
881
|
+
if (event.lineno) data.lineno = event.lineno;
|
|
882
|
+
if (event.colno) data.colno = event.colno;
|
|
883
|
+
if (event.error?.stack) data.stack = event.error.stack;
|
|
884
|
+
tell.logError(msg, "browser", data);
|
|
885
|
+
};
|
|
886
|
+
rejectionHandler = (event) => {
|
|
887
|
+
if (_disabled || _optedOut || closed) return;
|
|
888
|
+
const reason = event.reason;
|
|
889
|
+
const msg = reason instanceof Error ? reason.message : String(reason ?? "Unhandled promise rejection");
|
|
890
|
+
const data = {};
|
|
891
|
+
if (reason instanceof Error && reason.stack) data.stack = reason.stack;
|
|
892
|
+
tell.logError(msg, "browser", data);
|
|
893
|
+
};
|
|
894
|
+
window.addEventListener("error", errorHandler);
|
|
895
|
+
window.addEventListener("unhandledrejection", rejectionHandler);
|
|
896
|
+
}
|
|
897
|
+
configured = true;
|
|
898
|
+
closed = false;
|
|
899
|
+
sdkDebug(
|
|
900
|
+
`configured (endpoint=${resolvedConfig.endpoint}, batch=${resolvedConfig.batchSize})`
|
|
901
|
+
);
|
|
902
|
+
queue.replay(tell);
|
|
903
|
+
},
|
|
904
|
+
// -----------------------------------------------------------------------
|
|
905
|
+
// Events
|
|
906
|
+
// -----------------------------------------------------------------------
|
|
907
|
+
track(eventName, properties) {
|
|
908
|
+
if (!configured) {
|
|
909
|
+
queue.push({ method: "track", args: [eventName, properties] });
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
if (_disabled || _optedOut) return;
|
|
913
|
+
if (closed) {
|
|
914
|
+
reportError(new ClosedError());
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
try {
|
|
918
|
+
validateEventName(eventName);
|
|
919
|
+
} catch (err) {
|
|
920
|
+
reportError(err);
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
let event = {
|
|
924
|
+
type: "track",
|
|
925
|
+
event: eventName,
|
|
926
|
+
device_id: deviceId,
|
|
927
|
+
session_id: sessionManager.sessionId,
|
|
928
|
+
user_id: userId,
|
|
929
|
+
timestamp: Date.now(),
|
|
930
|
+
properties: { ...superProperties, ...properties }
|
|
931
|
+
};
|
|
932
|
+
if (beforeSend) {
|
|
933
|
+
event = runBeforeSend(event, beforeSend);
|
|
934
|
+
if (event === null) return;
|
|
935
|
+
}
|
|
936
|
+
sessionManager.touch();
|
|
937
|
+
eventBatcher.add(event);
|
|
938
|
+
},
|
|
939
|
+
identify(newUserId, traits) {
|
|
940
|
+
if (!configured) {
|
|
941
|
+
queue.push({ method: "identify", args: [newUserId, traits] });
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
if (_disabled || _optedOut) return;
|
|
945
|
+
if (closed) {
|
|
946
|
+
reportError(new ClosedError());
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
try {
|
|
950
|
+
validateUserId(newUserId);
|
|
951
|
+
} catch (err) {
|
|
952
|
+
reportError(err);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
userId = newUserId;
|
|
956
|
+
storage.set(STORAGE_KEYS.USER_ID, userId);
|
|
957
|
+
let event = {
|
|
958
|
+
type: "identify",
|
|
959
|
+
device_id: deviceId,
|
|
960
|
+
session_id: sessionManager.sessionId,
|
|
961
|
+
user_id: userId,
|
|
962
|
+
timestamp: Date.now(),
|
|
963
|
+
traits
|
|
964
|
+
};
|
|
965
|
+
if (beforeSend) {
|
|
966
|
+
event = runBeforeSend(event, beforeSend);
|
|
967
|
+
if (event === null) return;
|
|
968
|
+
}
|
|
969
|
+
sessionManager.touch();
|
|
970
|
+
eventBatcher.add(event);
|
|
971
|
+
},
|
|
972
|
+
group(groupId, properties) {
|
|
973
|
+
if (!configured) {
|
|
974
|
+
queue.push({ method: "group", args: [groupId, properties] });
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
if (_disabled || _optedOut) return;
|
|
978
|
+
if (closed) {
|
|
979
|
+
reportError(new ClosedError());
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
try {
|
|
983
|
+
if (!groupId) throw new ValidationError("groupId", "is required");
|
|
984
|
+
} catch (err) {
|
|
985
|
+
reportError(err);
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
let event = {
|
|
989
|
+
type: "group",
|
|
990
|
+
device_id: deviceId,
|
|
991
|
+
session_id: sessionManager.sessionId,
|
|
992
|
+
user_id: userId,
|
|
993
|
+
group_id: groupId,
|
|
994
|
+
timestamp: Date.now(),
|
|
995
|
+
properties: { ...superProperties, ...properties }
|
|
996
|
+
};
|
|
997
|
+
if (beforeSend) {
|
|
998
|
+
event = runBeforeSend(event, beforeSend);
|
|
999
|
+
if (event === null) return;
|
|
1000
|
+
}
|
|
1001
|
+
sessionManager.touch();
|
|
1002
|
+
eventBatcher.add(event);
|
|
1003
|
+
},
|
|
1004
|
+
revenue(amount, currency, orderId, properties) {
|
|
1005
|
+
if (!configured) {
|
|
1006
|
+
queue.push({ method: "revenue", args: [amount, currency, orderId, properties] });
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
if (_disabled || _optedOut) return;
|
|
1010
|
+
if (closed) {
|
|
1011
|
+
reportError(new ClosedError());
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
try {
|
|
1015
|
+
if (amount <= 0) throw new ValidationError("amount", "must be positive");
|
|
1016
|
+
if (!currency) throw new ValidationError("currency", "is required");
|
|
1017
|
+
if (!orderId) throw new ValidationError("orderId", "is required");
|
|
1018
|
+
} catch (err) {
|
|
1019
|
+
reportError(err);
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
let event = {
|
|
1023
|
+
type: "track",
|
|
1024
|
+
event: "Order Completed",
|
|
1025
|
+
device_id: deviceId,
|
|
1026
|
+
session_id: sessionManager.sessionId,
|
|
1027
|
+
user_id: userId,
|
|
1028
|
+
timestamp: Date.now(),
|
|
1029
|
+
properties: {
|
|
1030
|
+
...superProperties,
|
|
1031
|
+
...properties,
|
|
1032
|
+
order_id: orderId,
|
|
1033
|
+
amount,
|
|
1034
|
+
currency
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
if (beforeSend) {
|
|
1038
|
+
event = runBeforeSend(event, beforeSend);
|
|
1039
|
+
if (event === null) return;
|
|
1040
|
+
}
|
|
1041
|
+
sessionManager.touch();
|
|
1042
|
+
eventBatcher.add(event);
|
|
1043
|
+
},
|
|
1044
|
+
alias(previousId, newUserId) {
|
|
1045
|
+
if (!configured) {
|
|
1046
|
+
queue.push({ method: "alias", args: [previousId, newUserId] });
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
if (_disabled || _optedOut) return;
|
|
1050
|
+
if (closed) {
|
|
1051
|
+
reportError(new ClosedError());
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
try {
|
|
1055
|
+
if (!previousId)
|
|
1056
|
+
throw new ValidationError("previousId", "is required");
|
|
1057
|
+
validateUserId(newUserId);
|
|
1058
|
+
} catch (err) {
|
|
1059
|
+
reportError(err);
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
let event = {
|
|
1063
|
+
type: "alias",
|
|
1064
|
+
device_id: deviceId,
|
|
1065
|
+
session_id: sessionManager.sessionId,
|
|
1066
|
+
user_id: newUserId,
|
|
1067
|
+
timestamp: Date.now(),
|
|
1068
|
+
properties: { previous_id: previousId }
|
|
1069
|
+
};
|
|
1070
|
+
if (beforeSend) {
|
|
1071
|
+
event = runBeforeSend(event, beforeSend);
|
|
1072
|
+
if (event === null) return;
|
|
1073
|
+
}
|
|
1074
|
+
sessionManager.touch();
|
|
1075
|
+
eventBatcher.add(event);
|
|
1076
|
+
},
|
|
1077
|
+
// -----------------------------------------------------------------------
|
|
1078
|
+
// Logging
|
|
1079
|
+
// -----------------------------------------------------------------------
|
|
1080
|
+
log(level, message, service, data) {
|
|
1081
|
+
if (!configured) {
|
|
1082
|
+
queue.push({ method: "log", args: [level, message, service, data] });
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
if (_disabled || _optedOut) return;
|
|
1086
|
+
if (closed) {
|
|
1087
|
+
reportError(new ClosedError());
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
try {
|
|
1091
|
+
validateLogMessage(message);
|
|
1092
|
+
} catch (err) {
|
|
1093
|
+
reportError(err);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
let logEntry = {
|
|
1097
|
+
level,
|
|
1098
|
+
message,
|
|
1099
|
+
source: resolvedConfig.source,
|
|
1100
|
+
service: service ?? "app",
|
|
1101
|
+
session_id: sessionManager.sessionId,
|
|
1102
|
+
timestamp: Date.now(),
|
|
1103
|
+
data
|
|
1104
|
+
};
|
|
1105
|
+
if (beforeSendLog) {
|
|
1106
|
+
logEntry = runBeforeSend(logEntry, beforeSendLog);
|
|
1107
|
+
if (logEntry === null) return;
|
|
1108
|
+
}
|
|
1109
|
+
logBatcher.add(logEntry);
|
|
1110
|
+
},
|
|
1111
|
+
logEmergency(message, service, data) {
|
|
1112
|
+
tell.log("emergency", message, service, data);
|
|
1113
|
+
},
|
|
1114
|
+
logAlert(message, service, data) {
|
|
1115
|
+
tell.log("alert", message, service, data);
|
|
1116
|
+
},
|
|
1117
|
+
logCritical(message, service, data) {
|
|
1118
|
+
tell.log("critical", message, service, data);
|
|
1119
|
+
},
|
|
1120
|
+
logError(message, service, data) {
|
|
1121
|
+
tell.log("error", message, service, data);
|
|
1122
|
+
},
|
|
1123
|
+
logWarning(message, service, data) {
|
|
1124
|
+
tell.log("warning", message, service, data);
|
|
1125
|
+
},
|
|
1126
|
+
logNotice(message, service, data) {
|
|
1127
|
+
tell.log("notice", message, service, data);
|
|
1128
|
+
},
|
|
1129
|
+
logInfo(message, service, data) {
|
|
1130
|
+
tell.log("info", message, service, data);
|
|
1131
|
+
},
|
|
1132
|
+
logDebug(message, service, data) {
|
|
1133
|
+
tell.log("debug", message, service, data);
|
|
1134
|
+
},
|
|
1135
|
+
logTrace(message, service, data) {
|
|
1136
|
+
tell.log("trace", message, service, data);
|
|
1137
|
+
},
|
|
1138
|
+
// -----------------------------------------------------------------------
|
|
1139
|
+
// Super Properties
|
|
1140
|
+
// -----------------------------------------------------------------------
|
|
1141
|
+
register(properties) {
|
|
1142
|
+
if (!configured) {
|
|
1143
|
+
queue.push({ method: "register", args: [properties] });
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
Object.assign(superProperties, properties);
|
|
1147
|
+
persistSuperProps();
|
|
1148
|
+
},
|
|
1149
|
+
unregister(key) {
|
|
1150
|
+
if (!configured) {
|
|
1151
|
+
queue.push({ method: "unregister", args: [key] });
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
delete superProperties[key];
|
|
1155
|
+
persistSuperProps();
|
|
1156
|
+
},
|
|
1157
|
+
// -----------------------------------------------------------------------
|
|
1158
|
+
// Privacy
|
|
1159
|
+
// -----------------------------------------------------------------------
|
|
1160
|
+
optOut() {
|
|
1161
|
+
if (!configured) {
|
|
1162
|
+
queue.push({ method: "optOut", args: [] });
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
_optedOut = true;
|
|
1166
|
+
storage.set(STORAGE_KEYS.OPT_OUT, "1");
|
|
1167
|
+
},
|
|
1168
|
+
optIn() {
|
|
1169
|
+
if (!configured) {
|
|
1170
|
+
queue.push({ method: "optIn", args: [] });
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
_optedOut = false;
|
|
1174
|
+
storage.remove(STORAGE_KEYS.OPT_OUT);
|
|
1175
|
+
},
|
|
1176
|
+
isOptedOut() {
|
|
1177
|
+
return _optedOut;
|
|
1178
|
+
},
|
|
1179
|
+
// -----------------------------------------------------------------------
|
|
1180
|
+
// Control
|
|
1181
|
+
// -----------------------------------------------------------------------
|
|
1182
|
+
enable() {
|
|
1183
|
+
_disabled = false;
|
|
1184
|
+
},
|
|
1185
|
+
disable() {
|
|
1186
|
+
_disabled = true;
|
|
1187
|
+
},
|
|
1188
|
+
// -----------------------------------------------------------------------
|
|
1189
|
+
// Lifecycle
|
|
1190
|
+
// -----------------------------------------------------------------------
|
|
1191
|
+
async flush() {
|
|
1192
|
+
if (!configured) return;
|
|
1193
|
+
await Promise.all([eventBatcher.flush(), logBatcher.flush()]);
|
|
1194
|
+
},
|
|
1195
|
+
async close() {
|
|
1196
|
+
if (!configured || closed) return;
|
|
1197
|
+
closed = true;
|
|
1198
|
+
if (sessionManager) sessionManager.destroy();
|
|
1199
|
+
if (typeof window !== "undefined" && unloadHandler) {
|
|
1200
|
+
window.removeEventListener("beforeunload", unloadHandler);
|
|
1201
|
+
unloadHandler = null;
|
|
1202
|
+
}
|
|
1203
|
+
if (typeof document !== "undefined" && visibilityUnloadHandler) {
|
|
1204
|
+
document.removeEventListener("visibilitychange", visibilityUnloadHandler);
|
|
1205
|
+
visibilityUnloadHandler = null;
|
|
1206
|
+
}
|
|
1207
|
+
if (typeof window !== "undefined") {
|
|
1208
|
+
if (errorHandler) {
|
|
1209
|
+
window.removeEventListener("error", errorHandler);
|
|
1210
|
+
errorHandler = null;
|
|
1211
|
+
}
|
|
1212
|
+
if (rejectionHandler) {
|
|
1213
|
+
window.removeEventListener("unhandledrejection", rejectionHandler);
|
|
1214
|
+
rejectionHandler = null;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
const work = Promise.all([eventBatcher.close(), logBatcher.close()]);
|
|
1218
|
+
const timeout = new Promise(
|
|
1219
|
+
(_, reject) => setTimeout(
|
|
1220
|
+
() => reject(new Error("close timed out")),
|
|
1221
|
+
resolvedConfig.closeTimeout
|
|
1222
|
+
)
|
|
1223
|
+
);
|
|
1224
|
+
try {
|
|
1225
|
+
await Promise.race([work, timeout]);
|
|
1226
|
+
} catch (err) {
|
|
1227
|
+
reportError(err);
|
|
1228
|
+
}
|
|
1229
|
+
},
|
|
1230
|
+
reset() {
|
|
1231
|
+
if (!configured) return;
|
|
1232
|
+
userId = void 0;
|
|
1233
|
+
deviceId = generateId();
|
|
1234
|
+
superProperties = {};
|
|
1235
|
+
storage.remove(STORAGE_KEYS.USER_ID);
|
|
1236
|
+
storage.set(STORAGE_KEYS.DEVICE_ID, deviceId);
|
|
1237
|
+
storage.remove(STORAGE_KEYS.SUPER_PROPS);
|
|
1238
|
+
if (sessionManager) {
|
|
1239
|
+
sessionManager.sessionId = generateId();
|
|
1240
|
+
}
|
|
1241
|
+
},
|
|
1242
|
+
// -----------------------------------------------------------------------
|
|
1243
|
+
// Testing helper
|
|
1244
|
+
// -----------------------------------------------------------------------
|
|
1245
|
+
_resetForTesting() {
|
|
1246
|
+
if (configured && !closed) {
|
|
1247
|
+
if (sessionManager) sessionManager.destroy();
|
|
1248
|
+
if (eventBatcher) {
|
|
1249
|
+
eventBatcher.drain();
|
|
1250
|
+
eventBatcher.close().catch(() => {
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
if (logBatcher) {
|
|
1254
|
+
logBatcher.drain();
|
|
1255
|
+
logBatcher.close().catch(() => {
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
if (typeof window !== "undefined" && unloadHandler) {
|
|
1259
|
+
window.removeEventListener("beforeunload", unloadHandler);
|
|
1260
|
+
}
|
|
1261
|
+
if (typeof document !== "undefined" && visibilityUnloadHandler) {
|
|
1262
|
+
document.removeEventListener(
|
|
1263
|
+
"visibilitychange",
|
|
1264
|
+
visibilityUnloadHandler
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
if (typeof window !== "undefined") {
|
|
1268
|
+
if (errorHandler) window.removeEventListener("error", errorHandler);
|
|
1269
|
+
if (rejectionHandler)
|
|
1270
|
+
window.removeEventListener("unhandledrejection", rejectionHandler);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
configured = false;
|
|
1274
|
+
closed = false;
|
|
1275
|
+
_disabled = false;
|
|
1276
|
+
_optedOut = false;
|
|
1277
|
+
userId = void 0;
|
|
1278
|
+
deviceId = "";
|
|
1279
|
+
superProperties = {};
|
|
1280
|
+
beforeSend = void 0;
|
|
1281
|
+
beforeSendLog = void 0;
|
|
1282
|
+
sdkLogLevel = 0;
|
|
1283
|
+
unloadHandler = null;
|
|
1284
|
+
visibilityUnloadHandler = null;
|
|
1285
|
+
errorHandler = null;
|
|
1286
|
+
rejectionHandler = null;
|
|
1287
|
+
queue.clear();
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
var index_default = tell;
|
|
1291
|
+
//# sourceMappingURL=index.cjs.map
|