@tell-rs/browser 0.1.1 → 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/README.md CHANGED
@@ -135,6 +135,29 @@ tell.configure("your-api-key", production()); // defaults, error-only logging
135
135
  - **localStorage persistence** — device ID, user ID, session, and super properties survive page reloads
136
136
  - **Do Not Track** — optional respect for `navigator.doNotTrack`
137
137
 
138
+ ## Privacy & Redaction
139
+
140
+ The browser SDK automatically collects anonymous device context (browser, OS, screen, locale, timezone, referrer, connection type). See the [full data disclosure table](https://docs.tell.rs/tracking/sdks/javascript/browser#data-collected-automatically) for every field.
141
+
142
+ **URLs include query strings.** If your app puts tokens or sensitive data in URLs, use the `redact()` utility to strip them:
143
+
144
+ ```ts
145
+ import tell, { redact, redactLog, SENSITIVE_PARAMS } from "@tell-rs/browser";
146
+
147
+ tell.configure("your-api-key", {
148
+ beforeSend: redact({
149
+ dropRoutes: ["/internal", "/health"],
150
+ stripParams: [...SENSITIVE_PARAMS, "session_id"],
151
+ redactKeys: ["email", "phone"],
152
+ }),
153
+ beforeSendLog: redactLog({
154
+ redactKeys: ["password"],
155
+ }),
156
+ });
157
+ ```
158
+
159
+ See the [docs site](https://docs.tell.rs/tracking/sdks/javascript/browser#redaction--beforesend) for more `beforeSend` patterns and server-side pipeline redaction.
160
+
138
161
  ## Framework Integrations
139
162
 
140
163
  For React, Next.js, and Vue, use the dedicated packages:
@@ -0,0 +1,330 @@
1
+ // ../core/src/constants.ts
2
+ var Events = {
3
+ // User Lifecycle
4
+ UserSignedUp: "User Signed Up",
5
+ UserSignedIn: "User Signed In",
6
+ UserSignedOut: "User Signed Out",
7
+ UserInvited: "User Invited",
8
+ UserOnboarded: "User Onboarded",
9
+ AuthenticationFailed: "Authentication Failed",
10
+ PasswordReset: "Password Reset",
11
+ TwoFactorEnabled: "Two Factor Enabled",
12
+ TwoFactorDisabled: "Two Factor Disabled",
13
+ // Revenue & Billing
14
+ OrderCompleted: "Order Completed",
15
+ OrderRefunded: "Order Refunded",
16
+ OrderCanceled: "Order Canceled",
17
+ PaymentFailed: "Payment Failed",
18
+ PaymentMethodAdded: "Payment Method Added",
19
+ PaymentMethodUpdated: "Payment Method Updated",
20
+ PaymentMethodRemoved: "Payment Method Removed",
21
+ // Subscription
22
+ SubscriptionStarted: "Subscription Started",
23
+ SubscriptionRenewed: "Subscription Renewed",
24
+ SubscriptionPaused: "Subscription Paused",
25
+ SubscriptionResumed: "Subscription Resumed",
26
+ SubscriptionChanged: "Subscription Changed",
27
+ SubscriptionCanceled: "Subscription Canceled",
28
+ // Trial
29
+ TrialStarted: "Trial Started",
30
+ TrialEndingSoon: "Trial Ending Soon",
31
+ TrialEnded: "Trial Ended",
32
+ TrialConverted: "Trial Converted",
33
+ // Shopping
34
+ CartViewed: "Cart Viewed",
35
+ CartUpdated: "Cart Updated",
36
+ CartAbandoned: "Cart Abandoned",
37
+ CheckoutStarted: "Checkout Started",
38
+ CheckoutCompleted: "Checkout Completed",
39
+ // Engagement
40
+ PageViewed: "Page Viewed",
41
+ FeatureUsed: "Feature Used",
42
+ SearchPerformed: "Search Performed",
43
+ FileUploaded: "File Uploaded",
44
+ NotificationSent: "Notification Sent",
45
+ NotificationClicked: "Notification Clicked",
46
+ // Communication
47
+ EmailSent: "Email Sent",
48
+ EmailOpened: "Email Opened",
49
+ EmailClicked: "Email Clicked",
50
+ EmailBounced: "Email Bounced",
51
+ EmailUnsubscribed: "Email Unsubscribed",
52
+ SupportTicketCreated: "Support Ticket Created",
53
+ SupportTicketResolved: "Support Ticket Resolved"
54
+ };
55
+
56
+ // ../core/src/errors.ts
57
+ var TellError = class extends Error {
58
+ constructor(message) {
59
+ super(message);
60
+ this.name = "TellError";
61
+ }
62
+ };
63
+ var ConfigurationError = class extends TellError {
64
+ constructor(message) {
65
+ super(message);
66
+ this.name = "ConfigurationError";
67
+ }
68
+ };
69
+ var ValidationError = class extends TellError {
70
+ field;
71
+ constructor(field, message) {
72
+ super(`${field}: ${message}`);
73
+ this.name = "ValidationError";
74
+ this.field = field;
75
+ }
76
+ };
77
+ var NetworkError = class extends TellError {
78
+ statusCode;
79
+ constructor(message, statusCode) {
80
+ super(message);
81
+ this.name = "NetworkError";
82
+ this.statusCode = statusCode;
83
+ }
84
+ };
85
+ var ClosedError = class extends TellError {
86
+ constructor() {
87
+ super("Client is closed");
88
+ this.name = "ClosedError";
89
+ }
90
+ };
91
+ var SerializationError = class extends TellError {
92
+ constructor(message) {
93
+ super(message);
94
+ this.name = "SerializationError";
95
+ }
96
+ };
97
+
98
+ // ../core/src/validation.ts
99
+ var HEX_RE = /^[0-9a-fA-F]{32}$/;
100
+ var MAX_EVENT_NAME = 256;
101
+ var MAX_LOG_MESSAGE = 65536;
102
+ function validateApiKey(key) {
103
+ if (!key) {
104
+ throw new ConfigurationError("apiKey is required");
105
+ }
106
+ if (!HEX_RE.test(key)) {
107
+ throw new ConfigurationError(
108
+ "apiKey must be exactly 32 hex characters"
109
+ );
110
+ }
111
+ }
112
+ function validateEventName(name) {
113
+ if (typeof name !== "string" || name.length === 0) {
114
+ throw new ValidationError("eventName", "must be a non-empty string");
115
+ }
116
+ if (name.length > MAX_EVENT_NAME) {
117
+ throw new ValidationError(
118
+ "eventName",
119
+ `must be at most ${MAX_EVENT_NAME} characters, got ${name.length}`
120
+ );
121
+ }
122
+ }
123
+ function validateLogMessage(message) {
124
+ if (typeof message !== "string" || message.length === 0) {
125
+ throw new ValidationError("message", "must be a non-empty string");
126
+ }
127
+ if (message.length > MAX_LOG_MESSAGE) {
128
+ throw new ValidationError(
129
+ "message",
130
+ `must be at most ${MAX_LOG_MESSAGE} characters, got ${message.length}`
131
+ );
132
+ }
133
+ }
134
+ function validateUserId(id) {
135
+ if (typeof id !== "string" || id.length === 0) {
136
+ throw new ValidationError("userId", "must be a non-empty string");
137
+ }
138
+ }
139
+
140
+ // ../core/src/batcher.ts
141
+ var Batcher = class {
142
+ queue = [];
143
+ timer = null;
144
+ closed = false;
145
+ flushing = null;
146
+ config;
147
+ constructor(config) {
148
+ this.config = config;
149
+ this.timer = setInterval(() => {
150
+ if (this.queue.length > 0) {
151
+ this.flush().catch(() => {
152
+ });
153
+ }
154
+ }, config.interval);
155
+ if (this.timer && typeof this.timer.unref === "function") {
156
+ this.timer.unref();
157
+ }
158
+ }
159
+ add(item) {
160
+ if (this.closed) return;
161
+ if (this.queue.length >= this.config.maxQueueSize) {
162
+ this.queue.shift();
163
+ if (this.config.onOverflow) {
164
+ this.config.onOverflow();
165
+ }
166
+ }
167
+ this.queue.push(item);
168
+ if (this.queue.length >= this.config.size) {
169
+ this.flush().catch(() => {
170
+ });
171
+ }
172
+ }
173
+ async flush() {
174
+ if (this.flushing) {
175
+ return this.flushing;
176
+ }
177
+ this.flushing = this.doFlush();
178
+ try {
179
+ await this.flushing;
180
+ } finally {
181
+ this.flushing = null;
182
+ }
183
+ }
184
+ async close() {
185
+ this.closed = true;
186
+ if (this.timer !== null) {
187
+ clearInterval(this.timer);
188
+ this.timer = null;
189
+ }
190
+ await this.flush();
191
+ }
192
+ get pending() {
193
+ return this.queue.length;
194
+ }
195
+ drain() {
196
+ const items = this.queue;
197
+ this.queue = [];
198
+ return items;
199
+ }
200
+ halveBatchSize() {
201
+ this.config.size = Math.max(1, Math.floor(this.config.size / 2));
202
+ }
203
+ async doFlush() {
204
+ while (this.queue.length > 0) {
205
+ const batch = this.queue.slice(0, this.config.size);
206
+ try {
207
+ await this.config.send(batch);
208
+ this.queue.splice(0, batch.length);
209
+ } catch {
210
+ return;
211
+ }
212
+ }
213
+ }
214
+ };
215
+
216
+ // ../core/src/before-send.ts
217
+ function runBeforeSend(item, fns) {
218
+ const pipeline = Array.isArray(fns) ? fns : [fns];
219
+ let current = item;
220
+ for (const fn of pipeline) {
221
+ if (current === null) return null;
222
+ current = fn(current);
223
+ }
224
+ return current;
225
+ }
226
+
227
+ // ../core/src/redact.ts
228
+ var SENSITIVE_PARAMS = [
229
+ "token",
230
+ "api_key",
231
+ "key",
232
+ "secret",
233
+ "password",
234
+ "access_token",
235
+ "refresh_token",
236
+ "authorization"
237
+ ];
238
+ function stripUrlParams(url, params) {
239
+ try {
240
+ const u = new URL(url);
241
+ for (const p of params) u.searchParams.delete(p);
242
+ return u.toString();
243
+ } catch {
244
+ return url;
245
+ }
246
+ }
247
+ function stripParamsInProperties(props, params) {
248
+ if (!props) return props;
249
+ const out = {};
250
+ for (const [k, v] of Object.entries(props)) {
251
+ if (typeof v === "string" && v.startsWith("http")) {
252
+ out[k] = stripUrlParams(v, params);
253
+ } else {
254
+ out[k] = v;
255
+ }
256
+ }
257
+ return out;
258
+ }
259
+ function redactKeysInProperties(props, keys) {
260
+ if (!props) return props;
261
+ const out = {};
262
+ for (const [k, v] of Object.entries(props)) {
263
+ out[k] = keys.includes(k) ? "[REDACTED]" : v;
264
+ }
265
+ return out;
266
+ }
267
+ function redact(options) {
268
+ const { stripParams, redactKeys, dropRoutes } = options;
269
+ return (event) => {
270
+ if (dropRoutes && dropRoutes.length > 0 && event.context?.url) {
271
+ try {
272
+ const pathname = new URL(String(event.context.url)).pathname;
273
+ for (const prefix of dropRoutes) {
274
+ if (pathname.startsWith(prefix)) return null;
275
+ }
276
+ } catch {
277
+ }
278
+ }
279
+ let ctx = event.context;
280
+ let props = event.properties;
281
+ let traits = event.traits;
282
+ if (stripParams && stripParams.length > 0) {
283
+ if (ctx?.url && typeof ctx.url === "string") {
284
+ ctx = { ...ctx, url: stripUrlParams(ctx.url, stripParams) };
285
+ }
286
+ props = stripParamsInProperties(props, stripParams);
287
+ traits = stripParamsInProperties(traits, stripParams);
288
+ }
289
+ if (redactKeys && redactKeys.length > 0) {
290
+ props = redactKeysInProperties(props, redactKeys);
291
+ traits = redactKeysInProperties(traits, redactKeys);
292
+ }
293
+ if (ctx !== event.context || props !== event.properties || traits !== event.traits) {
294
+ return { ...event, context: ctx, properties: props, traits };
295
+ }
296
+ return event;
297
+ };
298
+ }
299
+ function redactLog(options) {
300
+ const { redactKeys } = options;
301
+ return (log) => {
302
+ if (redactKeys && redactKeys.length > 0 && log.data) {
303
+ const data = redactKeysInProperties(log.data, redactKeys);
304
+ if (data !== log.data) {
305
+ return { ...log, data };
306
+ }
307
+ }
308
+ return log;
309
+ };
310
+ }
311
+
312
+ export {
313
+ Events,
314
+ TellError,
315
+ ConfigurationError,
316
+ ValidationError,
317
+ NetworkError,
318
+ ClosedError,
319
+ SerializationError,
320
+ validateApiKey,
321
+ validateEventName,
322
+ validateLogMessage,
323
+ validateUserId,
324
+ Batcher,
325
+ runBeforeSend,
326
+ SENSITIVE_PARAMS,
327
+ redact,
328
+ redactLog
329
+ };
330
+ //# sourceMappingURL=chunk-HYQWDHQT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../core/src/constants.ts","../../core/src/errors.ts","../../core/src/validation.ts","../../core/src/batcher.ts","../../core/src/before-send.ts","../../core/src/redact.ts"],"sourcesContent":["// Standard event names — typed constants matching the spec appendix A\n\nexport const Events = {\n // User Lifecycle\n UserSignedUp: \"User Signed Up\",\n UserSignedIn: \"User Signed In\",\n UserSignedOut: \"User Signed Out\",\n UserInvited: \"User Invited\",\n UserOnboarded: \"User Onboarded\",\n AuthenticationFailed: \"Authentication Failed\",\n PasswordReset: \"Password Reset\",\n TwoFactorEnabled: \"Two Factor Enabled\",\n TwoFactorDisabled: \"Two Factor Disabled\",\n\n // Revenue & Billing\n OrderCompleted: \"Order Completed\",\n OrderRefunded: \"Order Refunded\",\n OrderCanceled: \"Order Canceled\",\n PaymentFailed: \"Payment Failed\",\n PaymentMethodAdded: \"Payment Method Added\",\n PaymentMethodUpdated: \"Payment Method Updated\",\n PaymentMethodRemoved: \"Payment Method Removed\",\n\n // Subscription\n SubscriptionStarted: \"Subscription Started\",\n SubscriptionRenewed: \"Subscription Renewed\",\n SubscriptionPaused: \"Subscription Paused\",\n SubscriptionResumed: \"Subscription Resumed\",\n SubscriptionChanged: \"Subscription Changed\",\n SubscriptionCanceled: \"Subscription Canceled\",\n\n // Trial\n TrialStarted: \"Trial Started\",\n TrialEndingSoon: \"Trial Ending Soon\",\n TrialEnded: \"Trial Ended\",\n TrialConverted: \"Trial Converted\",\n\n // Shopping\n CartViewed: \"Cart Viewed\",\n CartUpdated: \"Cart Updated\",\n CartAbandoned: \"Cart Abandoned\",\n CheckoutStarted: \"Checkout Started\",\n CheckoutCompleted: \"Checkout Completed\",\n\n // Engagement\n PageViewed: \"Page Viewed\",\n FeatureUsed: \"Feature Used\",\n SearchPerformed: \"Search Performed\",\n FileUploaded: \"File Uploaded\",\n NotificationSent: \"Notification Sent\",\n NotificationClicked: \"Notification Clicked\",\n\n // Communication\n EmailSent: \"Email Sent\",\n EmailOpened: \"Email Opened\",\n EmailClicked: \"Email Clicked\",\n EmailBounced: \"Email Bounced\",\n EmailUnsubscribed: \"Email Unsubscribed\",\n SupportTicketCreated: \"Support Ticket Created\",\n SupportTicketResolved: \"Support Ticket Resolved\",\n} as const;\n\nexport type EventName = (typeof Events)[keyof typeof Events];\n","export class TellError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TellError\";\n }\n}\n\nexport class ConfigurationError extends TellError {\n constructor(message: string) {\n super(message);\n this.name = \"ConfigurationError\";\n }\n}\n\nexport class ValidationError extends TellError {\n public readonly field: string;\n\n constructor(field: string, message: string) {\n super(`${field}: ${message}`);\n this.name = \"ValidationError\";\n this.field = field;\n }\n}\n\nexport class NetworkError extends TellError {\n public readonly statusCode?: number;\n\n constructor(message: string, statusCode?: number) {\n super(message);\n this.name = \"NetworkError\";\n this.statusCode = statusCode;\n }\n}\n\nexport class ClosedError extends TellError {\n constructor() {\n super(\"Client is closed\");\n this.name = \"ClosedError\";\n }\n}\n\nexport class SerializationError extends TellError {\n constructor(message: string) {\n super(message);\n this.name = \"SerializationError\";\n }\n}\n","import { ConfigurationError, ValidationError } from \"./errors.js\";\n\nconst HEX_RE = /^[0-9a-fA-F]{32}$/;\nconst MAX_EVENT_NAME = 256;\nconst MAX_LOG_MESSAGE = 65_536;\n\nexport function validateApiKey(key: string): void {\n if (!key) {\n throw new ConfigurationError(\"apiKey is required\");\n }\n if (!HEX_RE.test(key)) {\n throw new ConfigurationError(\n \"apiKey must be exactly 32 hex characters\"\n );\n }\n}\n\nexport function validateEventName(name: unknown): void {\n if (typeof name !== \"string\" || name.length === 0) {\n throw new ValidationError(\"eventName\", \"must be a non-empty string\");\n }\n if (name.length > MAX_EVENT_NAME) {\n throw new ValidationError(\n \"eventName\",\n `must be at most ${MAX_EVENT_NAME} characters, got ${name.length}`\n );\n }\n}\n\nexport function validateLogMessage(message: unknown): void {\n if (typeof message !== \"string\" || message.length === 0) {\n throw new ValidationError(\"message\", \"must be a non-empty string\");\n }\n if (message.length > MAX_LOG_MESSAGE) {\n throw new ValidationError(\n \"message\",\n `must be at most ${MAX_LOG_MESSAGE} characters, got ${message.length}`\n );\n }\n}\n\nexport function validateUserId(id: unknown): void {\n if (typeof id !== \"string\" || id.length === 0) {\n throw new ValidationError(\"userId\", \"must be a non-empty string\");\n }\n}\n","export interface BatcherConfig<T> {\n size: number;\n interval: number; // ms\n maxQueueSize: number;\n send: (items: T[]) => Promise<void>;\n onOverflow?: () => void;\n}\n\nexport class Batcher<T> {\n private queue: T[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private closed = false;\n private flushing: Promise<void> | null = null;\n private config: BatcherConfig<T>;\n\n constructor(config: BatcherConfig<T>) {\n this.config = config;\n this.timer = setInterval(() => {\n if (this.queue.length > 0) {\n this.flush().catch(() => {});\n }\n }, config.interval);\n // Don't keep Node.js process alive just for flush timer\n if (this.timer && typeof (this.timer as any).unref === \"function\") {\n (this.timer as any).unref();\n }\n }\n\n add(item: T): void {\n if (this.closed) return;\n\n if (this.queue.length >= this.config.maxQueueSize) {\n this.queue.shift(); // drop oldest\n if (this.config.onOverflow) {\n this.config.onOverflow();\n }\n }\n\n this.queue.push(item);\n\n if (this.queue.length >= this.config.size) {\n this.flush().catch(() => {});\n }\n }\n\n async flush(): Promise<void> {\n // Prevent concurrent flushes\n if (this.flushing) {\n return this.flushing;\n }\n this.flushing = this.doFlush();\n try {\n await this.flushing;\n } finally {\n this.flushing = null;\n }\n }\n\n async close(): Promise<void> {\n this.closed = true;\n if (this.timer !== null) {\n clearInterval(this.timer);\n this.timer = null;\n }\n await this.flush();\n }\n\n get pending(): number {\n return this.queue.length;\n }\n\n drain(): T[] {\n const items = this.queue;\n this.queue = [];\n return items;\n }\n\n halveBatchSize(): void {\n this.config.size = Math.max(1, Math.floor(this.config.size / 2));\n }\n\n private async doFlush(): Promise<void> {\n while (this.queue.length > 0) {\n const batch = this.queue.slice(0, this.config.size);\n try {\n await this.config.send(batch);\n this.queue.splice(0, batch.length); // remove only on success\n } catch {\n return; // items stay in queue (e.g. 413 — batch size already halved)\n }\n }\n }\n}\n","/**\n * A function that transforms or drops an item before it is queued.\n * Return the (possibly modified) item, or null to drop it.\n */\nexport type BeforeSendFn<T> = (item: T) => T | null;\n\n/**\n * Run an item through a pipeline of beforeSend functions.\n * Returns the transformed item, or null if any function in the chain drops it.\n */\nexport function runBeforeSend<T>(\n item: T,\n fns: BeforeSendFn<T> | BeforeSendFn<T>[]\n): T | null {\n const pipeline = Array.isArray(fns) ? fns : [fns];\n let current: T | null = item;\n\n for (const fn of pipeline) {\n if (current === null) return null;\n current = fn(current);\n }\n\n return current;\n}\n","import type { BeforeSendFn } from \"./before-send.js\";\nimport type { JsonEvent, JsonLog, Properties } from \"./types.js\";\n\n/**\n * Common query-parameter names that often carry secrets or tokens.\n * Pass these (or a subset) to `stripParams` for quick sanitization.\n */\nexport const SENSITIVE_PARAMS: readonly string[] = [\n \"token\",\n \"api_key\",\n \"key\",\n \"secret\",\n \"password\",\n \"access_token\",\n \"refresh_token\",\n \"authorization\",\n] as const;\n\nexport interface RedactOptions {\n /** Query-parameter names to strip from URLs (context.url and URL-shaped property values). */\n stripParams?: string[];\n /** Property/trait keys whose values should be replaced with \"[REDACTED]\". */\n redactKeys?: string[];\n /** URL pathname prefixes — events whose context.url matches are dropped entirely. */\n dropRoutes?: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction stripUrlParams(url: string, params: string[]): string {\n try {\n const u = new URL(url);\n for (const p of params) u.searchParams.delete(p);\n return u.toString();\n } catch {\n return url; // not a valid URL — leave as-is\n }\n}\n\nfunction stripParamsInProperties(\n props: Properties | undefined,\n params: string[],\n): Properties | undefined {\n if (!props) return props;\n const out: Properties = {};\n for (const [k, v] of Object.entries(props)) {\n if (typeof v === \"string\" && v.startsWith(\"http\")) {\n out[k] = stripUrlParams(v, params);\n } else {\n out[k] = v;\n }\n }\n return out;\n}\n\nfunction redactKeysInProperties(\n props: Properties | undefined,\n keys: string[],\n): Properties | undefined {\n if (!props) return props;\n const out: Properties = {};\n for (const [k, v] of Object.entries(props)) {\n out[k] = keys.includes(k) ? \"[REDACTED]\" : v;\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Factory that returns a `beforeSend` hook for events.\n *\n * - `dropRoutes` — drops events whose `context.url` pathname starts with a prefix.\n * - `stripParams` — removes query params from `context.url` and URL-shaped values\n * in `properties` and `traits`.\n * - `redactKeys` — replaces matching keys in `properties` and `traits` with `\"[REDACTED]\"`.\n *\n * The returned function never mutates the input event.\n */\nexport function redact(options: RedactOptions): BeforeSendFn<JsonEvent> {\n const { stripParams, redactKeys, dropRoutes } = options;\n\n return (event: JsonEvent): JsonEvent | null => {\n // --- dropRoutes ---\n if (dropRoutes && dropRoutes.length > 0 && event.context?.url) {\n try {\n const pathname = new URL(String(event.context.url)).pathname;\n for (const prefix of dropRoutes) {\n if (pathname.startsWith(prefix)) return null;\n }\n } catch {\n // not a valid URL — skip drop check\n }\n }\n\n let ctx = event.context;\n let props = event.properties;\n let traits = event.traits;\n\n // --- stripParams ---\n if (stripParams && stripParams.length > 0) {\n if (ctx?.url && typeof ctx.url === \"string\") {\n ctx = { ...ctx, url: stripUrlParams(ctx.url, stripParams) };\n }\n props = stripParamsInProperties(props, stripParams);\n traits = stripParamsInProperties(traits, stripParams);\n }\n\n // --- redactKeys ---\n if (redactKeys && redactKeys.length > 0) {\n props = redactKeysInProperties(props, redactKeys);\n traits = redactKeysInProperties(traits, redactKeys);\n }\n\n // Return a shallow copy if anything changed\n if (ctx !== event.context || props !== event.properties || traits !== event.traits) {\n return { ...event, context: ctx, properties: props, traits: traits };\n }\n\n return event;\n };\n}\n\n/**\n * Factory that returns a `beforeSend` hook for log entries.\n *\n * - `redactKeys` — replaces matching keys in `log.data` with `\"[REDACTED]\"`.\n *\n * The returned function never mutates the input log.\n */\nexport function redactLog(\n options: Pick<RedactOptions, \"redactKeys\">,\n): BeforeSendFn<JsonLog> {\n const { redactKeys } = options;\n\n return (log: JsonLog): JsonLog | null => {\n if (redactKeys && redactKeys.length > 0 && log.data) {\n const data = redactKeysInProperties(log.data, redactKeys);\n if (data !== log.data) {\n return { ...log, data };\n }\n }\n return log;\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,81 @@
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/events.ts
21
+ var events_exports = {};
22
+ __export(events_exports, {
23
+ Events: () => Events
24
+ });
25
+ module.exports = __toCommonJS(events_exports);
26
+
27
+ // ../core/src/constants.ts
28
+ var Events = {
29
+ // User Lifecycle
30
+ UserSignedUp: "User Signed Up",
31
+ UserSignedIn: "User Signed In",
32
+ UserSignedOut: "User Signed Out",
33
+ UserInvited: "User Invited",
34
+ UserOnboarded: "User Onboarded",
35
+ AuthenticationFailed: "Authentication Failed",
36
+ PasswordReset: "Password Reset",
37
+ TwoFactorEnabled: "Two Factor Enabled",
38
+ TwoFactorDisabled: "Two Factor Disabled",
39
+ // Revenue & Billing
40
+ OrderCompleted: "Order Completed",
41
+ OrderRefunded: "Order Refunded",
42
+ OrderCanceled: "Order Canceled",
43
+ PaymentFailed: "Payment Failed",
44
+ PaymentMethodAdded: "Payment Method Added",
45
+ PaymentMethodUpdated: "Payment Method Updated",
46
+ PaymentMethodRemoved: "Payment Method Removed",
47
+ // Subscription
48
+ SubscriptionStarted: "Subscription Started",
49
+ SubscriptionRenewed: "Subscription Renewed",
50
+ SubscriptionPaused: "Subscription Paused",
51
+ SubscriptionResumed: "Subscription Resumed",
52
+ SubscriptionChanged: "Subscription Changed",
53
+ SubscriptionCanceled: "Subscription Canceled",
54
+ // Trial
55
+ TrialStarted: "Trial Started",
56
+ TrialEndingSoon: "Trial Ending Soon",
57
+ TrialEnded: "Trial Ended",
58
+ TrialConverted: "Trial Converted",
59
+ // Shopping
60
+ CartViewed: "Cart Viewed",
61
+ CartUpdated: "Cart Updated",
62
+ CartAbandoned: "Cart Abandoned",
63
+ CheckoutStarted: "Checkout Started",
64
+ CheckoutCompleted: "Checkout Completed",
65
+ // Engagement
66
+ PageViewed: "Page Viewed",
67
+ FeatureUsed: "Feature Used",
68
+ SearchPerformed: "Search Performed",
69
+ FileUploaded: "File Uploaded",
70
+ NotificationSent: "Notification Sent",
71
+ NotificationClicked: "Notification Clicked",
72
+ // Communication
73
+ EmailSent: "Email Sent",
74
+ EmailOpened: "Email Opened",
75
+ EmailClicked: "Email Clicked",
76
+ EmailBounced: "Email Bounced",
77
+ EmailUnsubscribed: "Email Unsubscribed",
78
+ SupportTicketCreated: "Support Ticket Created",
79
+ SupportTicketResolved: "Support Ticket Resolved"
80
+ };
81
+ //# sourceMappingURL=events.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/events.ts","../../core/src/constants.ts"],"sourcesContent":["export { Events, type EventName } from \"@tell-rs/core\";\n","// Standard event names — typed constants matching the spec appendix A\n\nexport const Events = {\n // User Lifecycle\n UserSignedUp: \"User Signed Up\",\n UserSignedIn: \"User Signed In\",\n UserSignedOut: \"User Signed Out\",\n UserInvited: \"User Invited\",\n UserOnboarded: \"User Onboarded\",\n AuthenticationFailed: \"Authentication Failed\",\n PasswordReset: \"Password Reset\",\n TwoFactorEnabled: \"Two Factor Enabled\",\n TwoFactorDisabled: \"Two Factor Disabled\",\n\n // Revenue & Billing\n OrderCompleted: \"Order Completed\",\n OrderRefunded: \"Order Refunded\",\n OrderCanceled: \"Order Canceled\",\n PaymentFailed: \"Payment Failed\",\n PaymentMethodAdded: \"Payment Method Added\",\n PaymentMethodUpdated: \"Payment Method Updated\",\n PaymentMethodRemoved: \"Payment Method Removed\",\n\n // Subscription\n SubscriptionStarted: \"Subscription Started\",\n SubscriptionRenewed: \"Subscription Renewed\",\n SubscriptionPaused: \"Subscription Paused\",\n SubscriptionResumed: \"Subscription Resumed\",\n SubscriptionChanged: \"Subscription Changed\",\n SubscriptionCanceled: \"Subscription Canceled\",\n\n // Trial\n TrialStarted: \"Trial Started\",\n TrialEndingSoon: \"Trial Ending Soon\",\n TrialEnded: \"Trial Ended\",\n TrialConverted: \"Trial Converted\",\n\n // Shopping\n CartViewed: \"Cart Viewed\",\n CartUpdated: \"Cart Updated\",\n CartAbandoned: \"Cart Abandoned\",\n CheckoutStarted: \"Checkout Started\",\n CheckoutCompleted: \"Checkout Completed\",\n\n // Engagement\n PageViewed: \"Page Viewed\",\n FeatureUsed: \"Feature Used\",\n SearchPerformed: \"Search Performed\",\n FileUploaded: \"File Uploaded\",\n NotificationSent: \"Notification Sent\",\n NotificationClicked: \"Notification Clicked\",\n\n // Communication\n EmailSent: \"Email Sent\",\n EmailOpened: \"Email Opened\",\n EmailClicked: \"Email Clicked\",\n EmailBounced: \"Email Bounced\",\n EmailUnsubscribed: \"Email Unsubscribed\",\n SupportTicketCreated: \"Support Ticket Created\",\n SupportTicketResolved: \"Support Ticket Resolved\",\n} as const;\n\nexport type EventName = (typeof Events)[keyof typeof Events];\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEO,IAAM,SAAS;AAAA;AAAA,EAEpB,cAAc;AAAA,EACd,cAAc;AAAA,EACd,eAAe;AAAA,EACf,aAAa;AAAA,EACb,eAAe;AAAA,EACf,sBAAsB;AAAA,EACtB,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,mBAAmB;AAAA;AAAA,EAGnB,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,eAAe;AAAA,EACf,eAAe;AAAA,EACf,oBAAoB;AAAA,EACpB,sBAAsB;AAAA,EACtB,sBAAsB;AAAA;AAAA,EAGtB,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,oBAAoB;AAAA,EACpB,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,sBAAsB;AAAA;AAAA,EAGtB,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,YAAY;AAAA,EACZ,gBAAgB;AAAA;AAAA,EAGhB,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,mBAAmB;AAAA;AAAA,EAGnB,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,iBAAiB;AAAA,EACjB,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,qBAAqB;AAAA;AAAA,EAGrB,WAAW;AAAA,EACX,aAAa;AAAA,EACb,cAAc;AAAA,EACd,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,sBAAsB;AAAA,EACtB,uBAAuB;AACzB;","names":[]}
@@ -0,0 +1 @@
1
+ export { EventName, Events } from '@tell-rs/core';
@@ -0,0 +1 @@
1
+ export { EventName, Events } from '@tell-rs/core';
package/dist/events.js ADDED
@@ -0,0 +1,7 @@
1
+ import {
2
+ Events
3
+ } from "./chunk-HYQWDHQT.js";
4
+ export {
5
+ Events
6
+ };
7
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/index.cjs CHANGED
@@ -24,12 +24,15 @@ __export(index_exports, {
24
24
  ConfigurationError: () => ConfigurationError,
25
25
  Events: () => Events,
26
26
  NetworkError: () => NetworkError,
27
+ SENSITIVE_PARAMS: () => SENSITIVE_PARAMS,
27
28
  SerializationError: () => SerializationError,
28
29
  TellError: () => TellError,
29
30
  ValidationError: () => ValidationError,
30
31
  default: () => index_default,
31
32
  development: () => development,
32
33
  production: () => production,
34
+ redact: () => redact,
35
+ redactLog: () => redactLog,
33
36
  tell: () => tell
34
37
  });
35
38
  module.exports = __toCommonJS(index_exports);
@@ -260,6 +263,91 @@ function runBeforeSend(item, fns) {
260
263
  return current;
261
264
  }
262
265
 
266
+ // ../core/src/redact.ts
267
+ var SENSITIVE_PARAMS = [
268
+ "token",
269
+ "api_key",
270
+ "key",
271
+ "secret",
272
+ "password",
273
+ "access_token",
274
+ "refresh_token",
275
+ "authorization"
276
+ ];
277
+ function stripUrlParams(url, params) {
278
+ try {
279
+ const u = new URL(url);
280
+ for (const p of params) u.searchParams.delete(p);
281
+ return u.toString();
282
+ } catch {
283
+ return url;
284
+ }
285
+ }
286
+ function stripParamsInProperties(props, params) {
287
+ if (!props) return props;
288
+ const out = {};
289
+ for (const [k, v] of Object.entries(props)) {
290
+ if (typeof v === "string" && v.startsWith("http")) {
291
+ out[k] = stripUrlParams(v, params);
292
+ } else {
293
+ out[k] = v;
294
+ }
295
+ }
296
+ return out;
297
+ }
298
+ function redactKeysInProperties(props, keys) {
299
+ if (!props) return props;
300
+ const out = {};
301
+ for (const [k, v] of Object.entries(props)) {
302
+ out[k] = keys.includes(k) ? "[REDACTED]" : v;
303
+ }
304
+ return out;
305
+ }
306
+ function redact(options) {
307
+ const { stripParams, redactKeys, dropRoutes } = options;
308
+ return (event) => {
309
+ if (dropRoutes && dropRoutes.length > 0 && event.context?.url) {
310
+ try {
311
+ const pathname = new URL(String(event.context.url)).pathname;
312
+ for (const prefix of dropRoutes) {
313
+ if (pathname.startsWith(prefix)) return null;
314
+ }
315
+ } catch {
316
+ }
317
+ }
318
+ let ctx = event.context;
319
+ let props = event.properties;
320
+ let traits = event.traits;
321
+ if (stripParams && stripParams.length > 0) {
322
+ if (ctx?.url && typeof ctx.url === "string") {
323
+ ctx = { ...ctx, url: stripUrlParams(ctx.url, stripParams) };
324
+ }
325
+ props = stripParamsInProperties(props, stripParams);
326
+ traits = stripParamsInProperties(traits, stripParams);
327
+ }
328
+ if (redactKeys && redactKeys.length > 0) {
329
+ props = redactKeysInProperties(props, redactKeys);
330
+ traits = redactKeysInProperties(traits, redactKeys);
331
+ }
332
+ if (ctx !== event.context || props !== event.properties || traits !== event.traits) {
333
+ return { ...event, context: ctx, properties: props, traits };
334
+ }
335
+ return event;
336
+ };
337
+ }
338
+ function redactLog(options) {
339
+ const { redactKeys } = options;
340
+ return (log) => {
341
+ if (redactKeys && redactKeys.length > 0 && log.data) {
342
+ const data = redactKeysInProperties(log.data, redactKeys);
343
+ if (data !== log.data) {
344
+ return { ...log, data };
345
+ }
346
+ }
347
+ return log;
348
+ };
349
+ }
350
+
263
351
  // src/config.ts
264
352
  var DEFAULTS = {
265
353
  endpoint: "https://collect.tell.app",
@@ -623,12 +711,12 @@ var BrowserTransport = class {
623
711
  lastError = new NetworkError("Browser is offline");
624
712
  continue;
625
713
  }
714
+ const controller = new AbortController();
715
+ const timer = setTimeout(
716
+ () => controller.abort(),
717
+ this.networkTimeout
718
+ );
626
719
  try {
627
- const controller = new AbortController();
628
- const timer = setTimeout(
629
- () => controller.abort(),
630
- this.networkTimeout
631
- );
632
720
  const response = await fetch(url, {
633
721
  method: "POST",
634
722
  headers,
@@ -636,7 +724,6 @@ var BrowserTransport = class {
636
724
  signal: controller.signal,
637
725
  keepalive: true
638
726
  });
639
- clearTimeout(timer);
640
727
  if (response.status === 202) {
641
728
  return;
642
729
  }
@@ -679,6 +766,8 @@ var BrowserTransport = class {
679
766
  return;
680
767
  }
681
768
  lastError = err instanceof Error ? err : new NetworkError(String(err));
769
+ } finally {
770
+ clearTimeout(timer);
682
771
  }
683
772
  }
684
773
  if (lastError && this.onError) {
@@ -1226,6 +1315,7 @@ var tell = {
1226
1315
  } catch (err) {
1227
1316
  reportError(err);
1228
1317
  }
1318
+ configured = false;
1229
1319
  },
1230
1320
  reset() {
1231
1321
  if (!configured) return;