mockLocationBridge.ts 3.8 KB

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