164 lines
4.2 KiB
TypeScript
164 lines
4.2 KiB
TypeScript
import { type LocationSample } from './locationSource'
|
|
|
|
export const DEFAULT_MOCK_LOCATION_BRIDGE_URL = 'wss://gs.gotomars.xyz/mock-gps'
|
|
|
|
export interface MockLocationBridgeCallbacks {
|
|
onOpen: () => void
|
|
onClose: (message: string) => void
|
|
onError: (message: string) => void
|
|
onSample: (sample: LocationSample) => void
|
|
}
|
|
|
|
type RawMockGpsMessage = {
|
|
type?: string
|
|
timestamp?: number
|
|
lat?: number
|
|
lon?: number
|
|
accuracyMeters?: number
|
|
speedMps?: number
|
|
headingDeg?: number
|
|
channelId?: string
|
|
}
|
|
|
|
function normalizeMockChannelId(rawChannelId: string | null | undefined): string {
|
|
const trimmed = String(rawChannelId || '').trim()
|
|
return trimmed || 'default'
|
|
}
|
|
|
|
function safeParseMessage(data: string): RawMockGpsMessage | null {
|
|
try {
|
|
return JSON.parse(data) as RawMockGpsMessage
|
|
} catch (_error) {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function toLocationSample(message: RawMockGpsMessage, expectedChannelId: string): LocationSample | null {
|
|
if (message.type !== 'mock_gps') {
|
|
return null
|
|
}
|
|
|
|
if (!Number.isFinite(message.lat) || !Number.isFinite(message.lon)) {
|
|
return null
|
|
}
|
|
|
|
if (normalizeMockChannelId(message.channelId) !== expectedChannelId) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
latitude: Number(message.lat),
|
|
longitude: Number(message.lon),
|
|
accuracy: Number.isFinite(message.accuracyMeters) ? Number(message.accuracyMeters) : undefined,
|
|
speed: Number.isFinite(message.speedMps) ? Number(message.speedMps) : null,
|
|
headingDeg: Number.isFinite(message.headingDeg) ? Number(message.headingDeg) : null,
|
|
timestamp: Number.isFinite(message.timestamp) ? Number(message.timestamp) : Date.now(),
|
|
sourceMode: 'mock',
|
|
}
|
|
}
|
|
|
|
export class MockLocationBridge {
|
|
callbacks: MockLocationBridgeCallbacks
|
|
socketTask: WechatMiniprogram.SocketTask | null
|
|
connected: boolean
|
|
connecting: boolean
|
|
url: string
|
|
channelId: string
|
|
|
|
constructor(callbacks: MockLocationBridgeCallbacks) {
|
|
this.callbacks = callbacks
|
|
this.socketTask = null
|
|
this.connected = false
|
|
this.connecting = false
|
|
this.url = DEFAULT_MOCK_LOCATION_BRIDGE_URL
|
|
this.channelId = 'default'
|
|
}
|
|
|
|
setChannelId(channelId: string): void {
|
|
this.channelId = normalizeMockChannelId(channelId)
|
|
}
|
|
|
|
connect(url = DEFAULT_MOCK_LOCATION_BRIDGE_URL): void {
|
|
if (this.connected || this.connecting) {
|
|
return
|
|
}
|
|
|
|
this.url = url
|
|
this.connecting = true
|
|
|
|
try {
|
|
const socketTask = wx.connectSocket({
|
|
url,
|
|
})
|
|
this.socketTask = socketTask
|
|
|
|
socketTask.onOpen(() => {
|
|
this.connected = true
|
|
this.connecting = false
|
|
this.callbacks.onOpen()
|
|
})
|
|
|
|
socketTask.onClose((result) => {
|
|
const reason = result && result.reason ? result.reason : '模拟源连接已关闭'
|
|
this.connected = false
|
|
this.connecting = false
|
|
this.socketTask = null
|
|
this.callbacks.onClose(reason)
|
|
})
|
|
|
|
socketTask.onError((result) => {
|
|
const message = result && result.errMsg ? result.errMsg : '模拟源连接失败'
|
|
this.connected = false
|
|
this.connecting = false
|
|
this.socketTask = null
|
|
this.callbacks.onError(message)
|
|
})
|
|
|
|
socketTask.onMessage((result) => {
|
|
if (!result || typeof result.data !== 'string') {
|
|
return
|
|
}
|
|
|
|
const parsed = safeParseMessage(result.data)
|
|
if (!parsed) {
|
|
this.callbacks.onError('模拟源消息不是合法 JSON')
|
|
return
|
|
}
|
|
|
|
const sample = toLocationSample(parsed, this.channelId)
|
|
if (!sample) {
|
|
return
|
|
}
|
|
|
|
this.callbacks.onSample(sample)
|
|
})
|
|
} catch (error) {
|
|
this.connected = false
|
|
this.connecting = false
|
|
this.socketTask = null
|
|
const message = error && (error as Error).message ? (error as Error).message : '模拟源连接创建失败'
|
|
this.callbacks.onError(message)
|
|
}
|
|
}
|
|
|
|
disconnect(): void {
|
|
if (!this.socketTask) {
|
|
if (this.connected || this.connecting) {
|
|
this.connected = false
|
|
this.connecting = false
|
|
}
|
|
return
|
|
}
|
|
|
|
const socketTask = this.socketTask
|
|
this.socketTask = null
|
|
this.connected = false
|
|
this.connecting = false
|
|
socketTask.close({})
|
|
}
|
|
|
|
destroy(): void {
|
|
this.disconnect()
|
|
}
|
|
}
|