145 lines
4.1 KiB
TypeScript
145 lines
4.1 KiB
TypeScript
import Config from "./config"
|
|
|
|
export type AutoReconnectOptions = boolean | {
|
|
maxRetries?: number
|
|
retryInterval?: number
|
|
onMaxRetriesReached?: Function
|
|
}
|
|
|
|
export enum ConnectionStatus {
|
|
Disconnected = 'DISCONNECTED',
|
|
Connected = 'CONNECTED',
|
|
Error = 'ERROR'
|
|
}
|
|
|
|
class SocketService {
|
|
private static instance: SocketService | null = null
|
|
private ws: WebSocket | null = null
|
|
private listeners: Record<string, Function[]> = {}
|
|
private autoReconnect: AutoReconnectOptions = true
|
|
private times: any = null
|
|
private retries: number = 0
|
|
private connectionStatus: ConnectionStatus = ConnectionStatus.Disconnected
|
|
|
|
private constructor() {
|
|
this.connect()
|
|
}
|
|
|
|
public static getInstance(): SocketService {
|
|
if (!SocketService.instance) {
|
|
SocketService.instance = new SocketService()
|
|
}
|
|
return SocketService.instance
|
|
}
|
|
|
|
public setAutoReconnectOptions(options: AutoReconnectOptions) {
|
|
this.autoReconnect = options
|
|
}
|
|
|
|
public connect() {
|
|
this.ws = new WebSocket(Config.ws)
|
|
this.ws.onopen = () => {
|
|
this.connectionStatus = ConnectionStatus.Connected
|
|
this.emit('connected', null)
|
|
this.hert()
|
|
}
|
|
this.ws.onerror = () => {
|
|
this.connectionStatus = ConnectionStatus.Error
|
|
clearTimeout(this.times)
|
|
this.emit('error', null)
|
|
}
|
|
this.ws.onclose = () => {
|
|
this.connectionStatus = ConnectionStatus.Disconnected
|
|
clearTimeout(this.times)
|
|
this.emit('disconnected', null)
|
|
if (this.shouldReconnect()) {
|
|
setTimeout(() => this.connect(), this.getRetryInterval())
|
|
}
|
|
}
|
|
this.ws.onmessage = (event) => {
|
|
this.emit('message', event.data)
|
|
}
|
|
}
|
|
public hert(){
|
|
this.times = setTimeout(() => this.send({"type":"heartbeat"}), 3000)
|
|
}
|
|
private shouldReconnect(): boolean {
|
|
if (typeof this.autoReconnect === 'boolean') {
|
|
return this.autoReconnect
|
|
} else if (this.autoReconnect) {
|
|
const { maxRetries } = this.autoReconnect
|
|
|
|
if (maxRetries !== undefined) {
|
|
if (this.retries < maxRetries) {
|
|
this.retries++
|
|
return true
|
|
} else if (this.retries >= maxRetries) {
|
|
this.autoReconnect.onMaxRetriesReached && this.autoReconnect.onMaxRetriesReached()
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
private getRetryInterval(): number {
|
|
if (typeof this.autoReconnect === 'boolean') {
|
|
return 1000
|
|
} else if (this.autoReconnect && this.autoReconnect.retryInterval) {
|
|
return this.autoReconnect.retryInterval
|
|
}
|
|
return 1000
|
|
}
|
|
|
|
public send(data: any) {
|
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
this.ws.send(JSON.stringify(data))
|
|
} else {
|
|
console.error('WebSocket 连接未打开')
|
|
}
|
|
}
|
|
|
|
public close() {
|
|
if (this.ws) {
|
|
this.ws.close()
|
|
SocketService.instance = null
|
|
}
|
|
}
|
|
|
|
private emit(event: string, data: any) {
|
|
if (!this.listeners[event]) {
|
|
return
|
|
}
|
|
this.listeners[event].forEach((listener) => listener(data))
|
|
}
|
|
|
|
public on(event: string, listener: Function) {
|
|
if (!this.listeners[event]) {
|
|
this.listeners[event] = []
|
|
}
|
|
this.listeners[event].push(listener)
|
|
if (event === 'connected' && this.connectionStatus === ConnectionStatus.Connected) {
|
|
listener()
|
|
}
|
|
}
|
|
|
|
public off(event: string, listener: Function) {
|
|
if (!this.listeners[event]) {
|
|
return
|
|
}
|
|
this.listeners[event] = this.listeners[event].filter((l) => l !== listener)
|
|
}
|
|
|
|
public getConnectionStatus(): ConnectionStatus {
|
|
return this.connectionStatus
|
|
}
|
|
|
|
public watchConnectionStatus(callback: (status: ConnectionStatus) => void) {
|
|
this.on('connected', () => callback(ConnectionStatus.Connected))
|
|
this.on('disconnected', () => callback(ConnectionStatus.Disconnected))
|
|
this.on('error', () => callback(ConnectionStatus.Error))
|
|
}
|
|
}
|
|
|
|
export default SocketService
|
|
|