@tell-rs/node 0.2.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/dist/index.cjs ADDED
@@ -0,0 +1,829 @@
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
+ SENSITIVE_PARAMS: () => SENSITIVE_PARAMS,
28
+ SerializationError: () => SerializationError,
29
+ Tell: () => Tell,
30
+ TellError: () => TellError,
31
+ ValidationError: () => ValidationError,
32
+ development: () => development,
33
+ production: () => production,
34
+ redact: () => redact,
35
+ redactLog: () => redactLog
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+
39
+ // src/config.ts
40
+ var import_node_os = require("os");
41
+ var DEFAULTS = {
42
+ endpoint: "https://collect.tell.app",
43
+ batchSize: 100,
44
+ flushInterval: 1e4,
45
+ maxRetries: 3,
46
+ closeTimeout: 5e3,
47
+ networkTimeout: 3e4,
48
+ logLevel: "info",
49
+ source: (0, import_node_os.hostname)(),
50
+ disabled: false,
51
+ maxQueueSize: 1e3,
52
+ gzip: false
53
+ };
54
+ function resolveConfig(config) {
55
+ return { ...DEFAULTS, ...config };
56
+ }
57
+ function development(apiKey, overrides) {
58
+ return {
59
+ endpoint: "http://localhost:8080",
60
+ batchSize: 10,
61
+ flushInterval: 2e3,
62
+ logLevel: "debug",
63
+ ...overrides,
64
+ apiKey
65
+ };
66
+ }
67
+ function production(apiKey, overrides) {
68
+ return {
69
+ logLevel: "error",
70
+ ...overrides,
71
+ apiKey
72
+ };
73
+ }
74
+
75
+ // ../core/src/constants.ts
76
+ var Events = {
77
+ // User Lifecycle
78
+ UserSignedUp: "User Signed Up",
79
+ UserSignedIn: "User Signed In",
80
+ UserSignedOut: "User Signed Out",
81
+ UserInvited: "User Invited",
82
+ UserOnboarded: "User Onboarded",
83
+ AuthenticationFailed: "Authentication Failed",
84
+ PasswordReset: "Password Reset",
85
+ TwoFactorEnabled: "Two Factor Enabled",
86
+ TwoFactorDisabled: "Two Factor Disabled",
87
+ // Revenue & Billing
88
+ OrderCompleted: "Order Completed",
89
+ OrderRefunded: "Order Refunded",
90
+ OrderCanceled: "Order Canceled",
91
+ PaymentFailed: "Payment Failed",
92
+ PaymentMethodAdded: "Payment Method Added",
93
+ PaymentMethodUpdated: "Payment Method Updated",
94
+ PaymentMethodRemoved: "Payment Method Removed",
95
+ // Subscription
96
+ SubscriptionStarted: "Subscription Started",
97
+ SubscriptionRenewed: "Subscription Renewed",
98
+ SubscriptionPaused: "Subscription Paused",
99
+ SubscriptionResumed: "Subscription Resumed",
100
+ SubscriptionChanged: "Subscription Changed",
101
+ SubscriptionCanceled: "Subscription Canceled",
102
+ // Trial
103
+ TrialStarted: "Trial Started",
104
+ TrialEndingSoon: "Trial Ending Soon",
105
+ TrialEnded: "Trial Ended",
106
+ TrialConverted: "Trial Converted",
107
+ // Shopping
108
+ CartViewed: "Cart Viewed",
109
+ CartUpdated: "Cart Updated",
110
+ CartAbandoned: "Cart Abandoned",
111
+ CheckoutStarted: "Checkout Started",
112
+ CheckoutCompleted: "Checkout Completed",
113
+ // Engagement
114
+ PageViewed: "Page Viewed",
115
+ FeatureUsed: "Feature Used",
116
+ SearchPerformed: "Search Performed",
117
+ FileUploaded: "File Uploaded",
118
+ NotificationSent: "Notification Sent",
119
+ NotificationClicked: "Notification Clicked",
120
+ // Communication
121
+ EmailSent: "Email Sent",
122
+ EmailOpened: "Email Opened",
123
+ EmailClicked: "Email Clicked",
124
+ EmailBounced: "Email Bounced",
125
+ EmailUnsubscribed: "Email Unsubscribed",
126
+ SupportTicketCreated: "Support Ticket Created",
127
+ SupportTicketResolved: "Support Ticket Resolved"
128
+ };
129
+
130
+ // ../core/src/errors.ts
131
+ var TellError = class extends Error {
132
+ constructor(message) {
133
+ super(message);
134
+ this.name = "TellError";
135
+ }
136
+ };
137
+ var ConfigurationError = class extends TellError {
138
+ constructor(message) {
139
+ super(message);
140
+ this.name = "ConfigurationError";
141
+ }
142
+ };
143
+ var ValidationError = class extends TellError {
144
+ field;
145
+ constructor(field, message) {
146
+ super(`${field}: ${message}`);
147
+ this.name = "ValidationError";
148
+ this.field = field;
149
+ }
150
+ };
151
+ var NetworkError = class extends TellError {
152
+ statusCode;
153
+ constructor(message, statusCode) {
154
+ super(message);
155
+ this.name = "NetworkError";
156
+ this.statusCode = statusCode;
157
+ }
158
+ };
159
+ var ClosedError = class extends TellError {
160
+ constructor() {
161
+ super("Client is closed");
162
+ this.name = "ClosedError";
163
+ }
164
+ };
165
+ var SerializationError = class extends TellError {
166
+ constructor(message) {
167
+ super(message);
168
+ this.name = "SerializationError";
169
+ }
170
+ };
171
+
172
+ // ../core/src/validation.ts
173
+ var HEX_RE = /^[0-9a-fA-F]{32}$/;
174
+ var MAX_EVENT_NAME = 256;
175
+ var MAX_LOG_MESSAGE = 65536;
176
+ function validateApiKey(key) {
177
+ if (!key) {
178
+ throw new ConfigurationError("apiKey is required");
179
+ }
180
+ if (!HEX_RE.test(key)) {
181
+ throw new ConfigurationError(
182
+ "apiKey must be exactly 32 hex characters"
183
+ );
184
+ }
185
+ }
186
+ function validateEventName(name) {
187
+ if (typeof name !== "string" || name.length === 0) {
188
+ throw new ValidationError("eventName", "must be a non-empty string");
189
+ }
190
+ if (name.length > MAX_EVENT_NAME) {
191
+ throw new ValidationError(
192
+ "eventName",
193
+ `must be at most ${MAX_EVENT_NAME} characters, got ${name.length}`
194
+ );
195
+ }
196
+ }
197
+ function validateLogMessage(message) {
198
+ if (typeof message !== "string" || message.length === 0) {
199
+ throw new ValidationError("message", "must be a non-empty string");
200
+ }
201
+ if (message.length > MAX_LOG_MESSAGE) {
202
+ throw new ValidationError(
203
+ "message",
204
+ `must be at most ${MAX_LOG_MESSAGE} characters, got ${message.length}`
205
+ );
206
+ }
207
+ }
208
+ function validateUserId(id) {
209
+ if (typeof id !== "string" || id.length === 0) {
210
+ throw new ValidationError("userId", "must be a non-empty string");
211
+ }
212
+ }
213
+
214
+ // ../core/src/batcher.ts
215
+ var Batcher = class {
216
+ queue = [];
217
+ timer = null;
218
+ closed = false;
219
+ flushing = null;
220
+ config;
221
+ constructor(config) {
222
+ this.config = config;
223
+ this.timer = setInterval(() => {
224
+ if (this.queue.length > 0) {
225
+ this.flush().catch(() => {
226
+ });
227
+ }
228
+ }, config.interval);
229
+ if (this.timer && typeof this.timer.unref === "function") {
230
+ this.timer.unref();
231
+ }
232
+ }
233
+ add(item) {
234
+ if (this.closed) return;
235
+ if (this.queue.length >= this.config.maxQueueSize) {
236
+ this.queue.shift();
237
+ if (this.config.onOverflow) {
238
+ this.config.onOverflow();
239
+ }
240
+ }
241
+ this.queue.push(item);
242
+ if (this.queue.length >= this.config.size) {
243
+ this.flush().catch(() => {
244
+ });
245
+ }
246
+ }
247
+ async flush() {
248
+ if (this.flushing) {
249
+ return this.flushing;
250
+ }
251
+ this.flushing = this.doFlush();
252
+ try {
253
+ await this.flushing;
254
+ } finally {
255
+ this.flushing = null;
256
+ }
257
+ }
258
+ async close() {
259
+ this.closed = true;
260
+ if (this.timer !== null) {
261
+ clearInterval(this.timer);
262
+ this.timer = null;
263
+ }
264
+ await this.flush();
265
+ }
266
+ get pending() {
267
+ return this.queue.length;
268
+ }
269
+ drain() {
270
+ const items = this.queue;
271
+ this.queue = [];
272
+ return items;
273
+ }
274
+ halveBatchSize() {
275
+ this.config.size = Math.max(1, Math.floor(this.config.size / 2));
276
+ }
277
+ async doFlush() {
278
+ while (this.queue.length > 0) {
279
+ const batch = this.queue.slice(0, this.config.size);
280
+ try {
281
+ await this.config.send(batch);
282
+ this.queue.splice(0, batch.length);
283
+ } catch {
284
+ return;
285
+ }
286
+ }
287
+ }
288
+ };
289
+
290
+ // ../core/src/before-send.ts
291
+ function runBeforeSend(item, fns) {
292
+ const pipeline = Array.isArray(fns) ? fns : [fns];
293
+ let current = item;
294
+ for (const fn of pipeline) {
295
+ if (current === null) return null;
296
+ current = fn(current);
297
+ }
298
+ return current;
299
+ }
300
+
301
+ // ../core/src/redact.ts
302
+ var SENSITIVE_PARAMS = [
303
+ "token",
304
+ "api_key",
305
+ "key",
306
+ "secret",
307
+ "password",
308
+ "access_token",
309
+ "refresh_token",
310
+ "authorization"
311
+ ];
312
+ function stripUrlParams(url, params) {
313
+ try {
314
+ const u = new URL(url);
315
+ for (const p of params) u.searchParams.delete(p);
316
+ return u.toString();
317
+ } catch {
318
+ return url;
319
+ }
320
+ }
321
+ function stripParamsInProperties(props, params) {
322
+ if (!props) return props;
323
+ const out = {};
324
+ for (const [k, v] of Object.entries(props)) {
325
+ if (typeof v === "string" && v.startsWith("http")) {
326
+ out[k] = stripUrlParams(v, params);
327
+ } else {
328
+ out[k] = v;
329
+ }
330
+ }
331
+ return out;
332
+ }
333
+ function redactKeysInProperties(props, keys) {
334
+ if (!props) return props;
335
+ const out = {};
336
+ for (const [k, v] of Object.entries(props)) {
337
+ out[k] = keys.includes(k) ? "[REDACTED]" : v;
338
+ }
339
+ return out;
340
+ }
341
+ function redact(options) {
342
+ const { stripParams, redactKeys, dropRoutes } = options;
343
+ return (event) => {
344
+ if (dropRoutes && dropRoutes.length > 0 && event.context?.url) {
345
+ try {
346
+ const pathname = new URL(String(event.context.url)).pathname;
347
+ for (const prefix of dropRoutes) {
348
+ if (pathname.startsWith(prefix)) return null;
349
+ }
350
+ } catch {
351
+ }
352
+ }
353
+ let ctx = event.context;
354
+ let props = event.properties;
355
+ let traits = event.traits;
356
+ if (stripParams && stripParams.length > 0) {
357
+ if (ctx?.url && typeof ctx.url === "string") {
358
+ ctx = { ...ctx, url: stripUrlParams(ctx.url, stripParams) };
359
+ }
360
+ props = stripParamsInProperties(props, stripParams);
361
+ traits = stripParamsInProperties(traits, stripParams);
362
+ }
363
+ if (redactKeys && redactKeys.length > 0) {
364
+ props = redactKeysInProperties(props, redactKeys);
365
+ traits = redactKeysInProperties(traits, redactKeys);
366
+ }
367
+ if (ctx !== event.context || props !== event.properties || traits !== event.traits) {
368
+ return { ...event, context: ctx, properties: props, traits };
369
+ }
370
+ return event;
371
+ };
372
+ }
373
+ function redactLog(options) {
374
+ const { redactKeys } = options;
375
+ return (log) => {
376
+ if (redactKeys && redactKeys.length > 0 && log.data) {
377
+ const data = redactKeysInProperties(log.data, redactKeys);
378
+ if (data !== log.data) {
379
+ return { ...log, data };
380
+ }
381
+ }
382
+ return log;
383
+ };
384
+ }
385
+
386
+ // src/transport.ts
387
+ var import_node_zlib = require("zlib");
388
+ var HttpTransport = class {
389
+ endpoint;
390
+ apiKey;
391
+ maxRetries;
392
+ networkTimeout;
393
+ gzip;
394
+ onError;
395
+ onPayloadTooLarge;
396
+ constructor(config) {
397
+ this.endpoint = config.endpoint;
398
+ this.apiKey = config.apiKey;
399
+ this.maxRetries = config.maxRetries;
400
+ this.networkTimeout = config.networkTimeout;
401
+ this.gzip = config.gzip;
402
+ this.onError = config.onError;
403
+ this.onPayloadTooLarge = config.onPayloadTooLarge;
404
+ }
405
+ async sendEvents(events) {
406
+ if (events.length === 0) return;
407
+ const body = events.map((e) => JSON.stringify(e)).join("\n");
408
+ await this.send("/v1/events", body);
409
+ }
410
+ async sendLogs(logs) {
411
+ if (logs.length === 0) return;
412
+ const body = logs.map((l) => JSON.stringify(l)).join("\n");
413
+ await this.send("/v1/logs", body);
414
+ }
415
+ async send(path, body) {
416
+ const url = `${this.endpoint}${path}`;
417
+ const headers = {
418
+ "Content-Type": "application/x-ndjson",
419
+ Authorization: `Bearer ${this.apiKey}`
420
+ };
421
+ let payload = body;
422
+ if (this.gzip) {
423
+ payload = (0, import_node_zlib.gzipSync)(body);
424
+ headers["Content-Encoding"] = "gzip";
425
+ }
426
+ let lastError;
427
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
428
+ if (attempt > 0) {
429
+ await this.backoff(attempt);
430
+ }
431
+ try {
432
+ const response = await globalThis.fetch(url, {
433
+ method: "POST",
434
+ headers,
435
+ body: payload,
436
+ signal: AbortSignal.timeout(this.networkTimeout)
437
+ });
438
+ if (response.status === 202) {
439
+ return;
440
+ }
441
+ if (response.status === 207) {
442
+ if (this.onError) {
443
+ const data = await response.json().catch(() => null);
444
+ this.onError(
445
+ new NetworkError(
446
+ `Partial success: ${data?.rejected ?? "unknown"} items rejected`,
447
+ 207
448
+ )
449
+ );
450
+ }
451
+ return;
452
+ }
453
+ if (response.status === 413) {
454
+ if (this.onPayloadTooLarge) {
455
+ this.onPayloadTooLarge();
456
+ }
457
+ throw new NetworkError("Payload too large", 413);
458
+ }
459
+ if (response.status === 401) {
460
+ throw new NetworkError("Invalid API key", 401);
461
+ }
462
+ if (response.status >= 400 && response.status < 500) {
463
+ throw new NetworkError(
464
+ `HTTP ${response.status}: ${response.statusText}`,
465
+ response.status
466
+ );
467
+ }
468
+ lastError = new NetworkError(
469
+ `HTTP ${response.status}: ${response.statusText}`,
470
+ response.status
471
+ );
472
+ } catch (err) {
473
+ if (err instanceof NetworkError && err.statusCode === 413) {
474
+ throw err;
475
+ }
476
+ if (err instanceof NetworkError && err.statusCode && err.statusCode < 500) {
477
+ if (this.onError) this.onError(err);
478
+ return;
479
+ }
480
+ lastError = err instanceof Error ? err : new NetworkError(String(err));
481
+ }
482
+ }
483
+ if (lastError && this.onError) {
484
+ this.onError(lastError);
485
+ }
486
+ }
487
+ backoff(attempt) {
488
+ const base = 1e3 * Math.pow(1.5, attempt - 1);
489
+ const jitter = base * 0.2 * Math.random();
490
+ const delay = Math.min(base + jitter, 3e4);
491
+ return new Promise((resolve) => setTimeout(resolve, delay));
492
+ }
493
+ };
494
+
495
+ // src/index.ts
496
+ var LOG_LEVELS = {
497
+ error: 0,
498
+ warn: 1,
499
+ info: 2,
500
+ debug: 3
501
+ };
502
+ function uuid() {
503
+ return crypto.randomUUID();
504
+ }
505
+ var Tell = class {
506
+ transport;
507
+ eventBatcher;
508
+ logBatcher;
509
+ deviceId;
510
+ sessionId;
511
+ onError;
512
+ source;
513
+ closeTimeout;
514
+ sdkLogLevel;
515
+ beforeSend;
516
+ beforeSendLog;
517
+ superProperties = {};
518
+ closed = false;
519
+ _disabled;
520
+ constructor(config) {
521
+ validateApiKey(config.apiKey);
522
+ const resolved = resolveConfig(config);
523
+ this.onError = resolved.onError;
524
+ this.source = resolved.source;
525
+ this.closeTimeout = resolved.closeTimeout;
526
+ this.sdkLogLevel = LOG_LEVELS[resolved.logLevel] ?? 2;
527
+ this._disabled = resolved.disabled;
528
+ this.beforeSend = resolved.beforeSend;
529
+ this.beforeSendLog = resolved.beforeSendLog;
530
+ this.deviceId = uuid();
531
+ this.sessionId = uuid();
532
+ this.transport = new HttpTransport({
533
+ endpoint: resolved.endpoint,
534
+ apiKey: resolved.apiKey,
535
+ maxRetries: resolved.maxRetries,
536
+ networkTimeout: resolved.networkTimeout,
537
+ gzip: resolved.gzip,
538
+ onError: this.onError,
539
+ onPayloadTooLarge: () => {
540
+ this.eventBatcher.halveBatchSize();
541
+ this.logBatcher.halveBatchSize();
542
+ this.sdkDebug("413 received, halved batch size");
543
+ }
544
+ });
545
+ this.eventBatcher = new Batcher({
546
+ size: resolved.batchSize,
547
+ interval: resolved.flushInterval,
548
+ maxQueueSize: resolved.maxQueueSize,
549
+ send: (items) => this.transport.sendEvents(items),
550
+ onOverflow: () => {
551
+ this.sdkDebug("event queue overflow, dropping oldest item");
552
+ }
553
+ });
554
+ this.logBatcher = new Batcher({
555
+ size: resolved.batchSize,
556
+ interval: resolved.flushInterval,
557
+ maxQueueSize: resolved.maxQueueSize,
558
+ send: (items) => this.transport.sendLogs(items),
559
+ onOverflow: () => {
560
+ this.sdkDebug("log queue overflow, dropping oldest item");
561
+ }
562
+ });
563
+ this.sdkDebug(`initialized (endpoint=${resolved.endpoint}, batch=${resolved.batchSize}, flush=${resolved.flushInterval}ms)`);
564
+ }
565
+ // --- Super Properties ---
566
+ register(properties) {
567
+ Object.assign(this.superProperties, properties);
568
+ }
569
+ unregister(key) {
570
+ delete this.superProperties[key];
571
+ }
572
+ // --- Disabled ---
573
+ disable() {
574
+ this._disabled = true;
575
+ }
576
+ enable() {
577
+ this._disabled = false;
578
+ }
579
+ // --- Events ---
580
+ track(userId, eventName, properties) {
581
+ if (this._disabled) return;
582
+ if (this.closed) {
583
+ this.reportError(new ClosedError());
584
+ return;
585
+ }
586
+ try {
587
+ validateUserId(userId);
588
+ validateEventName(eventName);
589
+ } catch (err) {
590
+ this.reportError(err);
591
+ return;
592
+ }
593
+ let event = {
594
+ type: "track",
595
+ event: eventName,
596
+ device_id: this.deviceId,
597
+ session_id: this.sessionId,
598
+ user_id: userId,
599
+ timestamp: Date.now(),
600
+ properties: { ...this.superProperties, ...properties }
601
+ };
602
+ if (this.beforeSend) {
603
+ event = runBeforeSend(event, this.beforeSend);
604
+ if (event === null) return;
605
+ }
606
+ this.eventBatcher.add(event);
607
+ }
608
+ identify(userId, traits) {
609
+ if (this._disabled) return;
610
+ if (this.closed) {
611
+ this.reportError(new ClosedError());
612
+ return;
613
+ }
614
+ try {
615
+ validateUserId(userId);
616
+ } catch (err) {
617
+ this.reportError(err);
618
+ return;
619
+ }
620
+ let event = {
621
+ type: "identify",
622
+ device_id: this.deviceId,
623
+ session_id: this.sessionId,
624
+ user_id: userId,
625
+ timestamp: Date.now(),
626
+ traits
627
+ };
628
+ if (this.beforeSend) {
629
+ event = runBeforeSend(event, this.beforeSend);
630
+ if (event === null) return;
631
+ }
632
+ this.eventBatcher.add(event);
633
+ }
634
+ group(userId, groupId, properties) {
635
+ if (this._disabled) return;
636
+ if (this.closed) {
637
+ this.reportError(new ClosedError());
638
+ return;
639
+ }
640
+ try {
641
+ validateUserId(userId);
642
+ if (!groupId) throw new ValidationError("groupId", "is required");
643
+ } catch (err) {
644
+ this.reportError(err);
645
+ return;
646
+ }
647
+ let event = {
648
+ type: "group",
649
+ device_id: this.deviceId,
650
+ session_id: this.sessionId,
651
+ user_id: userId,
652
+ group_id: groupId,
653
+ timestamp: Date.now(),
654
+ properties: { ...this.superProperties, ...properties }
655
+ };
656
+ if (this.beforeSend) {
657
+ event = runBeforeSend(event, this.beforeSend);
658
+ if (event === null) return;
659
+ }
660
+ this.eventBatcher.add(event);
661
+ }
662
+ revenue(userId, amount, currency, orderId, properties) {
663
+ if (this._disabled) return;
664
+ if (this.closed) {
665
+ this.reportError(new ClosedError());
666
+ return;
667
+ }
668
+ try {
669
+ validateUserId(userId);
670
+ if (amount <= 0) throw new ValidationError("amount", "must be positive");
671
+ if (!currency) throw new ValidationError("currency", "is required");
672
+ if (!orderId) throw new ValidationError("orderId", "is required");
673
+ } catch (err) {
674
+ this.reportError(err);
675
+ return;
676
+ }
677
+ let event = {
678
+ type: "track",
679
+ event: "Order Completed",
680
+ device_id: this.deviceId,
681
+ session_id: this.sessionId,
682
+ user_id: userId,
683
+ timestamp: Date.now(),
684
+ properties: {
685
+ ...this.superProperties,
686
+ ...properties,
687
+ order_id: orderId,
688
+ amount,
689
+ currency
690
+ }
691
+ };
692
+ if (this.beforeSend) {
693
+ event = runBeforeSend(event, this.beforeSend);
694
+ if (event === null) return;
695
+ }
696
+ this.eventBatcher.add(event);
697
+ }
698
+ alias(previousId, userId) {
699
+ if (this._disabled) return;
700
+ if (this.closed) {
701
+ this.reportError(new ClosedError());
702
+ return;
703
+ }
704
+ try {
705
+ if (!previousId) throw new ValidationError("previousId", "is required");
706
+ validateUserId(userId);
707
+ } catch (err) {
708
+ this.reportError(err);
709
+ return;
710
+ }
711
+ let event = {
712
+ type: "alias",
713
+ device_id: this.deviceId,
714
+ session_id: this.sessionId,
715
+ user_id: userId,
716
+ timestamp: Date.now(),
717
+ properties: { previous_id: previousId }
718
+ };
719
+ if (this.beforeSend) {
720
+ event = runBeforeSend(event, this.beforeSend);
721
+ if (event === null) return;
722
+ }
723
+ this.eventBatcher.add(event);
724
+ }
725
+ resetSession() {
726
+ this.sessionId = uuid();
727
+ }
728
+ // --- Logging ---
729
+ log(level, message, service, data) {
730
+ if (this._disabled) return;
731
+ if (this.closed) {
732
+ this.reportError(new ClosedError());
733
+ return;
734
+ }
735
+ try {
736
+ validateLogMessage(message);
737
+ } catch (err) {
738
+ this.reportError(err);
739
+ return;
740
+ }
741
+ let logEntry = {
742
+ level,
743
+ message,
744
+ source: this.source,
745
+ service: service ?? "app",
746
+ session_id: this.sessionId,
747
+ timestamp: Date.now(),
748
+ data
749
+ };
750
+ if (this.beforeSendLog) {
751
+ logEntry = runBeforeSend(logEntry, this.beforeSendLog);
752
+ if (logEntry === null) return;
753
+ }
754
+ this.logBatcher.add(logEntry);
755
+ }
756
+ logEmergency(message, service, data) {
757
+ this.log("emergency", message, service, data);
758
+ }
759
+ logAlert(message, service, data) {
760
+ this.log("alert", message, service, data);
761
+ }
762
+ logCritical(message, service, data) {
763
+ this.log("critical", message, service, data);
764
+ }
765
+ logError(message, service, data) {
766
+ this.log("error", message, service, data);
767
+ }
768
+ logWarning(message, service, data) {
769
+ this.log("warning", message, service, data);
770
+ }
771
+ logNotice(message, service, data) {
772
+ this.log("notice", message, service, data);
773
+ }
774
+ logInfo(message, service, data) {
775
+ this.log("info", message, service, data);
776
+ }
777
+ logDebug(message, service, data) {
778
+ this.log("debug", message, service, data);
779
+ }
780
+ logTrace(message, service, data) {
781
+ this.log("trace", message, service, data);
782
+ }
783
+ // --- Lifecycle ---
784
+ async flush() {
785
+ await Promise.all([this.eventBatcher.flush(), this.logBatcher.flush()]);
786
+ }
787
+ async close() {
788
+ if (this.closed) return;
789
+ this.closed = true;
790
+ this.sdkDebug("closing...");
791
+ const work = Promise.all([this.eventBatcher.close(), this.logBatcher.close()]);
792
+ const timeout = new Promise(
793
+ (_, reject) => setTimeout(() => reject(new Error("close timed out")), this.closeTimeout)
794
+ );
795
+ try {
796
+ await Promise.race([work, timeout]);
797
+ } catch (err) {
798
+ this.reportError(err);
799
+ }
800
+ }
801
+ // --- Internal ---
802
+ reportError(err) {
803
+ if (this.onError && err instanceof Error) {
804
+ this.onError(err);
805
+ }
806
+ }
807
+ sdkDebug(msg) {
808
+ if (this.sdkLogLevel >= LOG_LEVELS.debug) {
809
+ console.debug(`[Tell] ${msg}`);
810
+ }
811
+ }
812
+ };
813
+ // Annotate the CommonJS export names for ESM import in node:
814
+ 0 && (module.exports = {
815
+ ClosedError,
816
+ ConfigurationError,
817
+ Events,
818
+ NetworkError,
819
+ SENSITIVE_PARAMS,
820
+ SerializationError,
821
+ Tell,
822
+ TellError,
823
+ ValidationError,
824
+ development,
825
+ production,
826
+ redact,
827
+ redactLog
828
+ });
829
+ //# sourceMappingURL=index.cjs.map