import { Platform } from 'react-native' import { getConfig, isInitialized, getUserId } from '@xuqm/rn-common' import { LogQueue } from './queue/LogQueue' import { ErrorCapture } from './capture/ErrorCapture' import { computeFingerprint } from './fingerprint' import { FunnelTracker } from './funnel/FunnelTracker' import type { LogLevel, Environment, IssueEvent, LogEvent, BugCollectEvent } from './types' // ─── Internal state ─────────────────────────────────────────────────────────── let _logLevel: LogLevel = 'warn' let _environment: Environment = 'production' let _queue: LogQueue | null = null let _errorCaptureStarted = false // Stable session id for the lifetime of the JS runtime const _sessionId: string = _generateSessionId() function _generateSessionId(): string { return 'xxxx-xxxx'.replace(/x/g, () => ((Math.random() * 16) | 0).toString(16)) } function _levelNum(level: LogLevel): number { return { debug: 0, info: 1, warn: 2, error: 3 }[level] } function _getPlatform(): 'ios' | 'android' { return Platform.OS === 'ios' ? 'ios' : 'android' } function _getAppVersion(): string { // eslint-disable-next-line @typescript-eslint/no-explicit-any const constants = Platform.constants as Record const v = constants['appVersion'] ?? constants['Version'] return typeof v === 'string' ? v : String(v ?? '0.0.0') } function _enqueue(event: BugCollectEvent): void { if (!_queue) { const cfg = getConfig() if (!cfg.bugCollectApiUrl || !cfg.bugCollectEnabled) return _queue = new LogQueue({ bugCollectApiUrl: cfg.bugCollectApiUrl, appKey: cfg.appKey }) } void _queue.push(event) } // ─── BugCollect public API ──────────────────────────────────────────────────── export const BugCollect = { /** Set the minimum log level. Events below this level are discarded. */ setLogLevel(level: LogLevel): void { _logLevel = level }, /** Set an environment tag attached to every log event. */ setEnvironment(env: Environment): void { _environment = env }, /** * Record a custom analytics event (funnel analysis, behaviour tracking). * Also automatically advances any matching funnel step. */ event(name: string, properties?: Record): void { if (!isInitialized()) return const event: LogEvent = { type: 'event', name, properties, timestamp: Date.now(), userId: getUserId() ?? undefined, sessionId: _sessionId, appKey: getConfig().appKey, platform: _getPlatform(), release: _getAppVersion(), environment: _environment, sdk: { name: 'bugcollect.rn', version: '0.2.0' }, } _enqueue(event) FunnelTracker.track(name, properties) }, /** Upload a JS exception to the log service. */ captureError(error: unknown, metadata?: Record): void { if (!isInitialized()) return const err = error instanceof Error ? error : new Error(String(error)) const issue: IssueEvent = { type: 'issue', level: 'error', platform: _getPlatform(), fingerprint: computeFingerprint('error', err.message, err.stack), timestamp: Date.now(), appKey: getConfig().appKey, release: _getAppVersion(), environment: _environment, exception: { type: err.name ?? 'Error', value: err.message, stacktrace: err.stack, }, userId: getUserId() ?? undefined, sessionId: _sessionId, tags: metadata, sdk: { name: 'bugcollect.rn', version: '0.2.0' }, } _enqueue(issue) }, /** Log a warning (if log level allows). */ warn(message: string, metadata?: Record): void { if (_levelNum(_logLevel) > _levelNum('warn')) return if (!isInitialized()) return const issue: IssueEvent = { type: 'issue', level: 'warning', platform: _getPlatform(), fingerprint: computeFingerprint('warning', message), timestamp: Date.now(), appKey: getConfig().appKey, release: _getAppVersion(), environment: _environment, exception: { type: 'Warning', value: message, }, userId: getUserId() ?? undefined, sessionId: _sessionId, tags: metadata, sdk: { name: 'bugcollect.rn', version: '0.2.0' }, } _enqueue(issue) }, /** Log an informational event (if log level allows). */ info(message: string, metadata?: Record): void { if (_levelNum(_logLevel) > _levelNum('info')) return if (!isInitialized()) return const issue: IssueEvent = { type: 'issue', level: 'info', platform: _getPlatform(), fingerprint: computeFingerprint('info', message), timestamp: Date.now(), appKey: getConfig().appKey, release: _getAppVersion(), environment: _environment, exception: { type: 'Info', value: message, }, userId: getUserId() ?? undefined, sessionId: _sessionId, tags: metadata, sdk: { name: 'bugcollect.rn', version: '0.2.0' }, } _enqueue(issue) }, /** * Enable automatic capture of JS global errors and unhandled Promise rejections. * Call once at app startup after XuqmSDK.initialize(). */ startCapture(): void { if (_errorCaptureStarted) return _errorCaptureStarted = true ErrorCapture.start(BugCollect.captureError.bind(BugCollect)) }, /** Define a funnel for step-by-step conversion tracking. */ defineFunnel: FunnelTracker.define.bind(FunnelTracker), /** Get client-side funnel progress (server aggregates across sessions). */ getFunnelProgress: FunnelTracker.getProgress.bind(FunnelTracker), /** Flush any pending events immediately (e.g. before app goes to background). */ async flush(): Promise { if (_queue) await _queue.flush() }, /** Clean up resources (timers, etc.). Call on app termination if needed. */ destroy(): void { if (_queue) { _queue.destroy() _queue = null } }, }