@tell-rs/browser 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-HYQWDHQT.js → chunk-VENRJ443.js} +19 -28
- package/dist/chunk-VENRJ443.js.map +1 -0
- package/dist/events.js +1 -1
- package/dist/index.cjs +58 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +41 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-HYQWDHQT.js.map +0 -1
|
@@ -244,18 +244,6 @@ function stripUrlParams(url, params) {
|
|
|
244
244
|
return url;
|
|
245
245
|
}
|
|
246
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
247
|
function redactKeysInProperties(props, keys) {
|
|
260
248
|
if (!props) return props;
|
|
261
249
|
const out = {};
|
|
@@ -267,33 +255,36 @@ function redactKeysInProperties(props, keys) {
|
|
|
267
255
|
function redact(options) {
|
|
268
256
|
const { stripParams, redactKeys, dropRoutes } = options;
|
|
269
257
|
return (event) => {
|
|
270
|
-
if (dropRoutes && dropRoutes.length > 0 && event.
|
|
258
|
+
if (dropRoutes && dropRoutes.length > 0 && event.url) {
|
|
271
259
|
try {
|
|
272
|
-
const pathname = new URL(String(event.
|
|
260
|
+
const pathname = new URL(String(event.url)).pathname;
|
|
273
261
|
for (const prefix of dropRoutes) {
|
|
274
262
|
if (pathname.startsWith(prefix)) return null;
|
|
275
263
|
}
|
|
276
264
|
} catch {
|
|
277
265
|
}
|
|
278
266
|
}
|
|
279
|
-
let
|
|
280
|
-
let props = event.properties;
|
|
281
|
-
let traits = event.traits;
|
|
267
|
+
let result = event;
|
|
282
268
|
if (stripParams && stripParams.length > 0) {
|
|
283
|
-
|
|
284
|
-
|
|
269
|
+
for (const [k, v] of Object.entries(event)) {
|
|
270
|
+
if (typeof v === "string" && v.startsWith("http")) {
|
|
271
|
+
const stripped = stripUrlParams(v, stripParams);
|
|
272
|
+
if (stripped !== v) {
|
|
273
|
+
if (result === event) result = { ...event };
|
|
274
|
+
result[k] = stripped;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
285
277
|
}
|
|
286
|
-
props = stripParamsInProperties(props, stripParams);
|
|
287
|
-
traits = stripParamsInProperties(traits, stripParams);
|
|
288
278
|
}
|
|
289
279
|
if (redactKeys && redactKeys.length > 0) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
280
|
+
for (const [k, v] of Object.entries(event)) {
|
|
281
|
+
if (redactKeys.includes(k)) {
|
|
282
|
+
if (result === event) result = { ...event };
|
|
283
|
+
result[k] = "[REDACTED]";
|
|
284
|
+
}
|
|
285
|
+
}
|
|
295
286
|
}
|
|
296
|
-
return
|
|
287
|
+
return result;
|
|
297
288
|
};
|
|
298
289
|
}
|
|
299
290
|
function redactLog(options) {
|
|
@@ -327,4 +318,4 @@ export {
|
|
|
327
318
|
redact,
|
|
328
319
|
redactLog
|
|
329
320
|
};
|
|
330
|
-
//# sourceMappingURL=chunk-
|
|
321
|
+
//# sourceMappingURL=chunk-VENRJ443.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 URL-shaped values. */\n stripParams?: string[];\n /** Keys whose values should be replaced with \"[REDACTED]\". */\n redactKeys?: string[];\n /** URL pathname prefixes — events whose 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 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 `url` pathname starts with a prefix.\n * - `stripParams` — removes query params from URL-shaped string values.\n * - `redactKeys` — replaces matching keys 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.url) {\n try {\n const pathname = new URL(String(event.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 result: JsonEvent = event;\n\n // --- stripParams ---\n if (stripParams && stripParams.length > 0) {\n for (const [k, v] of Object.entries(event)) {\n if (typeof v === \"string\" && v.startsWith(\"http\")) {\n const stripped = stripUrlParams(v, stripParams);\n if (stripped !== v) {\n if (result === event) result = { ...event };\n result[k] = stripped;\n }\n }\n }\n }\n\n // --- redactKeys ---\n if (redactKeys && redactKeys.length > 0) {\n for (const [k, v] of Object.entries(event)) {\n if (redactKeys.includes(k)) {\n if (result === event) result = { ...event };\n result[k] = \"[REDACTED]\";\n }\n }\n }\n\n return result;\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":";AAEO,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;;;AC5DO,IAAM,YAAN,cAAwB,MAAM;AAAA,EACnC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,qBAAN,cAAiC,UAAU;AAAA,EAChD,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,kBAAN,cAA8B,UAAU;AAAA,EAC7B;AAAA,EAEhB,YAAY,OAAe,SAAiB;AAC1C,UAAM,GAAG,KAAK,KAAK,OAAO,EAAE;AAC5B,SAAK,OAAO;AACZ,SAAK,QAAQ;AAAA,EACf;AACF;AAEO,IAAM,eAAN,cAA2B,UAAU;AAAA,EAC1B;AAAA,EAEhB,YAAY,SAAiB,YAAqB;AAChD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,aAAa;AAAA,EACpB;AACF;AAEO,IAAM,cAAN,cAA0B,UAAU;AAAA,EACzC,cAAc;AACZ,UAAM,kBAAkB;AACxB,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,qBAAN,cAAiC,UAAU;AAAA,EAChD,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;;;AC5CA,IAAM,SAAS;AACf,IAAM,iBAAiB;AACvB,IAAM,kBAAkB;AAEjB,SAAS,eAAe,KAAmB;AAChD,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,mBAAmB,oBAAoB;AAAA,EACnD;AACA,MAAI,CAAC,OAAO,KAAK,GAAG,GAAG;AACrB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,kBAAkB,MAAqB;AACrD,MAAI,OAAO,SAAS,YAAY,KAAK,WAAW,GAAG;AACjD,UAAM,IAAI,gBAAgB,aAAa,4BAA4B;AAAA,EACrE;AACA,MAAI,KAAK,SAAS,gBAAgB;AAChC,UAAM,IAAI;AAAA,MACR;AAAA,MACA,mBAAmB,cAAc,oBAAoB,KAAK,MAAM;AAAA,IAClE;AAAA,EACF;AACF;AAEO,SAAS,mBAAmB,SAAwB;AACzD,MAAI,OAAO,YAAY,YAAY,QAAQ,WAAW,GAAG;AACvD,UAAM,IAAI,gBAAgB,WAAW,4BAA4B;AAAA,EACnE;AACA,MAAI,QAAQ,SAAS,iBAAiB;AACpC,UAAM,IAAI;AAAA,MACR;AAAA,MACA,mBAAmB,eAAe,oBAAoB,QAAQ,MAAM;AAAA,IACtE;AAAA,EACF;AACF;AAEO,SAAS,eAAe,IAAmB;AAChD,MAAI,OAAO,OAAO,YAAY,GAAG,WAAW,GAAG;AAC7C,UAAM,IAAI,gBAAgB,UAAU,4BAA4B;AAAA,EAClE;AACF;;;ACrCO,IAAM,UAAN,MAAiB;AAAA,EACd,QAAa,CAAC;AAAA,EACd,QAA+C;AAAA,EAC/C,SAAS;AAAA,EACT,WAAiC;AAAA,EACjC;AAAA,EAER,YAAY,QAA0B;AACpC,SAAK,SAAS;AACd,SAAK,QAAQ,YAAY,MAAM;AAC7B,UAAI,KAAK,MAAM,SAAS,GAAG;AACzB,aAAK,MAAM,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAC7B;AAAA,IACF,GAAG,OAAO,QAAQ;AAElB,QAAI,KAAK,SAAS,OAAQ,KAAK,MAAc,UAAU,YAAY;AACjE,MAAC,KAAK,MAAc,MAAM;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,IAAI,MAAe;AACjB,QAAI,KAAK,OAAQ;AAEjB,QAAI,KAAK,MAAM,UAAU,KAAK,OAAO,cAAc;AACjD,WAAK,MAAM,MAAM;AACjB,UAAI,KAAK,OAAO,YAAY;AAC1B,aAAK,OAAO,WAAW;AAAA,MACzB;AAAA,IACF;AAEA,SAAK,MAAM,KAAK,IAAI;AAEpB,QAAI,KAAK,MAAM,UAAU,KAAK,OAAO,MAAM;AACzC,WAAK,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAE3B,QAAI,KAAK,UAAU;AACjB,aAAO,KAAK;AAAA,IACd;AACA,SAAK,WAAW,KAAK,QAAQ;AAC7B,QAAI;AACF,YAAM,KAAK;AAAA,IACb,UAAE;AACA,WAAK,WAAW;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAC3B,SAAK,SAAS;AACd,QAAI,KAAK,UAAU,MAAM;AACvB,oBAAc,KAAK,KAAK;AACxB,WAAK,QAAQ;AAAA,IACf;AACA,UAAM,KAAK,MAAM;AAAA,EACnB;AAAA,EAEA,IAAI,UAAkB;AACpB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,QAAa;AACX,UAAM,QAAQ,KAAK;AACnB,SAAK,QAAQ,CAAC;AACd,WAAO;AAAA,EACT;AAAA,EAEA,iBAAuB;AACrB,SAAK,OAAO,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,OAAO,OAAO,CAAC,CAAC;AAAA,EACjE;AAAA,EAEA,MAAc,UAAyB;AACrC,WAAO,KAAK,MAAM,SAAS,GAAG;AAC5B,YAAM,QAAQ,KAAK,MAAM,MAAM,GAAG,KAAK,OAAO,IAAI;AAClD,UAAI;AACF,cAAM,KAAK,OAAO,KAAK,KAAK;AAC5B,aAAK,MAAM,OAAO,GAAG,MAAM,MAAM;AAAA,MACnC,QAAQ;AACN;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AClFO,SAAS,cACd,MACA,KACU;AACV,QAAM,WAAW,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,GAAG;AAChD,MAAI,UAAoB;AAExB,aAAW,MAAM,UAAU;AACzB,QAAI,YAAY,KAAM,QAAO;AAC7B,cAAU,GAAG,OAAO;AAAA,EACtB;AAEA,SAAO;AACT;;;AChBO,IAAM,mBAAsC;AAAA,EACjD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAeA,SAAS,eAAe,KAAa,QAA0B;AAC7D,MAAI;AACF,UAAM,IAAI,IAAI,IAAI,GAAG;AACrB,eAAW,KAAK,OAAQ,GAAE,aAAa,OAAO,CAAC;AAC/C,WAAO,EAAE,SAAS;AAAA,EACpB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,uBACP,OACA,MACwB;AACxB,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,MAAkB,CAAC;AACzB,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,QAAI,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI,eAAe;AAAA,EAC7C;AACA,SAAO;AACT;AAeO,SAAS,OAAO,SAAiD;AACtE,QAAM,EAAE,aAAa,YAAY,WAAW,IAAI;AAEhD,SAAO,CAAC,UAAuC;AAE7C,QAAI,cAAc,WAAW,SAAS,KAAK,MAAM,KAAK;AACpD,UAAI;AACF,cAAM,WAAW,IAAI,IAAI,OAAO,MAAM,GAAG,CAAC,EAAE;AAC5C,mBAAW,UAAU,YAAY;AAC/B,cAAI,SAAS,WAAW,MAAM,EAAG,QAAO;AAAA,QAC1C;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,QAAI,SAAoB;AAGxB,QAAI,eAAe,YAAY,SAAS,GAAG;AACzC,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,YAAI,OAAO,MAAM,YAAY,EAAE,WAAW,MAAM,GAAG;AACjD,gBAAM,WAAW,eAAe,GAAG,WAAW;AAC9C,cAAI,aAAa,GAAG;AAClB,gBAAI,WAAW,MAAO,UAAS,EAAE,GAAG,MAAM;AAC1C,mBAAO,CAAC,IAAI;AAAA,UACd;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,QAAI,cAAc,WAAW,SAAS,GAAG;AACvC,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,YAAI,WAAW,SAAS,CAAC,GAAG;AAC1B,cAAI,WAAW,MAAO,UAAS,EAAE,GAAG,MAAM;AAC1C,iBAAO,CAAC,IAAI;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AASO,SAAS,UACd,SACuB;AACvB,QAAM,EAAE,WAAW,IAAI;AAEvB,SAAO,CAAC,QAAiC;AACvC,QAAI,cAAc,WAAW,SAAS,KAAK,IAAI,MAAM;AACnD,YAAM,OAAO,uBAAuB,IAAI,MAAM,UAAU;AACxD,UAAI,SAAS,IAAI,MAAM;AACrB,eAAO,EAAE,GAAG,KAAK,KAAK;AAAA,MACxB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;","names":[]}
|
package/dist/events.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -283,18 +283,6 @@ function stripUrlParams(url, params) {
|
|
|
283
283
|
return url;
|
|
284
284
|
}
|
|
285
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
286
|
function redactKeysInProperties(props, keys) {
|
|
299
287
|
if (!props) return props;
|
|
300
288
|
const out = {};
|
|
@@ -306,33 +294,36 @@ function redactKeysInProperties(props, keys) {
|
|
|
306
294
|
function redact(options) {
|
|
307
295
|
const { stripParams, redactKeys, dropRoutes } = options;
|
|
308
296
|
return (event) => {
|
|
309
|
-
if (dropRoutes && dropRoutes.length > 0 && event.
|
|
297
|
+
if (dropRoutes && dropRoutes.length > 0 && event.url) {
|
|
310
298
|
try {
|
|
311
|
-
const pathname = new URL(String(event.
|
|
299
|
+
const pathname = new URL(String(event.url)).pathname;
|
|
312
300
|
for (const prefix of dropRoutes) {
|
|
313
301
|
if (pathname.startsWith(prefix)) return null;
|
|
314
302
|
}
|
|
315
303
|
} catch {
|
|
316
304
|
}
|
|
317
305
|
}
|
|
318
|
-
let
|
|
319
|
-
let props = event.properties;
|
|
320
|
-
let traits = event.traits;
|
|
306
|
+
let result = event;
|
|
321
307
|
if (stripParams && stripParams.length > 0) {
|
|
322
|
-
|
|
323
|
-
|
|
308
|
+
for (const [k, v] of Object.entries(event)) {
|
|
309
|
+
if (typeof v === "string" && v.startsWith("http")) {
|
|
310
|
+
const stripped = stripUrlParams(v, stripParams);
|
|
311
|
+
if (stripped !== v) {
|
|
312
|
+
if (result === event) result = { ...event };
|
|
313
|
+
result[k] = stripped;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
324
316
|
}
|
|
325
|
-
props = stripParamsInProperties(props, stripParams);
|
|
326
|
-
traits = stripParamsInProperties(traits, stripParams);
|
|
327
317
|
}
|
|
328
318
|
if (redactKeys && redactKeys.length > 0) {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
319
|
+
for (const [k, v] of Object.entries(event)) {
|
|
320
|
+
if (redactKeys.includes(k)) {
|
|
321
|
+
if (result === event) result = { ...event };
|
|
322
|
+
result[k] = "[REDACTED]";
|
|
323
|
+
}
|
|
324
|
+
}
|
|
334
325
|
}
|
|
335
|
-
return
|
|
326
|
+
return result;
|
|
336
327
|
};
|
|
337
328
|
}
|
|
338
329
|
function redactLog(options) {
|
|
@@ -476,7 +467,6 @@ function captureContext() {
|
|
|
476
467
|
ctx.browser_version = parsed.browserVersion;
|
|
477
468
|
ctx.os_name = parsed.os;
|
|
478
469
|
ctx.os_version = parsed.osVersion;
|
|
479
|
-
ctx.device_type = inferDeviceType(parsed.os);
|
|
480
470
|
ctx.locale = navigator.language;
|
|
481
471
|
if ("hardwareConcurrency" in navigator) {
|
|
482
472
|
ctx.cpu_cores = navigator.hardwareConcurrency;
|
|
@@ -487,6 +477,7 @@ function captureContext() {
|
|
|
487
477
|
if ("maxTouchPoints" in navigator) {
|
|
488
478
|
ctx.touch = navigator.maxTouchPoints > 0;
|
|
489
479
|
}
|
|
480
|
+
ctx.device_type = inferDeviceType(parsed.os, ua, ctx.touch);
|
|
490
481
|
const conn = navigator.connection;
|
|
491
482
|
if (conn?.effectiveType) {
|
|
492
483
|
ctx.connection_type = conn.effectiveType;
|
|
@@ -522,8 +513,10 @@ function captureContext() {
|
|
|
522
513
|
}
|
|
523
514
|
return ctx;
|
|
524
515
|
}
|
|
525
|
-
function inferDeviceType(os) {
|
|
526
|
-
if (os === "
|
|
516
|
+
function inferDeviceType(os, ua, touch) {
|
|
517
|
+
if (os === "macOS" && touch) return "tablet";
|
|
518
|
+
if (os === "Android") return ua && !/Mobile/.test(ua) ? "tablet" : "mobile";
|
|
519
|
+
if (os === "iOS") return "mobile";
|
|
527
520
|
return "desktop";
|
|
528
521
|
}
|
|
529
522
|
function parseUA(ua) {
|
|
@@ -685,22 +678,31 @@ var BrowserTransport = class {
|
|
|
685
678
|
if (typeof navigator === "undefined" || !navigator.sendBeacon) return;
|
|
686
679
|
if (events.length > 0) {
|
|
687
680
|
const body = events.map((e) => JSON.stringify(e)).join("\n");
|
|
688
|
-
const blob = new Blob([body], { type: "
|
|
681
|
+
const blob = new Blob([body], { type: "text/plain" });
|
|
689
682
|
const url = `${this.endpoint}/v1/events?token=${encodeURIComponent(this.apiKey)}`;
|
|
690
683
|
navigator.sendBeacon(url, blob);
|
|
691
684
|
}
|
|
692
685
|
if (logs.length > 0) {
|
|
693
686
|
const body = logs.map((l) => JSON.stringify(l)).join("\n");
|
|
694
|
-
const blob = new Blob([body], { type: "
|
|
687
|
+
const blob = new Blob([body], { type: "text/plain" });
|
|
695
688
|
const url = `${this.endpoint}/v1/logs?token=${encodeURIComponent(this.apiKey)}`;
|
|
696
689
|
navigator.sendBeacon(url, blob);
|
|
697
690
|
}
|
|
698
691
|
}
|
|
692
|
+
resolvePort() {
|
|
693
|
+
try {
|
|
694
|
+
const u = new URL(this.endpoint);
|
|
695
|
+
if (u.port) return u.port;
|
|
696
|
+
return u.protocol === "https:" ? "443" : "80";
|
|
697
|
+
} catch {
|
|
698
|
+
return "unknown";
|
|
699
|
+
}
|
|
700
|
+
}
|
|
699
701
|
async send(path, body) {
|
|
700
|
-
const url = `${this.endpoint}${path}`;
|
|
702
|
+
const url = `${this.endpoint}${path}?token=${encodeURIComponent(this.apiKey)}`;
|
|
703
|
+
const port = this.resolvePort();
|
|
701
704
|
const headers = {
|
|
702
|
-
"Content-Type": "
|
|
703
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
705
|
+
"Content-Type": "text/plain"
|
|
704
706
|
};
|
|
705
707
|
let lastError;
|
|
706
708
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
@@ -708,7 +710,7 @@ var BrowserTransport = class {
|
|
|
708
710
|
await this.backoff(attempt);
|
|
709
711
|
}
|
|
710
712
|
if (typeof navigator !== "undefined" && !navigator.onLine) {
|
|
711
|
-
lastError = new NetworkError(
|
|
713
|
+
lastError = new NetworkError(`Browser is offline (endpoint: ${url}, port: ${port})`);
|
|
712
714
|
continue;
|
|
713
715
|
}
|
|
714
716
|
const controller = new AbortController();
|
|
@@ -754,7 +756,7 @@ var BrowserTransport = class {
|
|
|
754
756
|
);
|
|
755
757
|
}
|
|
756
758
|
lastError = new NetworkError(
|
|
757
|
-
`HTTP ${response.status}: ${response.statusText}`,
|
|
759
|
+
`HTTP ${response.status} from ${url} (port ${port}): ${response.statusText}`,
|
|
758
760
|
response.status
|
|
759
761
|
);
|
|
760
762
|
} catch (err) {
|
|
@@ -766,7 +768,12 @@ var BrowserTransport = class {
|
|
|
766
768
|
return;
|
|
767
769
|
}
|
|
768
770
|
if (err instanceof TypeError) {
|
|
769
|
-
if (this.onError)
|
|
771
|
+
if (this.onError)
|
|
772
|
+
this.onError(
|
|
773
|
+
new NetworkError(
|
|
774
|
+
`Failed to connect to ${url} (port ${port}): ${err.message}`
|
|
775
|
+
)
|
|
776
|
+
);
|
|
770
777
|
return;
|
|
771
778
|
}
|
|
772
779
|
lastError = err instanceof Error ? err : new NetworkError(String(err));
|
|
@@ -875,7 +882,8 @@ function onNewSession(reason, sessionId) {
|
|
|
875
882
|
session_id: sessionId,
|
|
876
883
|
user_id: userId,
|
|
877
884
|
timestamp: Date.now(),
|
|
878
|
-
|
|
885
|
+
reason,
|
|
886
|
+
...ctx
|
|
879
887
|
};
|
|
880
888
|
eventBatcher.add(event);
|
|
881
889
|
}
|
|
@@ -1020,7 +1028,8 @@ var tell = {
|
|
|
1020
1028
|
session_id: sessionManager.sessionId,
|
|
1021
1029
|
user_id: userId,
|
|
1022
1030
|
timestamp: Date.now(),
|
|
1023
|
-
|
|
1031
|
+
...superProperties,
|
|
1032
|
+
...properties
|
|
1024
1033
|
};
|
|
1025
1034
|
if (beforeSend) {
|
|
1026
1035
|
event = runBeforeSend(event, beforeSend);
|
|
@@ -1053,7 +1062,7 @@ var tell = {
|
|
|
1053
1062
|
session_id: sessionManager.sessionId,
|
|
1054
1063
|
user_id: userId,
|
|
1055
1064
|
timestamp: Date.now(),
|
|
1056
|
-
traits
|
|
1065
|
+
...traits
|
|
1057
1066
|
};
|
|
1058
1067
|
if (beforeSend) {
|
|
1059
1068
|
event = runBeforeSend(event, beforeSend);
|
|
@@ -1085,7 +1094,8 @@ var tell = {
|
|
|
1085
1094
|
user_id: userId,
|
|
1086
1095
|
group_id: groupId,
|
|
1087
1096
|
timestamp: Date.now(),
|
|
1088
|
-
|
|
1097
|
+
...superProperties,
|
|
1098
|
+
...properties
|
|
1089
1099
|
};
|
|
1090
1100
|
if (beforeSend) {
|
|
1091
1101
|
event = runBeforeSend(event, beforeSend);
|
|
@@ -1119,13 +1129,11 @@ var tell = {
|
|
|
1119
1129
|
session_id: sessionManager.sessionId,
|
|
1120
1130
|
user_id: userId,
|
|
1121
1131
|
timestamp: Date.now(),
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
currency
|
|
1128
|
-
}
|
|
1132
|
+
...superProperties,
|
|
1133
|
+
...properties,
|
|
1134
|
+
order_id: orderId,
|
|
1135
|
+
amount,
|
|
1136
|
+
currency
|
|
1129
1137
|
};
|
|
1130
1138
|
if (beforeSend) {
|
|
1131
1139
|
event = runBeforeSend(event, beforeSend);
|
|
@@ -1158,7 +1166,7 @@ var tell = {
|
|
|
1158
1166
|
session_id: sessionManager.sessionId,
|
|
1159
1167
|
user_id: newUserId,
|
|
1160
1168
|
timestamp: Date.now(),
|
|
1161
|
-
|
|
1169
|
+
previous_id: previousId
|
|
1162
1170
|
};
|
|
1163
1171
|
if (beforeSend) {
|
|
1164
1172
|
event = runBeforeSend(event, beforeSend);
|