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, XLogEvent } 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: XLogEvent): void { if (!_queue) { const cfg = getConfig() if (!cfg.logApiUrl || !cfg.logEnabled) return _queue = new LogQueue({ logApiUrl: cfg.logApiUrl, appKey: cfg.appKey }) } void _queue.push(event) } // ─── XLog public API ────────────────────────────────────────────────────────── export const XLog = { /** 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: properties ? { ...properties, environment: _environment } : { environment: _environment }, timestamp: Date.now(), userId: getUserId() ?? undefined, sessionId: _sessionId, appKey: getConfig().appKey, platform: _getPlatform(), appVersion: _getAppVersion(), } _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: 'js_error', message: err.message, stack: err.stack, fingerprint: computeFingerprint('js_error', err.message, err.stack), timestamp: Date.now(), userId: getUserId() ?? undefined, sessionId: _sessionId, appKey: getConfig().appKey, platform: _getPlatform(), appVersion: _getAppVersion(), metadata: metadata ? { ...metadata, environment: _environment } : { environment: _environment }, } _enqueue(issue) }, /** Log a warning (if log level allows). */ warn(message: string, metadata?: Record): void { if (_levelNum(_logLevel) > _levelNum('warn')) return XLog.captureError(new Error(message), { ...metadata, level: 'warn' }) }, /** Log an informational event (if log level allows). */ info(message: string, metadata?: Record): void { if (_levelNum(_logLevel) > _levelNum('info')) return XLog.event('__log_info', { message, ...metadata }) }, /** * 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(XLog.captureError.bind(XLog)) }, /** 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 } }, }