@scoperat/tracker 0.3.1 → 0.3.2
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.js +3 -6
- package/dist/index.js.map +1 -1
- package/dist/widget/index.js +3 -6
- package/dist/widget/index.js.map +1 -1
- package/dist/widget.global.js +1 -1
- package/dist/widget.global.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -356,13 +356,10 @@ var TrackerImpl = class {
|
|
|
356
356
|
userId: this.userId,
|
|
357
357
|
anonymousId: this.anonymousId,
|
|
358
358
|
orgId: this.orgId
|
|
359
|
-
}),
|
|
360
|
-
onEvaluation: (key, flag) => this.track("$flag_evaluation", {
|
|
361
|
-
flag_key: key,
|
|
362
|
-
flag_value: flag.value,
|
|
363
|
-
flag_type: flag.type,
|
|
364
|
-
flag_source: flag.source
|
|
365
359
|
})
|
|
360
|
+
// Flag evaluations are an internal system signal, not a product
|
|
361
|
+
// event — we deliberately don't emit `$flag_evaluation` into the
|
|
362
|
+
// user's event stream (the server also refuses to persist it).
|
|
366
363
|
});
|
|
367
364
|
}
|
|
368
365
|
/** The underlying flag evaluation client. Use for direct SDK integration. */
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/breadcrumbs.ts","../src/context.ts","../src/errors.ts","../src/flag-client.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["export interface Breadcrumb {\n type: string;\n category: string;\n message: string;\n timestamp: number;\n data?: Record<string, unknown>;\n}\n\nconst MAX_BREADCRUMBS = 20;\nconst MAX_TEXT_LENGTH = 120;\n\nlet buffer: Breadcrumb[] = [];\nlet installed = false;\n\nfunction push(crumb: Breadcrumb): void {\n buffer.push(crumb);\n if (buffer.length > MAX_BREADCRUMBS) {\n buffer = buffer.slice(-MAX_BREADCRUMBS);\n }\n}\n\nfunction truncate(str: string, max = MAX_TEXT_LENGTH): string {\n return str.length > max ? str.slice(0, max) + \"…\" : str;\n}\n\nexport function getBreadcrumbs(): Breadcrumb[] {\n return [...buffer];\n}\n\nexport function addBreadcrumb(crumb: Breadcrumb): void {\n push(crumb);\n}\n\nexport function startBreadcrumbCollection(): void {\n if (installed || typeof window === \"undefined\") return;\n installed = true;\n\n document.addEventListener(\n \"click\",\n (e) => {\n const target = e.target as HTMLElement | null;\n if (!target) return;\n const text =\n target.textContent?.trim() || target.getAttribute(\"aria-label\") || \"\";\n push({\n type: \"user\",\n category: \"click\",\n message: truncate(\n `${target.tagName.toLowerCase()}${text ? ` \"${text}\"` : \"\"}`,\n ),\n timestamp: Date.now(),\n data: {\n tagName: target.tagName.toLowerCase(),\n className: target.className\n ? truncate(target.className, 80)\n : undefined,\n },\n });\n },\n { capture: true },\n );\n\n const origPushState = history.pushState;\n history.pushState = function (...args) {\n const from = location.pathname;\n origPushState.apply(this, args);\n push({\n type: \"navigation\",\n category: \"navigation\",\n message: `${from} → ${location.pathname}`,\n timestamp: Date.now(),\n });\n };\n\n window.addEventListener(\"popstate\", () => {\n push({\n type: \"navigation\",\n category: \"navigation\",\n message: `popstate → ${location.pathname}`,\n timestamp: Date.now(),\n });\n });\n\n const wrapConsole = (level: \"error\" | \"warn\") => {\n const orig = console[level];\n console[level] = (...args: unknown[]) => {\n push({\n type: \"console\",\n category: `console.${level}`,\n message: truncate(\n args.map((a) => (typeof a === \"string\" ? a : String(a))).join(\" \"),\n ),\n timestamp: Date.now(),\n });\n orig.apply(console, args);\n };\n };\n\n wrapConsole(\"error\");\n wrapConsole(\"warn\");\n}\n","/**\n * Auto-capture browser context: page, referrer, screen, locale, user agent.\n */\nexport function getBrowserContext(): Record<string, unknown> {\n if (typeof window === \"undefined\") {\n return {};\n }\n\n return {\n page: {\n url: window.location.href,\n path: window.location.pathname,\n title: document.title,\n referrer: document.referrer || undefined,\n },\n screen: {\n width: window.screen.width,\n height: window.screen.height,\n },\n locale: navigator.language,\n userAgent: navigator.userAgent,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n };\n}\n","interface TrackerLike {\n track(event: string, properties?: Record<string, unknown>): void;\n}\n\ninterface ErrorProperties {\n type: string;\n message: string;\n stack: string | undefined;\n handled: boolean;\n mechanism: string;\n source: \"browser\";\n filename?: string;\n lineno?: number;\n colno?: number;\n}\n\ntype BreadcrumbGetter = () => Array<{\n type: string;\n category: string;\n message: string;\n timestamp: number;\n data?: Record<string, unknown>;\n}>;\n\nconst DEDUP_COOLDOWN_MS = 60_000;\nconst dedupMap = new Map<string, number>();\n\nfunction simpleFingerprint(type: string, stack?: string): string {\n const firstFrame = stack?.split(\"\\n\")[1]?.trim() ?? \"\";\n return `${type}:${firstFrame}`;\n}\n\nfunction isDuplicate(fingerprint: string): boolean {\n const lastSent = dedupMap.get(fingerprint);\n if (lastSent && Date.now() - lastSent < DEDUP_COOLDOWN_MS) return true;\n dedupMap.set(fingerprint, Date.now());\n return false;\n}\n\nfunction sendException(\n tracker: TrackerLike,\n props: ErrorProperties,\n getBreadcrumbs: BreadcrumbGetter,\n): void {\n const fp = simpleFingerprint(props.type, props.stack);\n if (isDuplicate(fp)) return;\n\n tracker.track(\"$exception\", {\n ...props,\n context: { breadcrumbs: getBreadcrumbs() as unknown[] },\n });\n}\n\nexport function installGlobalHandlers(\n tracker: TrackerLike,\n getBreadcrumbs: BreadcrumbGetter,\n): void {\n if (typeof window === \"undefined\") return;\n\n window.onerror = (\n _message: string | Event,\n filename?: string,\n lineno?: number,\n colno?: number,\n error?: Error,\n ) => {\n const err = error ?? new Error(String(_message));\n sendException(\n tracker,\n {\n type: err.name || \"Error\",\n message: err.message || String(_message),\n stack: err.stack,\n handled: false,\n mechanism: \"onerror\",\n source: \"browser\",\n filename,\n lineno,\n colno,\n },\n getBreadcrumbs,\n );\n };\n\n window.onunhandledrejection = (event: PromiseRejectionEvent) => {\n const reason = event.reason;\n const err = reason instanceof Error ? reason : new Error(String(reason));\n sendException(\n tracker,\n {\n type: err.name || \"UnhandledRejection\",\n message: err.message,\n stack: err.stack,\n handled: false,\n mechanism: \"onunhandledrejection\",\n source: \"browser\",\n },\n getBreadcrumbs,\n );\n };\n}\n","import type { EvaluatedFlag } from \"./types.js\";\n\nconst DEFAULT_FLAG_POLL_INTERVAL = 60_000;\n\nexport interface FlagClientConfig {\n endpoint: string;\n writeKey: string;\n timeout?: number;\n environment?: string;\n pollInterval?: number;\n getIdentity: () => {\n userId?: string;\n anonymousId?: string;\n orgId?: string;\n };\n /** Called when a flag is first evaluated (for impression tracking). */\n onEvaluation?: (key: string, flag: EvaluatedFlag) => void;\n}\n\nexport class FlagClient {\n private endpoint: string;\n private writeKey: string;\n private timeout: number;\n private environment: string | undefined;\n private getIdentity: FlagClientConfig[\"getIdentity\"];\n private onEvaluation: FlagClientConfig[\"onEvaluation\"];\n\n private cache = new Map<string, EvaluatedFlag>();\n private ready = false;\n private readyCallbacks: Array<() => void> = [];\n private changeListeners = new Set<() => void>();\n private evalTracked = new Set<string>();\n private timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(config: FlagClientConfig) {\n this.endpoint = config.endpoint;\n this.writeKey = config.writeKey;\n this.timeout = config.timeout ?? 10_000;\n this.environment = config.environment;\n this.getIdentity = config.getIdentity;\n this.onEvaluation = config.onEvaluation;\n\n const interval = config.pollInterval ?? DEFAULT_FLAG_POLL_INTERVAL;\n this.fetch();\n this.timer = setInterval(() => this.fetch(), interval);\n }\n\n getFlag<T = unknown>(key: string, defaultValue: T): T {\n const flag = this.cache.get(key);\n if (!flag) return defaultValue;\n\n if (!this.evalTracked.has(key)) {\n this.evalTracked.add(key);\n this.onEvaluation?.(key, flag);\n }\n\n return flag.value as T;\n }\n\n onFlagsReady(callback: () => void): void {\n if (this.ready) {\n callback();\n } else {\n this.readyCallbacks.push(callback);\n }\n }\n\n onFlagChange(listener: () => void): () => void {\n this.changeListeners.add(listener);\n return () => this.changeListeners.delete(listener);\n }\n\n async refresh(): Promise<void> {\n await this.fetch();\n }\n\n destroy(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n }\n\n private async fetch(): Promise<void> {\n if (!this.writeKey || !this.endpoint) return;\n\n const params = new URLSearchParams();\n if (this.environment) params.set(\"environment\", this.environment);\n const identity = this.getIdentity();\n const identifier = identity.userId ?? identity.anonymousId;\n if (identifier) params.set(\"identifier\", identifier);\n if (identity.orgId) params.set(\"orgId\", identity.orgId);\n\n const qs = params.toString();\n const url = `${this.endpoint}/api/flags${qs ? `?${qs}` : \"\"}`;\n\n try {\n const res = await globalThis.fetch(url, {\n headers: { Authorization: `Bearer ${this.writeKey}` },\n signal: AbortSignal.timeout(this.timeout),\n });\n if (!res.ok) return;\n\n const json = (await res.json()) as {\n flags: Record<string, EvaluatedFlag>;\n };\n\n this.cache.clear();\n for (const [key, flag] of Object.entries(json.flags)) {\n this.cache.set(key, flag);\n }\n\n if (!this.ready) {\n this.ready = true;\n for (const cb of this.readyCallbacks) {\n try {\n cb();\n } catch {\n // ignore callback errors\n }\n }\n this.readyCallbacks = [];\n }\n\n for (const listener of this.changeListeners) {\n try {\n listener();\n } catch {\n // ignore listener errors\n }\n }\n } catch {\n // Silently fail — flags use cached values or defaults\n }\n }\n}\n","import {\n addBreadcrumb,\n getBreadcrumbs,\n startBreadcrumbCollection,\n} from \"./breadcrumbs.js\";\nimport { getBrowserContext } from \"./context.js\";\nimport { installGlobalHandlers } from \"./errors.js\";\nimport { FlagClient } from \"./flag-client.js\";\nimport type {\n FlushResult,\n IdentifyOptions,\n IdentifyTraits,\n InitOptions,\n TrackEvent,\n} from \"./types.js\";\n\nconst DEFAULT_FLUSH_AT = 10;\nconst DEFAULT_FLUSH_INTERVAL = 3_000;\nconst DEFAULT_TIMEOUT = 10_000;\nconst DEFAULT_SESSION_TIMEOUT = 30 * 60 * 1_000; // 30 minutes\nconst MAX_BATCH_SIZE = 100;\nconst ANON_ID_KEY = \"__scoperat_anon_id\";\nconst SESSION_ID_KEY = \"__scoperat_session_id\";\nconst LAST_ACTIVITY_KEY = \"__scoperat_last_activity\";\n/**\n * Last identified user id, persisted so we can tell a genuine login /\n * account switch (→ start a fresh session) apart from a re-identify of\n * the same user across a page reload (→ keep the current session).\n */\nconst USER_ID_KEY = \"__scoperat_user_id\";\n\n/**\n * Lifecycle events emitted as a session begins and ends, mirroring the\n * `session_start` / `session_end` convention used by analytics platforms\n * like GA4 and Amplitude. A session ends on logout, on an explicit\n * `startSession`, or after `sessionTimeout` of inactivity (detected lazily\n * on the next activity, backdated to the last recorded activity).\n */\nconst SESSION_START_EVENT = \"$session_start\";\nconst SESSION_END_EVENT = \"$session_end\";\n\n/** Why a session ended / began, attached as the event's `reason` property. */\nexport type SessionReason = \"initial\" | \"login\" | \"logout\" | \"timeout\";\n/**\n * Storage key for the anonymous support-ticket session token minted\n * by the server via `POST /ticket/session`. Stored as\n * `{ anonymousId, token, expiresAt }` JSON. The widget replays this\n * token on every ticket call so the server never has to trust a\n * client-supplied anonymous id (see `lib/ticket-session.ts` in the\n * API).\n */\nconst TICKET_SESSION_KEY = \"__scoperat_ticket_session\";\n\ninterface StoredTicketSession {\n anonymousId: string;\n token: string;\n expiresAt: number;\n}\n\nfunction generateAnonId(): string {\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nfunction getOrCreateAnonId(): string | undefined {\n if (typeof window === \"undefined\" || typeof localStorage === \"undefined\") {\n return undefined;\n }\n\n try {\n let id = localStorage.getItem(ANON_ID_KEY);\n if (!id) {\n id = generateAnonId();\n localStorage.setItem(ANON_ID_KEY, id);\n }\n return id;\n } catch {\n return generateAnonId();\n }\n}\n\nclass TrackerImpl {\n private writeKey: string | null = null;\n private endpoint: string = \"\";\n private flushAt: number = DEFAULT_FLUSH_AT;\n private flushInterval: number = DEFAULT_FLUSH_INTERVAL;\n private timeout: number = DEFAULT_TIMEOUT;\n private onError?: (error: Error, events: TrackEvent[]) => void;\n\n private buffer: TrackEvent[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private visibilityHandler: (() => void) | null = null;\n private userId: string | undefined;\n private orgId: string | undefined;\n private traits: IdentifyTraits = {};\n private userHash: string | undefined;\n private anonymousId: string | undefined;\n private sessionId: string | undefined;\n private sessionTimeout: number = DEFAULT_SESSION_TIMEOUT;\n private ticketSessionFetch: Promise<StoredTicketSession | null> | null = null;\n\n private _flagClient: FlagClient | null = null;\n\n init(writeKey: string, options: InitOptions = {}): void {\n this.writeKey = writeKey;\n this.endpoint = (\n options.endpoint ??\n globalThis.location?.origin ??\n \"\"\n ).replace(/\\/+$/, \"\");\n this.flushAt = options.flushAt ?? DEFAULT_FLUSH_AT;\n this.flushInterval = options.flushInterval ?? DEFAULT_FLUSH_INTERVAL;\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT;\n this.onError = options.onError;\n this.sessionTimeout = options.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT;\n\n this.anonymousId = getOrCreateAnonId();\n\n if (this.timer) clearInterval(this.timer);\n this.timer = setInterval(() => {\n if (this.buffer.length > 0) {\n this.flush().catch(() => {});\n }\n }, this.flushInterval);\n\n if (typeof window !== \"undefined\") {\n if (this.visibilityHandler) {\n window.removeEventListener(\"visibilitychange\", this.visibilityHandler);\n }\n this.visibilityHandler = () => {\n if (document.visibilityState === \"hidden\" && this.buffer.length > 0) {\n this.flushSync();\n }\n };\n window.addEventListener(\"visibilitychange\", this.visibilityHandler);\n }\n\n if (options.captureErrors) {\n installGlobalHandlers(this, getBreadcrumbs);\n startBreadcrumbCollection();\n }\n\n this._flagClient?.destroy();\n this._flagClient = new FlagClient({\n endpoint: this.endpoint,\n writeKey,\n timeout: this.timeout,\n environment: options.environment,\n pollInterval: options.flagPollInterval,\n getIdentity: () => ({\n userId: this.userId,\n anonymousId: this.anonymousId,\n orgId: this.orgId,\n }),\n onEvaluation: (key, flag) =>\n this.track(\"$flag_evaluation\", {\n flag_key: key,\n flag_value: flag.value,\n flag_type: flag.type,\n flag_source: flag.source,\n }),\n });\n }\n\n /** The underlying flag evaluation client. Use for direct SDK integration. */\n get flags(): FlagClient {\n if (!this._flagClient) {\n throw new Error(\"Tracker not initialized. Call init() first.\");\n }\n return this._flagClient;\n }\n\n identify(\n userId: string,\n traits?: IdentifyTraits,\n options?: IdentifyOptions,\n ): void {\n // Compare against the last identified user (in-memory, falling back to\n // the persisted id so a fresh page load doesn't look like a new login).\n const previousUserId = this.userId ?? this.readStoredUserId();\n\n this.userId = userId;\n if (traits) {\n this.traits = { ...traits };\n if (traits.orgId) this.orgId = traits.orgId;\n }\n // Switching from one identified user to another invalidates the\n // prior userHash — callers must either pass a new one or accept\n // that ticket calls degrade to the anonymous session channel.\n this.userHash = options?.userHash;\n\n for (const evt of this.buffer) {\n if (!evt.user_id) evt.user_id = userId;\n if (!evt.org_id && this.orgId) evt.org_id = this.orgId;\n }\n\n // A genuine login or account switch starts a fresh analytics session.\n // Re-identifying the same user (e.g. after a reload) keeps the\n // existing session so reloads don't fragment a single visit.\n if (userId !== previousUserId) {\n this.writeStoredUserId(userId);\n this.startSession(\"login\");\n }\n\n this._flagClient?.refresh();\n }\n\n reset(): void {\n // End the active session (logout) and best-effort deliver it before\n // the caller tears down / navigates away.\n this.endSession(\"logout\", true);\n this.userId = undefined;\n this.orgId = undefined;\n this.traits = {};\n this.userHash = undefined;\n this.anonymousId = getOrCreateAnonId();\n this.clearStoredUserId();\n this.clearTicketSession();\n }\n\n /**\n * Begin a new analytics session, ending the current one first if it\n * exists. Emits `$session_end` (for the old session) and\n * `$session_start` (for the new). Called automatically on login via\n * {@link identify}; also exposed for explicit control.\n */\n startSession(reason: SessionReason = \"login\"): string | undefined {\n if (typeof window === \"undefined\") return this.sessionId;\n\n const now = Date.now();\n const existingId = this.readStoredSession();\n if (existingId) {\n this.emitSessionEvent(SESSION_END_EVENT, existingId, reason);\n }\n\n const newId = generateAnonId();\n this.persistSession(newId, now);\n this.emitSessionEvent(SESSION_START_EVENT, newId, reason);\n return newId;\n }\n\n /**\n * End the current analytics session (if any), emitting `$session_end`,\n * and clear the stored session. The next tracked event will lazily\n * start a fresh session. Called automatically on logout via\n * {@link reset}; also exposed for explicit control.\n *\n * @param immediate - flush buffered events synchronously (via\n * `sendBeacon`) so the end event isn't lost if the page unloads.\n */\n endSession(reason: SessionReason = \"logout\", immediate = false): void {\n if (typeof window === \"undefined\") return;\n\n const existingId = this.readStoredSession();\n if (existingId) {\n this.emitSessionEvent(SESSION_END_EVENT, existingId, reason);\n }\n\n this.sessionId = undefined;\n this.clearSession();\n\n if (immediate) {\n this.flushSync();\n }\n }\n\n private readStoredUserId(): string | undefined {\n if (typeof localStorage === \"undefined\") return undefined;\n try {\n return localStorage.getItem(USER_ID_KEY) ?? undefined;\n } catch {\n return undefined;\n }\n }\n\n private writeStoredUserId(userId: string): void {\n if (typeof localStorage === \"undefined\") return;\n try {\n localStorage.setItem(USER_ID_KEY, userId);\n } catch {\n // ignore storage errors\n }\n }\n\n private clearStoredUserId(): void {\n if (typeof localStorage === \"undefined\") return;\n try {\n localStorage.removeItem(USER_ID_KEY);\n } catch {\n // ignore storage errors\n }\n }\n\n private clearTicketSession(): void {\n this.ticketSessionFetch = null;\n if (typeof localStorage === \"undefined\") return;\n try {\n localStorage.removeItem(TICKET_SESSION_KEY);\n } catch {\n // ignore storage errors\n }\n }\n\n private readStoredTicketSession(): StoredTicketSession | null {\n if (typeof localStorage === \"undefined\") return null;\n try {\n const raw = localStorage.getItem(TICKET_SESSION_KEY);\n if (!raw) return null;\n const parsed = JSON.parse(raw) as Partial<StoredTicketSession>;\n if (\n typeof parsed.anonymousId === \"string\" &&\n typeof parsed.token === \"string\" &&\n typeof parsed.expiresAt === \"number\" &&\n parsed.expiresAt > Date.now() + 60_000\n ) {\n return parsed as StoredTicketSession;\n }\n localStorage.removeItem(TICKET_SESSION_KEY);\n } catch {\n // fall through to network fetch\n }\n return null;\n }\n\n /**\n * @internal Used by the widget API helpers. Returns a cached or\n * newly-minted anonymous session token, or `null` if the server\n * has anonymous sessions disabled. Coalesces concurrent callers\n * onto a single in-flight network request so mount-time parallel\n * API calls don't each trigger a `POST /ticket/session`.\n */\n _ensureTicketSession(): Promise<StoredTicketSession | null> {\n const cached = this.readStoredTicketSession();\n if (cached) return Promise.resolve(cached);\n\n if (this.ticketSessionFetch) return this.ticketSessionFetch;\n if (!this.writeKey) return Promise.resolve(null);\n\n this.ticketSessionFetch = (async () => {\n try {\n const res = await fetch(`${this.endpoint}/api/ingest/ticket/session`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.writeKey}`,\n },\n body: \"{}\",\n signal: AbortSignal.timeout(this.timeout),\n });\n if (!res.ok) return null;\n const json = (await res.json()) as StoredTicketSession;\n if (\n typeof json?.anonymousId === \"string\" &&\n typeof json?.token === \"string\" &&\n typeof json?.expiresAt === \"number\"\n ) {\n try {\n localStorage?.setItem(TICKET_SESSION_KEY, JSON.stringify(json));\n } catch {\n // ignore storage errors\n }\n // Keep our in-memory anonymousId in sync with the\n // server-issued one so any code that reads `_getIdentity`\n // outside the ticket flow (e.g. analytics) sees a\n // consistent value.\n this.anonymousId = json.anonymousId;\n return json;\n }\n return null;\n } catch {\n return null;\n } finally {\n this.ticketSessionFetch = null;\n }\n })();\n\n return this.ticketSessionFetch;\n }\n\n /**\n * Return the id of the current session, rotating it when the previous\n * session has expired through inactivity. On rotation it emits a\n * `$session_end` for the timed-out session (backdated to the last\n * recorded activity) and a `$session_start` for the new one. Called on\n * every tracked event so sessions advance with real user activity.\n */\n private ensureSession(now: number): string | undefined {\n if (typeof window === \"undefined\") return this.sessionId;\n\n try {\n const lastActivity = this.readLastActivity();\n const existingId = this.readStoredSession();\n const expired =\n lastActivity === undefined || now - lastActivity > this.sessionTimeout;\n\n if (existingId && !expired) {\n this.sessionId = existingId;\n try {\n localStorage.setItem(LAST_ACTIVITY_KEY, String(now));\n } catch {\n // ignore storage errors\n }\n return existingId;\n }\n\n if (existingId) {\n // Previous session lapsed — close it, backdated to the moment of\n // last activity so the session duration reflects reality rather\n // than the gap until the user returned.\n this.emitSessionEvent(\n SESSION_END_EVENT,\n existingId,\n \"timeout\",\n lastActivity !== undefined\n ? new Date(lastActivity).toISOString()\n : undefined,\n );\n }\n\n const newId = generateAnonId();\n this.persistSession(newId, now);\n this.emitSessionEvent(\n SESSION_START_EVENT,\n newId,\n existingId ? \"timeout\" : \"initial\",\n );\n return newId;\n } catch {\n return this.sessionId ?? generateAnonId();\n }\n }\n\n private readStoredSession(): string | undefined {\n if (this.sessionId) return this.sessionId;\n if (typeof sessionStorage === \"undefined\") return undefined;\n try {\n return sessionStorage.getItem(SESSION_ID_KEY) ?? undefined;\n } catch {\n return undefined;\n }\n }\n\n private readLastActivity(): number | undefined {\n if (typeof localStorage === \"undefined\") return undefined;\n try {\n const raw = localStorage.getItem(LAST_ACTIVITY_KEY);\n return raw ? Number(raw) : undefined;\n } catch {\n return undefined;\n }\n }\n\n private persistSession(id: string, now: number): void {\n this.sessionId = id;\n if (typeof window === \"undefined\") return;\n try {\n sessionStorage.setItem(SESSION_ID_KEY, id);\n localStorage.setItem(LAST_ACTIVITY_KEY, String(now));\n } catch {\n // ignore storage errors\n }\n }\n\n /**\n * Enqueue a session lifecycle event already stamped with its session id\n * (so it never re-enters {@link ensureSession} and risks recursion).\n */\n private emitSessionEvent(\n event: string,\n sessionId: string,\n reason: SessionReason,\n occurredAt?: string,\n ): void {\n if (!this.writeKey) return;\n\n const traitsContext =\n Object.keys(this.traits).length > 0 ? { traits: this.traits } : {};\n\n this.buffer.push({\n event,\n properties: { reason },\n occurred_at: occurredAt ?? new Date().toISOString(),\n source: \"browser\",\n org_id: this.orgId,\n user_id: this.userId,\n anonymous_id: this.anonymousId,\n session_id: sessionId,\n context: {\n ...getBrowserContext(),\n ...traitsContext,\n },\n });\n }\n\n private clearSession(): void {\n if (typeof window === \"undefined\") return;\n try {\n sessionStorage.removeItem(SESSION_ID_KEY);\n localStorage.removeItem(LAST_ACTIVITY_KEY);\n } catch {\n // Ignore storage errors\n }\n }\n\n getSessionId(): string | undefined {\n return this.ensureSession(Date.now());\n }\n\n /** @internal Used by the widget to read tracker config. */\n _getEndpoint(): string {\n return this.endpoint;\n }\n\n /** @internal Used by the widget to read the write key. */\n _getWriteKey(): string | null {\n return this.writeKey;\n }\n\n /** @internal Used by the widget to read the current user identity. */\n _getIdentity(): {\n userId?: string;\n anonymousId?: string;\n email?: string;\n name?: string;\n userHash?: string;\n } {\n return {\n userId: this.userId,\n anonymousId: this.anonymousId,\n email: this.traits.email as string | undefined,\n name: this.traits.name as string | undefined,\n userHash: this.userHash,\n };\n }\n\n captureException(error: Error, context?: Record<string, unknown>): void {\n if (!this.writeKey) return;\n\n const breadcrumbs = getBreadcrumbs();\n this.track(\"$exception\", {\n type: error.name || \"Error\",\n message: error.message,\n stack: error.stack,\n handled: true,\n mechanism: (context?.mechanism as string) ?? \"manual\",\n source: \"browser\",\n ...context,\n context: { breadcrumbs: breadcrumbs as unknown[] },\n });\n }\n\n addBreadcrumb(\n category: string,\n message: string,\n data?: Record<string, unknown>,\n ): void {\n addBreadcrumb({\n type: \"custom\",\n category,\n message,\n timestamp: Date.now(),\n data,\n });\n }\n\n track(event: string, properties?: Record<string, unknown>): void;\n track(event: TrackEvent): void;\n track(\n eventOrName: string | TrackEvent,\n properties?: Record<string, unknown>,\n ): void {\n if (!this.writeKey) return;\n\n const evt: TrackEvent =\n typeof eventOrName === \"string\"\n ? { event: eventOrName, properties }\n : eventOrName;\n\n const traitsContext =\n Object.keys(this.traits).length > 0 ? { traits: this.traits } : {};\n\n const enriched: TrackEvent = {\n ...evt,\n occurred_at: evt.occurred_at ?? new Date().toISOString(),\n source: evt.source ?? \"browser\",\n org_id: evt.org_id ?? this.orgId,\n user_id: evt.user_id ?? this.userId,\n anonymous_id: evt.anonymous_id ?? this.anonymousId,\n session_id: evt.session_id ?? this.ensureSession(Date.now()),\n context: {\n ...getBrowserContext(),\n ...traitsContext,\n ...evt.context,\n },\n };\n\n this.buffer.push(enriched);\n\n if (this.buffer.length >= this.flushAt) {\n this.flush().catch(() => {});\n }\n }\n\n async flush(): Promise<FlushResult> {\n if (!this.writeKey || this.buffer.length === 0) {\n return { success: true, ingested: 0 };\n }\n\n const batch = this.buffer.splice(0, MAX_BATCH_SIZE);\n\n try {\n const res = await fetch(`${this.endpoint}/api/ingest/track`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.writeKey}`,\n },\n body: JSON.stringify({ events: batch }),\n signal: AbortSignal.timeout(this.timeout),\n });\n\n if (!res.ok) {\n const body = await res.text().catch(() => \"\");\n throw new Error(`API returned ${res.status}: ${body}`);\n }\n\n const json = (await res.json()) as { ingested: number };\n return { success: true, ingested: json.ingested };\n } catch (error) {\n this.buffer.unshift(...batch);\n const err = error instanceof Error ? error : new Error(String(error));\n this.onError?.(err, batch);\n return { success: false, ingested: 0 };\n }\n }\n\n /**\n * Fire-and-forget flush that survives page unload.\n *\n * Uses `fetch` with `keepalive` rather than `navigator.sendBeacon`:\n * beacons are always dispatched with credentials, and a JSON beacon is\n * preflighted — a credentialed preflighted request fails the CORS check\n * against the wildcard (`Access-Control-Allow-Origin: *`) ingest policy,\n * so the browser silently drops the beacon before it is ever sent. A\n * keepalive fetch is non-credentialed by default and carries the normal\n * Bearer header, giving it the exact CORS profile of the regular\n * `flush()` path while still outliving the page.\n */\n private flushSync(): void {\n if (!this.writeKey) return;\n if (typeof fetch === \"undefined\") return;\n if (this.buffer.length === 0) return;\n\n const batch = this.buffer.splice(0, MAX_BATCH_SIZE);\n\n try {\n void fetch(`${this.endpoint}/api/ingest/track`, {\n method: \"POST\",\n keepalive: true,\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.writeKey}`,\n },\n body: JSON.stringify({ events: batch }),\n }).catch(() => {\n // Best effort on unload\n });\n } catch {\n // Best effort on unload\n }\n }\n\n getFlag<T = unknown>(key: string, defaultValue: T): T {\n return this._flagClient?.getFlag(key, defaultValue) ?? defaultValue;\n }\n\n onFlagsReady(callback: () => void): void {\n this._flagClient?.onFlagsReady(callback);\n }\n\n onFlagChange(listener: () => void): () => void {\n return this._flagClient?.onFlagChange(listener) ?? (() => {});\n }\n\n async refreshFlags(): Promise<void> {\n await this._flagClient?.refresh();\n }\n\n async shutdown(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n\n this._flagClient?.destroy();\n this._flagClient = null;\n\n if (typeof window !== \"undefined\" && this.visibilityHandler) {\n window.removeEventListener(\"visibilitychange\", this.visibilityHandler);\n this.visibilityHandler = null;\n }\n\n while (this.buffer.length > 0) {\n await this.flush();\n }\n }\n}\n\nconst scoperat = new TrackerImpl();\nexport default scoperat;\n","import scoperat from \"./client.js\";\n\nexport default scoperat;\nexport { FlagClient } from \"./flag-client.js\";\nexport type {\n EvaluatedFlag,\n EventSource,\n FlushResult,\n IdentifyOptions,\n IdentifyTraits,\n InitOptions,\n TrackEvent,\n} from \"./types.js\";\n"],"mappings":"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","names":["getBreadcrumbs"]}
|
|
1
|
+
{"version":3,"sources":["../src/breadcrumbs.ts","../src/context.ts","../src/errors.ts","../src/flag-client.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["export interface Breadcrumb {\n type: string;\n category: string;\n message: string;\n timestamp: number;\n data?: Record<string, unknown>;\n}\n\nconst MAX_BREADCRUMBS = 20;\nconst MAX_TEXT_LENGTH = 120;\n\nlet buffer: Breadcrumb[] = [];\nlet installed = false;\n\nfunction push(crumb: Breadcrumb): void {\n buffer.push(crumb);\n if (buffer.length > MAX_BREADCRUMBS) {\n buffer = buffer.slice(-MAX_BREADCRUMBS);\n }\n}\n\nfunction truncate(str: string, max = MAX_TEXT_LENGTH): string {\n return str.length > max ? str.slice(0, max) + \"…\" : str;\n}\n\nexport function getBreadcrumbs(): Breadcrumb[] {\n return [...buffer];\n}\n\nexport function addBreadcrumb(crumb: Breadcrumb): void {\n push(crumb);\n}\n\nexport function startBreadcrumbCollection(): void {\n if (installed || typeof window === \"undefined\") return;\n installed = true;\n\n document.addEventListener(\n \"click\",\n (e) => {\n const target = e.target as HTMLElement | null;\n if (!target) return;\n const text =\n target.textContent?.trim() || target.getAttribute(\"aria-label\") || \"\";\n push({\n type: \"user\",\n category: \"click\",\n message: truncate(\n `${target.tagName.toLowerCase()}${text ? ` \"${text}\"` : \"\"}`,\n ),\n timestamp: Date.now(),\n data: {\n tagName: target.tagName.toLowerCase(),\n className: target.className\n ? truncate(target.className, 80)\n : undefined,\n },\n });\n },\n { capture: true },\n );\n\n const origPushState = history.pushState;\n history.pushState = function (...args) {\n const from = location.pathname;\n origPushState.apply(this, args);\n push({\n type: \"navigation\",\n category: \"navigation\",\n message: `${from} → ${location.pathname}`,\n timestamp: Date.now(),\n });\n };\n\n window.addEventListener(\"popstate\", () => {\n push({\n type: \"navigation\",\n category: \"navigation\",\n message: `popstate → ${location.pathname}`,\n timestamp: Date.now(),\n });\n });\n\n const wrapConsole = (level: \"error\" | \"warn\") => {\n const orig = console[level];\n console[level] = (...args: unknown[]) => {\n push({\n type: \"console\",\n category: `console.${level}`,\n message: truncate(\n args.map((a) => (typeof a === \"string\" ? a : String(a))).join(\" \"),\n ),\n timestamp: Date.now(),\n });\n orig.apply(console, args);\n };\n };\n\n wrapConsole(\"error\");\n wrapConsole(\"warn\");\n}\n","/**\n * Auto-capture browser context: page, referrer, screen, locale, user agent.\n */\nexport function getBrowserContext(): Record<string, unknown> {\n if (typeof window === \"undefined\") {\n return {};\n }\n\n return {\n page: {\n url: window.location.href,\n path: window.location.pathname,\n title: document.title,\n referrer: document.referrer || undefined,\n },\n screen: {\n width: window.screen.width,\n height: window.screen.height,\n },\n locale: navigator.language,\n userAgent: navigator.userAgent,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n };\n}\n","interface TrackerLike {\n track(event: string, properties?: Record<string, unknown>): void;\n}\n\ninterface ErrorProperties {\n type: string;\n message: string;\n stack: string | undefined;\n handled: boolean;\n mechanism: string;\n source: \"browser\";\n filename?: string;\n lineno?: number;\n colno?: number;\n}\n\ntype BreadcrumbGetter = () => Array<{\n type: string;\n category: string;\n message: string;\n timestamp: number;\n data?: Record<string, unknown>;\n}>;\n\nconst DEDUP_COOLDOWN_MS = 60_000;\nconst dedupMap = new Map<string, number>();\n\nfunction simpleFingerprint(type: string, stack?: string): string {\n const firstFrame = stack?.split(\"\\n\")[1]?.trim() ?? \"\";\n return `${type}:${firstFrame}`;\n}\n\nfunction isDuplicate(fingerprint: string): boolean {\n const lastSent = dedupMap.get(fingerprint);\n if (lastSent && Date.now() - lastSent < DEDUP_COOLDOWN_MS) return true;\n dedupMap.set(fingerprint, Date.now());\n return false;\n}\n\nfunction sendException(\n tracker: TrackerLike,\n props: ErrorProperties,\n getBreadcrumbs: BreadcrumbGetter,\n): void {\n const fp = simpleFingerprint(props.type, props.stack);\n if (isDuplicate(fp)) return;\n\n tracker.track(\"$exception\", {\n ...props,\n context: { breadcrumbs: getBreadcrumbs() as unknown[] },\n });\n}\n\nexport function installGlobalHandlers(\n tracker: TrackerLike,\n getBreadcrumbs: BreadcrumbGetter,\n): void {\n if (typeof window === \"undefined\") return;\n\n window.onerror = (\n _message: string | Event,\n filename?: string,\n lineno?: number,\n colno?: number,\n error?: Error,\n ) => {\n const err = error ?? new Error(String(_message));\n sendException(\n tracker,\n {\n type: err.name || \"Error\",\n message: err.message || String(_message),\n stack: err.stack,\n handled: false,\n mechanism: \"onerror\",\n source: \"browser\",\n filename,\n lineno,\n colno,\n },\n getBreadcrumbs,\n );\n };\n\n window.onunhandledrejection = (event: PromiseRejectionEvent) => {\n const reason = event.reason;\n const err = reason instanceof Error ? reason : new Error(String(reason));\n sendException(\n tracker,\n {\n type: err.name || \"UnhandledRejection\",\n message: err.message,\n stack: err.stack,\n handled: false,\n mechanism: \"onunhandledrejection\",\n source: \"browser\",\n },\n getBreadcrumbs,\n );\n };\n}\n","import type { EvaluatedFlag } from \"./types.js\";\n\nconst DEFAULT_FLAG_POLL_INTERVAL = 60_000;\n\nexport interface FlagClientConfig {\n endpoint: string;\n writeKey: string;\n timeout?: number;\n environment?: string;\n pollInterval?: number;\n getIdentity: () => {\n userId?: string;\n anonymousId?: string;\n orgId?: string;\n };\n /** Called when a flag is first evaluated (for impression tracking). */\n onEvaluation?: (key: string, flag: EvaluatedFlag) => void;\n}\n\nexport class FlagClient {\n private endpoint: string;\n private writeKey: string;\n private timeout: number;\n private environment: string | undefined;\n private getIdentity: FlagClientConfig[\"getIdentity\"];\n private onEvaluation: FlagClientConfig[\"onEvaluation\"];\n\n private cache = new Map<string, EvaluatedFlag>();\n private ready = false;\n private readyCallbacks: Array<() => void> = [];\n private changeListeners = new Set<() => void>();\n private evalTracked = new Set<string>();\n private timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(config: FlagClientConfig) {\n this.endpoint = config.endpoint;\n this.writeKey = config.writeKey;\n this.timeout = config.timeout ?? 10_000;\n this.environment = config.environment;\n this.getIdentity = config.getIdentity;\n this.onEvaluation = config.onEvaluation;\n\n const interval = config.pollInterval ?? DEFAULT_FLAG_POLL_INTERVAL;\n this.fetch();\n this.timer = setInterval(() => this.fetch(), interval);\n }\n\n getFlag<T = unknown>(key: string, defaultValue: T): T {\n const flag = this.cache.get(key);\n if (!flag) return defaultValue;\n\n if (!this.evalTracked.has(key)) {\n this.evalTracked.add(key);\n this.onEvaluation?.(key, flag);\n }\n\n return flag.value as T;\n }\n\n onFlagsReady(callback: () => void): void {\n if (this.ready) {\n callback();\n } else {\n this.readyCallbacks.push(callback);\n }\n }\n\n onFlagChange(listener: () => void): () => void {\n this.changeListeners.add(listener);\n return () => this.changeListeners.delete(listener);\n }\n\n async refresh(): Promise<void> {\n await this.fetch();\n }\n\n destroy(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n }\n\n private async fetch(): Promise<void> {\n if (!this.writeKey || !this.endpoint) return;\n\n const params = new URLSearchParams();\n if (this.environment) params.set(\"environment\", this.environment);\n const identity = this.getIdentity();\n const identifier = identity.userId ?? identity.anonymousId;\n if (identifier) params.set(\"identifier\", identifier);\n if (identity.orgId) params.set(\"orgId\", identity.orgId);\n\n const qs = params.toString();\n const url = `${this.endpoint}/api/flags${qs ? `?${qs}` : \"\"}`;\n\n try {\n const res = await globalThis.fetch(url, {\n headers: { Authorization: `Bearer ${this.writeKey}` },\n signal: AbortSignal.timeout(this.timeout),\n });\n if (!res.ok) return;\n\n const json = (await res.json()) as {\n flags: Record<string, EvaluatedFlag>;\n };\n\n this.cache.clear();\n for (const [key, flag] of Object.entries(json.flags)) {\n this.cache.set(key, flag);\n }\n\n if (!this.ready) {\n this.ready = true;\n for (const cb of this.readyCallbacks) {\n try {\n cb();\n } catch {\n // ignore callback errors\n }\n }\n this.readyCallbacks = [];\n }\n\n for (const listener of this.changeListeners) {\n try {\n listener();\n } catch {\n // ignore listener errors\n }\n }\n } catch {\n // Silently fail — flags use cached values or defaults\n }\n }\n}\n","import {\n addBreadcrumb,\n getBreadcrumbs,\n startBreadcrumbCollection,\n} from \"./breadcrumbs.js\";\nimport { getBrowserContext } from \"./context.js\";\nimport { installGlobalHandlers } from \"./errors.js\";\nimport { FlagClient } from \"./flag-client.js\";\nimport type {\n FlushResult,\n IdentifyOptions,\n IdentifyTraits,\n InitOptions,\n TrackEvent,\n} from \"./types.js\";\n\nconst DEFAULT_FLUSH_AT = 10;\nconst DEFAULT_FLUSH_INTERVAL = 3_000;\nconst DEFAULT_TIMEOUT = 10_000;\nconst DEFAULT_SESSION_TIMEOUT = 30 * 60 * 1_000; // 30 minutes\nconst MAX_BATCH_SIZE = 100;\nconst ANON_ID_KEY = \"__scoperat_anon_id\";\nconst SESSION_ID_KEY = \"__scoperat_session_id\";\nconst LAST_ACTIVITY_KEY = \"__scoperat_last_activity\";\n/**\n * Last identified user id, persisted so we can tell a genuine login /\n * account switch (→ start a fresh session) apart from a re-identify of\n * the same user across a page reload (→ keep the current session).\n */\nconst USER_ID_KEY = \"__scoperat_user_id\";\n\n/**\n * Lifecycle events emitted as a session begins and ends, mirroring the\n * `session_start` / `session_end` convention used by analytics platforms\n * like GA4 and Amplitude. A session ends on logout, on an explicit\n * `startSession`, or after `sessionTimeout` of inactivity (detected lazily\n * on the next activity, backdated to the last recorded activity).\n */\nconst SESSION_START_EVENT = \"$session_start\";\nconst SESSION_END_EVENT = \"$session_end\";\n\n/** Why a session ended / began, attached as the event's `reason` property. */\nexport type SessionReason = \"initial\" | \"login\" | \"logout\" | \"timeout\";\n/**\n * Storage key for the anonymous support-ticket session token minted\n * by the server via `POST /ticket/session`. Stored as\n * `{ anonymousId, token, expiresAt }` JSON. The widget replays this\n * token on every ticket call so the server never has to trust a\n * client-supplied anonymous id (see `lib/ticket-session.ts` in the\n * API).\n */\nconst TICKET_SESSION_KEY = \"__scoperat_ticket_session\";\n\ninterface StoredTicketSession {\n anonymousId: string;\n token: string;\n expiresAt: number;\n}\n\nfunction generateAnonId(): string {\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nfunction getOrCreateAnonId(): string | undefined {\n if (typeof window === \"undefined\" || typeof localStorage === \"undefined\") {\n return undefined;\n }\n\n try {\n let id = localStorage.getItem(ANON_ID_KEY);\n if (!id) {\n id = generateAnonId();\n localStorage.setItem(ANON_ID_KEY, id);\n }\n return id;\n } catch {\n return generateAnonId();\n }\n}\n\nclass TrackerImpl {\n private writeKey: string | null = null;\n private endpoint: string = \"\";\n private flushAt: number = DEFAULT_FLUSH_AT;\n private flushInterval: number = DEFAULT_FLUSH_INTERVAL;\n private timeout: number = DEFAULT_TIMEOUT;\n private onError?: (error: Error, events: TrackEvent[]) => void;\n\n private buffer: TrackEvent[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private visibilityHandler: (() => void) | null = null;\n private userId: string | undefined;\n private orgId: string | undefined;\n private traits: IdentifyTraits = {};\n private userHash: string | undefined;\n private anonymousId: string | undefined;\n private sessionId: string | undefined;\n private sessionTimeout: number = DEFAULT_SESSION_TIMEOUT;\n private ticketSessionFetch: Promise<StoredTicketSession | null> | null = null;\n\n private _flagClient: FlagClient | null = null;\n\n init(writeKey: string, options: InitOptions = {}): void {\n this.writeKey = writeKey;\n this.endpoint = (\n options.endpoint ??\n globalThis.location?.origin ??\n \"\"\n ).replace(/\\/+$/, \"\");\n this.flushAt = options.flushAt ?? DEFAULT_FLUSH_AT;\n this.flushInterval = options.flushInterval ?? DEFAULT_FLUSH_INTERVAL;\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT;\n this.onError = options.onError;\n this.sessionTimeout = options.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT;\n\n this.anonymousId = getOrCreateAnonId();\n\n if (this.timer) clearInterval(this.timer);\n this.timer = setInterval(() => {\n if (this.buffer.length > 0) {\n this.flush().catch(() => {});\n }\n }, this.flushInterval);\n\n if (typeof window !== \"undefined\") {\n if (this.visibilityHandler) {\n window.removeEventListener(\"visibilitychange\", this.visibilityHandler);\n }\n this.visibilityHandler = () => {\n if (document.visibilityState === \"hidden\" && this.buffer.length > 0) {\n this.flushSync();\n }\n };\n window.addEventListener(\"visibilitychange\", this.visibilityHandler);\n }\n\n if (options.captureErrors) {\n installGlobalHandlers(this, getBreadcrumbs);\n startBreadcrumbCollection();\n }\n\n this._flagClient?.destroy();\n this._flagClient = new FlagClient({\n endpoint: this.endpoint,\n writeKey,\n timeout: this.timeout,\n environment: options.environment,\n pollInterval: options.flagPollInterval,\n getIdentity: () => ({\n userId: this.userId,\n anonymousId: this.anonymousId,\n orgId: this.orgId,\n }),\n // Flag evaluations are an internal system signal, not a product\n // event — we deliberately don't emit `$flag_evaluation` into the\n // user's event stream (the server also refuses to persist it).\n });\n }\n\n /** The underlying flag evaluation client. Use for direct SDK integration. */\n get flags(): FlagClient {\n if (!this._flagClient) {\n throw new Error(\"Tracker not initialized. Call init() first.\");\n }\n return this._flagClient;\n }\n\n identify(\n userId: string,\n traits?: IdentifyTraits,\n options?: IdentifyOptions,\n ): void {\n // Compare against the last identified user (in-memory, falling back to\n // the persisted id so a fresh page load doesn't look like a new login).\n const previousUserId = this.userId ?? this.readStoredUserId();\n\n this.userId = userId;\n if (traits) {\n this.traits = { ...traits };\n if (traits.orgId) this.orgId = traits.orgId;\n }\n // Switching from one identified user to another invalidates the\n // prior userHash — callers must either pass a new one or accept\n // that ticket calls degrade to the anonymous session channel.\n this.userHash = options?.userHash;\n\n for (const evt of this.buffer) {\n if (!evt.user_id) evt.user_id = userId;\n if (!evt.org_id && this.orgId) evt.org_id = this.orgId;\n }\n\n // A genuine login or account switch starts a fresh analytics session.\n // Re-identifying the same user (e.g. after a reload) keeps the\n // existing session so reloads don't fragment a single visit.\n if (userId !== previousUserId) {\n this.writeStoredUserId(userId);\n this.startSession(\"login\");\n }\n\n this._flagClient?.refresh();\n }\n\n reset(): void {\n // End the active session (logout) and best-effort deliver it before\n // the caller tears down / navigates away.\n this.endSession(\"logout\", true);\n this.userId = undefined;\n this.orgId = undefined;\n this.traits = {};\n this.userHash = undefined;\n this.anonymousId = getOrCreateAnonId();\n this.clearStoredUserId();\n this.clearTicketSession();\n }\n\n /**\n * Begin a new analytics session, ending the current one first if it\n * exists. Emits `$session_end` (for the old session) and\n * `$session_start` (for the new). Called automatically on login via\n * {@link identify}; also exposed for explicit control.\n */\n startSession(reason: SessionReason = \"login\"): string | undefined {\n if (typeof window === \"undefined\") return this.sessionId;\n\n const now = Date.now();\n const existingId = this.readStoredSession();\n if (existingId) {\n this.emitSessionEvent(SESSION_END_EVENT, existingId, reason);\n }\n\n const newId = generateAnonId();\n this.persistSession(newId, now);\n this.emitSessionEvent(SESSION_START_EVENT, newId, reason);\n return newId;\n }\n\n /**\n * End the current analytics session (if any), emitting `$session_end`,\n * and clear the stored session. The next tracked event will lazily\n * start a fresh session. Called automatically on logout via\n * {@link reset}; also exposed for explicit control.\n *\n * @param immediate - flush buffered events synchronously (via\n * `sendBeacon`) so the end event isn't lost if the page unloads.\n */\n endSession(reason: SessionReason = \"logout\", immediate = false): void {\n if (typeof window === \"undefined\") return;\n\n const existingId = this.readStoredSession();\n if (existingId) {\n this.emitSessionEvent(SESSION_END_EVENT, existingId, reason);\n }\n\n this.sessionId = undefined;\n this.clearSession();\n\n if (immediate) {\n this.flushSync();\n }\n }\n\n private readStoredUserId(): string | undefined {\n if (typeof localStorage === \"undefined\") return undefined;\n try {\n return localStorage.getItem(USER_ID_KEY) ?? undefined;\n } catch {\n return undefined;\n }\n }\n\n private writeStoredUserId(userId: string): void {\n if (typeof localStorage === \"undefined\") return;\n try {\n localStorage.setItem(USER_ID_KEY, userId);\n } catch {\n // ignore storage errors\n }\n }\n\n private clearStoredUserId(): void {\n if (typeof localStorage === \"undefined\") return;\n try {\n localStorage.removeItem(USER_ID_KEY);\n } catch {\n // ignore storage errors\n }\n }\n\n private clearTicketSession(): void {\n this.ticketSessionFetch = null;\n if (typeof localStorage === \"undefined\") return;\n try {\n localStorage.removeItem(TICKET_SESSION_KEY);\n } catch {\n // ignore storage errors\n }\n }\n\n private readStoredTicketSession(): StoredTicketSession | null {\n if (typeof localStorage === \"undefined\") return null;\n try {\n const raw = localStorage.getItem(TICKET_SESSION_KEY);\n if (!raw) return null;\n const parsed = JSON.parse(raw) as Partial<StoredTicketSession>;\n if (\n typeof parsed.anonymousId === \"string\" &&\n typeof parsed.token === \"string\" &&\n typeof parsed.expiresAt === \"number\" &&\n parsed.expiresAt > Date.now() + 60_000\n ) {\n return parsed as StoredTicketSession;\n }\n localStorage.removeItem(TICKET_SESSION_KEY);\n } catch {\n // fall through to network fetch\n }\n return null;\n }\n\n /**\n * @internal Used by the widget API helpers. Returns a cached or\n * newly-minted anonymous session token, or `null` if the server\n * has anonymous sessions disabled. Coalesces concurrent callers\n * onto a single in-flight network request so mount-time parallel\n * API calls don't each trigger a `POST /ticket/session`.\n */\n _ensureTicketSession(): Promise<StoredTicketSession | null> {\n const cached = this.readStoredTicketSession();\n if (cached) return Promise.resolve(cached);\n\n if (this.ticketSessionFetch) return this.ticketSessionFetch;\n if (!this.writeKey) return Promise.resolve(null);\n\n this.ticketSessionFetch = (async () => {\n try {\n const res = await fetch(`${this.endpoint}/api/ingest/ticket/session`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.writeKey}`,\n },\n body: \"{}\",\n signal: AbortSignal.timeout(this.timeout),\n });\n if (!res.ok) return null;\n const json = (await res.json()) as StoredTicketSession;\n if (\n typeof json?.anonymousId === \"string\" &&\n typeof json?.token === \"string\" &&\n typeof json?.expiresAt === \"number\"\n ) {\n try {\n localStorage?.setItem(TICKET_SESSION_KEY, JSON.stringify(json));\n } catch {\n // ignore storage errors\n }\n // Keep our in-memory anonymousId in sync with the\n // server-issued one so any code that reads `_getIdentity`\n // outside the ticket flow (e.g. analytics) sees a\n // consistent value.\n this.anonymousId = json.anonymousId;\n return json;\n }\n return null;\n } catch {\n return null;\n } finally {\n this.ticketSessionFetch = null;\n }\n })();\n\n return this.ticketSessionFetch;\n }\n\n /**\n * Return the id of the current session, rotating it when the previous\n * session has expired through inactivity. On rotation it emits a\n * `$session_end` for the timed-out session (backdated to the last\n * recorded activity) and a `$session_start` for the new one. Called on\n * every tracked event so sessions advance with real user activity.\n */\n private ensureSession(now: number): string | undefined {\n if (typeof window === \"undefined\") return this.sessionId;\n\n try {\n const lastActivity = this.readLastActivity();\n const existingId = this.readStoredSession();\n const expired =\n lastActivity === undefined || now - lastActivity > this.sessionTimeout;\n\n if (existingId && !expired) {\n this.sessionId = existingId;\n try {\n localStorage.setItem(LAST_ACTIVITY_KEY, String(now));\n } catch {\n // ignore storage errors\n }\n return existingId;\n }\n\n if (existingId) {\n // Previous session lapsed — close it, backdated to the moment of\n // last activity so the session duration reflects reality rather\n // than the gap until the user returned.\n this.emitSessionEvent(\n SESSION_END_EVENT,\n existingId,\n \"timeout\",\n lastActivity !== undefined\n ? new Date(lastActivity).toISOString()\n : undefined,\n );\n }\n\n const newId = generateAnonId();\n this.persistSession(newId, now);\n this.emitSessionEvent(\n SESSION_START_EVENT,\n newId,\n existingId ? \"timeout\" : \"initial\",\n );\n return newId;\n } catch {\n return this.sessionId ?? generateAnonId();\n }\n }\n\n private readStoredSession(): string | undefined {\n if (this.sessionId) return this.sessionId;\n if (typeof sessionStorage === \"undefined\") return undefined;\n try {\n return sessionStorage.getItem(SESSION_ID_KEY) ?? undefined;\n } catch {\n return undefined;\n }\n }\n\n private readLastActivity(): number | undefined {\n if (typeof localStorage === \"undefined\") return undefined;\n try {\n const raw = localStorage.getItem(LAST_ACTIVITY_KEY);\n return raw ? Number(raw) : undefined;\n } catch {\n return undefined;\n }\n }\n\n private persistSession(id: string, now: number): void {\n this.sessionId = id;\n if (typeof window === \"undefined\") return;\n try {\n sessionStorage.setItem(SESSION_ID_KEY, id);\n localStorage.setItem(LAST_ACTIVITY_KEY, String(now));\n } catch {\n // ignore storage errors\n }\n }\n\n /**\n * Enqueue a session lifecycle event already stamped with its session id\n * (so it never re-enters {@link ensureSession} and risks recursion).\n */\n private emitSessionEvent(\n event: string,\n sessionId: string,\n reason: SessionReason,\n occurredAt?: string,\n ): void {\n if (!this.writeKey) return;\n\n const traitsContext =\n Object.keys(this.traits).length > 0 ? { traits: this.traits } : {};\n\n this.buffer.push({\n event,\n properties: { reason },\n occurred_at: occurredAt ?? new Date().toISOString(),\n source: \"browser\",\n org_id: this.orgId,\n user_id: this.userId,\n anonymous_id: this.anonymousId,\n session_id: sessionId,\n context: {\n ...getBrowserContext(),\n ...traitsContext,\n },\n });\n }\n\n private clearSession(): void {\n if (typeof window === \"undefined\") return;\n try {\n sessionStorage.removeItem(SESSION_ID_KEY);\n localStorage.removeItem(LAST_ACTIVITY_KEY);\n } catch {\n // Ignore storage errors\n }\n }\n\n getSessionId(): string | undefined {\n return this.ensureSession(Date.now());\n }\n\n /** @internal Used by the widget to read tracker config. */\n _getEndpoint(): string {\n return this.endpoint;\n }\n\n /** @internal Used by the widget to read the write key. */\n _getWriteKey(): string | null {\n return this.writeKey;\n }\n\n /** @internal Used by the widget to read the current user identity. */\n _getIdentity(): {\n userId?: string;\n anonymousId?: string;\n email?: string;\n name?: string;\n userHash?: string;\n } {\n return {\n userId: this.userId,\n anonymousId: this.anonymousId,\n email: this.traits.email as string | undefined,\n name: this.traits.name as string | undefined,\n userHash: this.userHash,\n };\n }\n\n captureException(error: Error, context?: Record<string, unknown>): void {\n if (!this.writeKey) return;\n\n const breadcrumbs = getBreadcrumbs();\n this.track(\"$exception\", {\n type: error.name || \"Error\",\n message: error.message,\n stack: error.stack,\n handled: true,\n mechanism: (context?.mechanism as string) ?? \"manual\",\n source: \"browser\",\n ...context,\n context: { breadcrumbs: breadcrumbs as unknown[] },\n });\n }\n\n addBreadcrumb(\n category: string,\n message: string,\n data?: Record<string, unknown>,\n ): void {\n addBreadcrumb({\n type: \"custom\",\n category,\n message,\n timestamp: Date.now(),\n data,\n });\n }\n\n track(event: string, properties?: Record<string, unknown>): void;\n track(event: TrackEvent): void;\n track(\n eventOrName: string | TrackEvent,\n properties?: Record<string, unknown>,\n ): void {\n if (!this.writeKey) return;\n\n const evt: TrackEvent =\n typeof eventOrName === \"string\"\n ? { event: eventOrName, properties }\n : eventOrName;\n\n const traitsContext =\n Object.keys(this.traits).length > 0 ? { traits: this.traits } : {};\n\n const enriched: TrackEvent = {\n ...evt,\n occurred_at: evt.occurred_at ?? new Date().toISOString(),\n source: evt.source ?? \"browser\",\n org_id: evt.org_id ?? this.orgId,\n user_id: evt.user_id ?? this.userId,\n anonymous_id: evt.anonymous_id ?? this.anonymousId,\n session_id: evt.session_id ?? this.ensureSession(Date.now()),\n context: {\n ...getBrowserContext(),\n ...traitsContext,\n ...evt.context,\n },\n };\n\n this.buffer.push(enriched);\n\n if (this.buffer.length >= this.flushAt) {\n this.flush().catch(() => {});\n }\n }\n\n async flush(): Promise<FlushResult> {\n if (!this.writeKey || this.buffer.length === 0) {\n return { success: true, ingested: 0 };\n }\n\n const batch = this.buffer.splice(0, MAX_BATCH_SIZE);\n\n try {\n const res = await fetch(`${this.endpoint}/api/ingest/track`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.writeKey}`,\n },\n body: JSON.stringify({ events: batch }),\n signal: AbortSignal.timeout(this.timeout),\n });\n\n if (!res.ok) {\n const body = await res.text().catch(() => \"\");\n throw new Error(`API returned ${res.status}: ${body}`);\n }\n\n const json = (await res.json()) as { ingested: number };\n return { success: true, ingested: json.ingested };\n } catch (error) {\n this.buffer.unshift(...batch);\n const err = error instanceof Error ? error : new Error(String(error));\n this.onError?.(err, batch);\n return { success: false, ingested: 0 };\n }\n }\n\n /**\n * Fire-and-forget flush that survives page unload.\n *\n * Uses `fetch` with `keepalive` rather than `navigator.sendBeacon`:\n * beacons are always dispatched with credentials, and a JSON beacon is\n * preflighted — a credentialed preflighted request fails the CORS check\n * against the wildcard (`Access-Control-Allow-Origin: *`) ingest policy,\n * so the browser silently drops the beacon before it is ever sent. A\n * keepalive fetch is non-credentialed by default and carries the normal\n * Bearer header, giving it the exact CORS profile of the regular\n * `flush()` path while still outliving the page.\n */\n private flushSync(): void {\n if (!this.writeKey) return;\n if (typeof fetch === \"undefined\") return;\n if (this.buffer.length === 0) return;\n\n const batch = this.buffer.splice(0, MAX_BATCH_SIZE);\n\n try {\n void fetch(`${this.endpoint}/api/ingest/track`, {\n method: \"POST\",\n keepalive: true,\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.writeKey}`,\n },\n body: JSON.stringify({ events: batch }),\n }).catch(() => {\n // Best effort on unload\n });\n } catch {\n // Best effort on unload\n }\n }\n\n getFlag<T = unknown>(key: string, defaultValue: T): T {\n return this._flagClient?.getFlag(key, defaultValue) ?? defaultValue;\n }\n\n onFlagsReady(callback: () => void): void {\n this._flagClient?.onFlagsReady(callback);\n }\n\n onFlagChange(listener: () => void): () => void {\n return this._flagClient?.onFlagChange(listener) ?? (() => {});\n }\n\n async refreshFlags(): Promise<void> {\n await this._flagClient?.refresh();\n }\n\n async shutdown(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n\n this._flagClient?.destroy();\n this._flagClient = null;\n\n if (typeof window !== \"undefined\" && this.visibilityHandler) {\n window.removeEventListener(\"visibilitychange\", this.visibilityHandler);\n this.visibilityHandler = null;\n }\n\n while (this.buffer.length > 0) {\n await this.flush();\n }\n }\n}\n\nconst scoperat = new TrackerImpl();\nexport default scoperat;\n","import scoperat from \"./client.js\";\n\nexport default scoperat;\nexport { FlagClient } from \"./flag-client.js\";\nexport type {\n EvaluatedFlag,\n EventSource,\n FlushResult,\n IdentifyOptions,\n IdentifyTraits,\n InitOptions,\n TrackEvent,\n} from \"./types.js\";\n"],"mappings":"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","names":["getBreadcrumbs"]}
|
package/dist/widget/index.js
CHANGED
|
@@ -1294,13 +1294,10 @@ var TrackerImpl = class {
|
|
|
1294
1294
|
userId: this.userId,
|
|
1295
1295
|
anonymousId: this.anonymousId,
|
|
1296
1296
|
orgId: this.orgId
|
|
1297
|
-
}),
|
|
1298
|
-
onEvaluation: (key, flag) => this.track("$flag_evaluation", {
|
|
1299
|
-
flag_key: key,
|
|
1300
|
-
flag_value: flag.value,
|
|
1301
|
-
flag_type: flag.type,
|
|
1302
|
-
flag_source: flag.source
|
|
1303
1297
|
})
|
|
1298
|
+
// Flag evaluations are an internal system signal, not a product
|
|
1299
|
+
// event — we deliberately don't emit `$flag_evaluation` into the
|
|
1300
|
+
// user's event stream (the server also refuses to persist it).
|
|
1304
1301
|
});
|
|
1305
1302
|
}
|
|
1306
1303
|
/** The underlying flag evaluation client. Use for direct SDK integration. */
|