222 lines
5.4 KiB
JavaScript
222 lines
5.4 KiB
JavaScript
const WS_BASE_URL = 'wss://attendant.huashengtec.com/ws'
|
|
//const WS_BASE_URL = 'ws://127.0.0.1:9005'
|
|
|
|
class AIChatSocket {
|
|
constructor(url = `${WS_BASE_URL}/chat`, options = {}) {
|
|
this.url = url
|
|
this.options = {
|
|
timeout: 10000,
|
|
heartbeatInterval: 30000,
|
|
autoReconnect: true,
|
|
maxReconnectAttempts: 3,
|
|
reconnectDelay: 3000,
|
|
...options
|
|
}
|
|
|
|
this.socketTask = null
|
|
this.isConnected = false
|
|
this.isConnecting = false
|
|
this.messageQueue = []
|
|
this.reconnectAttempts = 0
|
|
|
|
this._openCallback = null
|
|
this._closeCallback = null
|
|
this._errorCallback = null
|
|
this._messageCallback = null
|
|
this._heartbeatTimer = null
|
|
}
|
|
|
|
connect() {
|
|
if (this.isConnected || this.isConnecting) {
|
|
console.log('[AIChatSocket] Already connected or connecting')
|
|
return Promise.resolve()
|
|
}
|
|
|
|
this.isConnecting = true
|
|
|
|
return new Promise((resolve, reject) => {
|
|
this.socketTask = wx.connectSocket({
|
|
url: this.url,
|
|
header: {
|
|
'content-type': 'application/json',
|
|
...this.options.header
|
|
},
|
|
protocols: this.options.protocols || [],
|
|
success: () => {
|
|
console.log('[AIChatSocket] WebSocket connecting...')
|
|
},
|
|
fail: (err) => {
|
|
this.isConnecting = false
|
|
console.error('[AIChatSocket] Connect failed:', err)
|
|
reject(err)
|
|
}
|
|
})
|
|
|
|
if (!this.socketTask) {
|
|
this.isConnecting = false
|
|
reject(new Error('SocketTask creation failed'))
|
|
return
|
|
}
|
|
|
|
this.socketTask.onOpen(() => {
|
|
console.log('[AIChatSocket] Connection opened')
|
|
this.isConnected = true
|
|
this.isConnecting = false
|
|
this.reconnectAttempts = 0
|
|
this._startHeartbeat()
|
|
|
|
this._flushMessageQueue()
|
|
|
|
if (this._openCallback) {
|
|
this._openCallback()
|
|
}
|
|
resolve()
|
|
})
|
|
|
|
this.socketTask.onClose((res) => {
|
|
console.log('[AIChatSocket] Connection closed:', res)
|
|
this.isConnected = false
|
|
this.isConnecting = false
|
|
this._stopHeartbeat()
|
|
|
|
if (this._closeCallback) {
|
|
this._closeCallback(res)
|
|
}
|
|
|
|
if (this.options.autoReconnect && this.reconnectAttempts < this.options.maxReconnectAttempts) {
|
|
this._reconnect()
|
|
}
|
|
})
|
|
|
|
this.socketTask.onError((err) => {
|
|
console.error('[AIChatSocket] WebSocket error:', err)
|
|
this.isConnected = false
|
|
this.isConnecting = false
|
|
|
|
if (this._errorCallback) {
|
|
this._errorCallback(err)
|
|
}
|
|
})
|
|
|
|
this.socketTask.onMessage((res) => {
|
|
try {
|
|
const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
|
|
if (this._messageCallback) {
|
|
this._messageCallback(data)
|
|
}
|
|
} catch (e) {
|
|
if (this._messageCallback) {
|
|
this._messageCallback(res.data)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
send(data) {
|
|
const message = typeof data === 'string' ? data : JSON.stringify(data)
|
|
|
|
if (!this.isConnected || !this.socketTask) {
|
|
console.log('[AIChatSocket] Not connected, queueing message')
|
|
this.messageQueue.push(message)
|
|
return false
|
|
}
|
|
|
|
try {
|
|
this.socketTask.send({
|
|
data: message,
|
|
fail: (err) => {
|
|
console.error('[AIChatSocket] Send failed:', err)
|
|
this.messageQueue.push(message)
|
|
}
|
|
})
|
|
return true
|
|
} catch (err) {
|
|
console.error('[AIChatSocket] Send error:', err)
|
|
this.messageQueue.push(message)
|
|
return false
|
|
}
|
|
}
|
|
|
|
close() {
|
|
this._stopHeartbeat()
|
|
this.options.autoReconnect = false
|
|
|
|
if (this.socketTask) {
|
|
this.socketTask.close({
|
|
code: 1000,
|
|
reason: 'User closed',
|
|
fail: (err) => {
|
|
console.error('[AIChatSocket] Close failed:', err)
|
|
}
|
|
})
|
|
this.socketTask = null
|
|
}
|
|
|
|
this.isConnected = false
|
|
this.isConnecting = false
|
|
this.messageQueue = []
|
|
}
|
|
|
|
onOpen(callback) {
|
|
this._openCallback = callback
|
|
}
|
|
|
|
onClose(callback) {
|
|
this._closeCallback = callback
|
|
}
|
|
|
|
onError(callback) {
|
|
this._errorCallback = callback
|
|
}
|
|
|
|
onMessage(callback) {
|
|
this._messageCallback = callback
|
|
}
|
|
|
|
_flushMessageQueue() {
|
|
if (this.messageQueue.length === 0) return
|
|
|
|
console.log(`[AIChatSocket] Flushing ${this.messageQueue.length} queued messages`)
|
|
while (this.messageQueue.length > 0) {
|
|
const message = this.messageQueue.shift()
|
|
this.send(message)
|
|
}
|
|
}
|
|
|
|
_reconnect() {
|
|
this.reconnectAttempts++
|
|
console.log(`[AIChatSocket] Reconnecting... Attempt ${this.reconnectAttempts}/${this.options.maxReconnectAttempts}`)
|
|
|
|
setTimeout(() => {
|
|
this.connect().catch((err) => {
|
|
console.error('[AIChatSocket] Reconnect failed:', err)
|
|
})
|
|
}, this.options.reconnectDelay)
|
|
}
|
|
|
|
_startHeartbeat() {
|
|
this._stopHeartbeat()
|
|
|
|
this._heartbeatTimer = setInterval(() => {
|
|
if (this.isConnected && this.socketTask) {
|
|
this.socketTask.send({
|
|
data: JSON.stringify({ type: 'ping' }),
|
|
fail: (err) => {
|
|
console.error('[AIChatSocket] Heartbeat failed:', err)
|
|
this._stopHeartbeat()
|
|
}
|
|
})
|
|
}
|
|
}, this.options.heartbeatInterval)
|
|
}
|
|
|
|
_stopHeartbeat() {
|
|
if (this._heartbeatTimer) {
|
|
clearInterval(this._heartbeatTimer)
|
|
this._heartbeatTimer = null
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = AIChatSocket |