mockSimulatorDebugLogger.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. const DEFAULT_DEBUG_LOG_URL = 'wss://gs.gotomars.xyz/debug-log'
  2. const MAX_QUEUED_LOGS = 80
  3. export type MockSimulatorDebugLogLevel = 'info' | 'warn' | 'error'
  4. export interface MockSimulatorDebugLoggerState {
  5. enabled: boolean
  6. connected: boolean
  7. connecting: boolean
  8. url: string
  9. statusText: string
  10. }
  11. export interface MockSimulatorDebugLogEntry {
  12. type: 'debug-log'
  13. timestamp: number
  14. scope: string
  15. level: MockSimulatorDebugLogLevel
  16. message: string
  17. payload?: Record<string, unknown>
  18. }
  19. function normalizeMockSimulatorLogUrl(rawUrl: string): string {
  20. const trimmed = String(rawUrl || '').trim()
  21. if (!trimmed) {
  22. return DEFAULT_DEBUG_LOG_URL
  23. }
  24. let normalized = trimmed
  25. if (!/^wss?:\/\//i.test(normalized)) {
  26. normalized = `ws://${normalized.replace(/^\/+/, '')}`
  27. }
  28. if (!/\/debug-log(?:\?.*)?$/i.test(normalized)) {
  29. normalized = normalized.replace(/\/+$/, '')
  30. normalized = `${normalized}/debug-log`
  31. }
  32. return normalized
  33. }
  34. export class MockSimulatorDebugLogger {
  35. socketTask: WechatMiniprogram.SocketTask | null
  36. enabled: boolean
  37. connected: boolean
  38. connecting: boolean
  39. url: string
  40. queue: MockSimulatorDebugLogEntry[]
  41. onStateChange?: (state: MockSimulatorDebugLoggerState) => void
  42. constructor(onStateChange?: (state: MockSimulatorDebugLoggerState) => void) {
  43. this.socketTask = null
  44. this.enabled = false
  45. this.connected = false
  46. this.connecting = false
  47. this.url = DEFAULT_DEBUG_LOG_URL
  48. this.queue = []
  49. this.onStateChange = onStateChange
  50. }
  51. getState(): MockSimulatorDebugLoggerState {
  52. return {
  53. enabled: this.enabled,
  54. connected: this.connected,
  55. connecting: this.connecting,
  56. url: this.url,
  57. statusText: !this.enabled
  58. ? `已关闭 (${this.url})`
  59. : this.connected
  60. ? `已连接 (${this.url})`
  61. : this.connecting
  62. ? `连接中 (${this.url})`
  63. : `未连接 (${this.url})`,
  64. }
  65. }
  66. emitState(): void {
  67. if (this.onStateChange) {
  68. this.onStateChange(this.getState())
  69. }
  70. }
  71. setEnabled(enabled: boolean): void {
  72. if (this.enabled === enabled) {
  73. return
  74. }
  75. this.enabled = enabled
  76. if (!enabled) {
  77. this.disconnect()
  78. this.queue = []
  79. this.emitState()
  80. return
  81. }
  82. this.emitState()
  83. this.connect()
  84. }
  85. setUrl(url: string): void {
  86. const nextUrl = normalizeMockSimulatorLogUrl(url)
  87. if (this.url === nextUrl) {
  88. return
  89. }
  90. this.url = nextUrl
  91. if (!this.enabled) {
  92. this.emitState()
  93. return
  94. }
  95. this.disconnect()
  96. this.emitState()
  97. this.connect()
  98. }
  99. log(
  100. scope: string,
  101. level: MockSimulatorDebugLogLevel,
  102. message: string,
  103. payload?: Record<string, unknown>,
  104. ): void {
  105. if (!this.enabled) {
  106. return
  107. }
  108. const entry: MockSimulatorDebugLogEntry = {
  109. type: 'debug-log',
  110. timestamp: Date.now(),
  111. scope,
  112. level,
  113. message,
  114. ...(payload ? { payload } : {}),
  115. }
  116. if (this.connected && this.socketTask) {
  117. this.send(entry)
  118. return
  119. }
  120. this.queue.push(entry)
  121. if (this.queue.length > MAX_QUEUED_LOGS) {
  122. this.queue.splice(0, this.queue.length - MAX_QUEUED_LOGS)
  123. }
  124. this.connect()
  125. }
  126. disconnect(): void {
  127. const socketTask = this.socketTask
  128. this.socketTask = null
  129. this.connected = false
  130. this.connecting = false
  131. if (socketTask) {
  132. try {
  133. socketTask.close({})
  134. } catch (_error) {
  135. // noop
  136. }
  137. }
  138. this.emitState()
  139. }
  140. destroy(): void {
  141. this.disconnect()
  142. this.queue = []
  143. }
  144. connect(): void {
  145. if (!this.enabled || this.connected || this.connecting) {
  146. return
  147. }
  148. this.connecting = true
  149. this.emitState()
  150. try {
  151. const socketTask = wx.connectSocket({
  152. url: this.url,
  153. })
  154. this.socketTask = socketTask
  155. socketTask.onOpen(() => {
  156. this.connected = true
  157. this.connecting = false
  158. this.emitState()
  159. this.send({
  160. type: 'debug-log',
  161. timestamp: Date.now(),
  162. scope: 'logger',
  163. level: 'info',
  164. message: 'logger channel connected',
  165. payload: {
  166. url: this.url,
  167. },
  168. })
  169. this.flush()
  170. })
  171. socketTask.onClose(() => {
  172. this.connected = false
  173. this.connecting = false
  174. this.socketTask = null
  175. this.emitState()
  176. })
  177. socketTask.onError(() => {
  178. this.connected = false
  179. this.connecting = false
  180. this.socketTask = null
  181. this.emitState()
  182. })
  183. socketTask.onMessage(() => {
  184. // 模拟器会广播所有消息,debug logger 不消费回包。
  185. })
  186. } catch (_error) {
  187. this.connected = false
  188. this.connecting = false
  189. this.socketTask = null
  190. this.emitState()
  191. }
  192. }
  193. flush(): void {
  194. if (!this.connected || !this.socketTask || !this.queue.length) {
  195. return
  196. }
  197. const pending = this.queue.splice(0, this.queue.length)
  198. pending.forEach((entry) => {
  199. this.send(entry)
  200. })
  201. }
  202. send(entry: MockSimulatorDebugLogEntry): void {
  203. if (!this.socketTask || !this.connected) {
  204. return
  205. }
  206. try {
  207. this.socketTask.send({
  208. data: JSON.stringify(entry),
  209. })
  210. } catch (_error) {
  211. this.connected = false
  212. this.connecting = false
  213. this.socketTask = null
  214. this.emitState()
  215. }
  216. }
  217. }