mockHeartRateBridge.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. export const DEFAULT_MOCK_HEART_RATE_BRIDGE_URL = 'wss://gs.gotomars.xyz/mock-hr'
  2. export interface MockHeartRateBridgeCallbacks {
  3. onOpen: () => void
  4. onClose: (message: string) => void
  5. onError: (message: string) => void
  6. onBpm: (bpm: number) => void
  7. }
  8. type RawMockHeartRateMessage = {
  9. type?: string
  10. timestamp?: number
  11. bpm?: number
  12. channelId?: string
  13. }
  14. function normalizeMockChannelId(rawChannelId: string | null | undefined): string {
  15. const trimmed = String(rawChannelId || '').trim()
  16. return trimmed || 'default'
  17. }
  18. function safeParseMessage(data: string): RawMockHeartRateMessage | null {
  19. try {
  20. return JSON.parse(data) as RawMockHeartRateMessage
  21. } catch (_error) {
  22. return null
  23. }
  24. }
  25. function toHeartRateValue(message: RawMockHeartRateMessage, expectedChannelId: string): number | null {
  26. if (message.type !== 'mock_heart_rate' || !Number.isFinite(message.bpm)) {
  27. return null
  28. }
  29. if (normalizeMockChannelId(message.channelId) !== expectedChannelId) {
  30. return null
  31. }
  32. const bpm = Math.round(Number(message.bpm))
  33. if (bpm <= 0) {
  34. return null
  35. }
  36. return bpm
  37. }
  38. export class MockHeartRateBridge {
  39. callbacks: MockHeartRateBridgeCallbacks
  40. socketTask: WechatMiniprogram.SocketTask | null
  41. connected: boolean
  42. connecting: boolean
  43. url: string
  44. channelId: string
  45. constructor(callbacks: MockHeartRateBridgeCallbacks) {
  46. this.callbacks = callbacks
  47. this.socketTask = null
  48. this.connected = false
  49. this.connecting = false
  50. this.url = DEFAULT_MOCK_HEART_RATE_BRIDGE_URL
  51. this.channelId = 'default'
  52. }
  53. setChannelId(channelId: string): void {
  54. this.channelId = normalizeMockChannelId(channelId)
  55. }
  56. connect(url = DEFAULT_MOCK_HEART_RATE_BRIDGE_URL): void {
  57. if (this.connected || this.connecting) {
  58. return
  59. }
  60. this.url = url
  61. this.connecting = true
  62. try {
  63. const socketTask = wx.connectSocket({
  64. url,
  65. })
  66. this.socketTask = socketTask
  67. socketTask.onOpen(() => {
  68. this.connected = true
  69. this.connecting = false
  70. this.callbacks.onOpen()
  71. })
  72. socketTask.onClose((result) => {
  73. const reason = result && result.reason ? result.reason : '模拟心率源连接已关闭'
  74. this.connected = false
  75. this.connecting = false
  76. this.socketTask = null
  77. this.callbacks.onClose(reason)
  78. })
  79. socketTask.onError((result) => {
  80. const message = result && result.errMsg ? result.errMsg : '模拟心率源连接失败'
  81. this.connected = false
  82. this.connecting = false
  83. this.socketTask = null
  84. this.callbacks.onError(message)
  85. })
  86. socketTask.onMessage((result) => {
  87. if (!result || typeof result.data !== 'string') {
  88. return
  89. }
  90. const parsed = safeParseMessage(result.data)
  91. if (!parsed) {
  92. this.callbacks.onError('模拟心率消息不是合法 JSON')
  93. return
  94. }
  95. const bpm = toHeartRateValue(parsed, this.channelId)
  96. if (bpm === null) {
  97. return
  98. }
  99. this.callbacks.onBpm(bpm)
  100. })
  101. } catch (error) {
  102. this.connected = false
  103. this.connecting = false
  104. this.socketTask = null
  105. const message = error && (error as Error).message ? (error as Error).message : '模拟心率源连接创建失败'
  106. this.callbacks.onError(message)
  107. }
  108. }
  109. disconnect(): void {
  110. if (!this.socketTask) {
  111. if (this.connected || this.connecting) {
  112. this.connected = false
  113. this.connecting = false
  114. }
  115. return
  116. }
  117. const socketTask = this.socketTask
  118. this.socketTask = null
  119. this.connected = false
  120. this.connecting = false
  121. socketTask.close({})
  122. }
  123. destroy(): void {
  124. this.disconnect()
  125. }
  126. }