const DEFAULT_DEBUG_LOG_URL = 'wss://gs.gotomars.xyz/debug-log' const MAX_QUEUED_LOGS = 80 export type MockSimulatorDebugLogLevel = 'info' | 'warn' | 'error' export interface MockSimulatorDebugLoggerState { enabled: boolean connected: boolean connecting: boolean url: string statusText: string } export interface MockSimulatorDebugLogEntry { type: 'debug-log' timestamp: number channelId?: string scope: string level: MockSimulatorDebugLogLevel message: string payload?: Record } function normalizeMockSimulatorChannelId(rawChannelId: string | null | undefined): string { const trimmed = String(rawChannelId || '').trim() return trimmed || 'default' } function normalizeMockSimulatorLogUrl(rawUrl: string): string { const trimmed = String(rawUrl || '').trim() if (!trimmed) { return DEFAULT_DEBUG_LOG_URL } let normalized = trimmed if (!/^wss?:\/\//i.test(normalized)) { normalized = `ws://${normalized.replace(/^\/+/, '')}` } if (!/\/debug-log(?:\?.*)?$/i.test(normalized)) { normalized = normalized.replace(/\/+$/, '') normalized = `${normalized}/debug-log` } return normalized } export class MockSimulatorDebugLogger { socketTask: WechatMiniprogram.SocketTask | null enabled: boolean connected: boolean connecting: boolean url: string channelId: string queue: MockSimulatorDebugLogEntry[] onStateChange?: (state: MockSimulatorDebugLoggerState) => void constructor(onStateChange?: (state: MockSimulatorDebugLoggerState) => void) { this.socketTask = null this.enabled = false this.connected = false this.connecting = false this.url = DEFAULT_DEBUG_LOG_URL this.channelId = 'default' this.queue = [] this.onStateChange = onStateChange } getState(): MockSimulatorDebugLoggerState { return { enabled: this.enabled, connected: this.connected, connecting: this.connecting, url: this.url, statusText: !this.enabled ? `已关闭 (${this.url})` : this.connected ? `已连接 (${this.url})` : this.connecting ? `连接中 (${this.url})` : `未连接 (${this.url})`, } } emitState(): void { if (this.onStateChange) { this.onStateChange(this.getState()) } } setEnabled(enabled: boolean): void { if (this.enabled === enabled) { return } this.enabled = enabled if (!enabled) { this.disconnect() this.queue = [] this.emitState() return } this.emitState() this.connect() } setUrl(url: string): void { const nextUrl = normalizeMockSimulatorLogUrl(url) if (this.url === nextUrl) { return } this.url = nextUrl if (!this.enabled) { this.emitState() return } this.disconnect() this.emitState() this.connect() } setChannelId(channelId: string): void { this.channelId = normalizeMockSimulatorChannelId(channelId) } log( scope: string, level: MockSimulatorDebugLogLevel, message: string, payload?: Record, ): void { if (!this.enabled) { return } const entry: MockSimulatorDebugLogEntry = { type: 'debug-log', timestamp: Date.now(), channelId: this.channelId, scope, level, message, ...(payload ? { payload } : {}), } if (this.connected && this.socketTask) { this.send(entry) return } this.queue.push(entry) if (this.queue.length > MAX_QUEUED_LOGS) { this.queue.splice(0, this.queue.length - MAX_QUEUED_LOGS) } this.connect() } disconnect(): void { const socketTask = this.socketTask this.socketTask = null this.connected = false this.connecting = false if (socketTask) { try { socketTask.close({}) } catch (_error) { // noop } } this.emitState() } destroy(): void { this.disconnect() this.queue = [] } connect(): void { if (!this.enabled || this.connected || this.connecting) { return } this.connecting = true this.emitState() try { const socketTask = wx.connectSocket({ url: this.url, }) this.socketTask = socketTask socketTask.onOpen(() => { this.connected = true this.connecting = false this.emitState() this.send({ type: 'debug-log', timestamp: Date.now(), channelId: this.channelId, scope: 'logger', level: 'info', message: 'logger channel connected', payload: { url: this.url, channelId: this.channelId, }, }) this.flush() }) socketTask.onClose(() => { this.connected = false this.connecting = false this.socketTask = null this.emitState() }) socketTask.onError(() => { this.connected = false this.connecting = false this.socketTask = null this.emitState() }) socketTask.onMessage(() => { // 模拟器会广播所有消息,debug logger 不消费回包。 }) } catch (_error) { this.connected = false this.connecting = false this.socketTask = null this.emitState() } } flush(): void { if (!this.connected || !this.socketTask || !this.queue.length) { return } const pending = this.queue.splice(0, this.queue.length) pending.forEach((entry) => { this.send(entry) }) } send(entry: MockSimulatorDebugLogEntry): void { if (!this.socketTask || !this.connected) { return } try { this.socketTask.send({ data: JSON.stringify(entry), }) } catch (_error) { this.connected = false this.connecting = false this.socketTask = null this.emitState() } } }