diff options
Diffstat (limited to 'websocket_client.ts')
| -rw-r--r-- | websocket_client.ts | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/websocket_client.ts b/websocket_client.ts new file mode 100644 index 0000000..62d11fc --- /dev/null +++ b/websocket_client.ts @@ -0,0 +1,167 @@ +import { WuiResponseInterface } from "./response.js" + +const AUTO_RECONNECT_INTERVAL = 5000 + +interface RequestQueue { + req: WuiWebSocketRequest + cbSuccess: (res: WuiWebSocketResponse) => void + cbFail: (err: string) => void +} + +export interface WuiWebSocketOptions { + address: string + insecure: boolean // If true the client will connect without SSL. + auto_reconnect: boolean // If true the client will handle auto-reconnect. + auto_reconnect_interval: number // The interval for auto-reconnect, default to 5 seconds. + onBroadcast: (res: WuiWebSocketResponse) => void + onConnected: () => void + onDisconnected: () => void + onError: () => void +} + +export interface WuiWebSocketRequest { + id: number + method: string + target: string + body?: string +} + +export interface WuiWebSocketResponse { + id: number + code: number + message: string + body: string +} + +export class WuiWebSocketClient { + address: string + conn!: WebSocket + requestQueue: RequestQueue[] = [] + reconnect_id: number = 0 + isOpen: boolean = false + error: string = "" + + constructor(public opts: WuiWebSocketOptions) { + if (opts.insecure) { + this.address = "ws://" + opts.address + } else { + this.address = "wss://" + opts.address + } + if (opts.auto_reconnect) { + if (opts.auto_reconnect_interval <= 0) { + opts.auto_reconnect_interval = + AUTO_RECONNECT_INTERVAL + } + } + this.connect() + } + + // + // Send the request and wait for response similar to HTTP + // request-response. + // + async Send(req: WuiWebSocketRequest): Promise<WuiResponseInterface> { + return new Promise((resolve, reject) => { + let wuiRes: WuiResponseInterface = { + code: 0, + message: "", + } + let reqQueue: RequestQueue = { + req: req, + cbSuccess: (res: WuiWebSocketResponse) => { + wuiRes.code = res.code + wuiRes.message = res.message + if ( + res.code === 200 && + res.body.length > 0 + ) { + wuiRes.data = JSON.parse( + atob(res.body), + ) + } + resolve(wuiRes) + }, + cbFail: (err: string) => { + wuiRes.code = 500 + wuiRes.message = err + resolve(wuiRes) + }, + } + this.requestQueue.push(reqQueue) + this.conn.send(JSON.stringify(req)) + }) + } + + connect() { + this.conn = new WebSocket(this.address) + + this.conn.onclose = (ev: CloseEvent) => { + this.onClose(ev) + } + this.conn.onerror = (ev: Event) => { + this.onError(ev) + } + this.conn.onmessage = (ev: MessageEvent) => { + this.onMessage(ev) + } + this.conn.onopen = (ev: Event) => { + this.onOpen(ev) + } + } + + // onClose handle connection closed by cleaning up the request + // queue. + onClose(ev: CloseEvent) { + for (let x = 0; x < this.requestQueue.length; x++) { + this.requestQueue[x].cbFail("connection closed") + } + + this.isOpen = false + this.error = "connection is closed by server" + + if (this.opts.auto_reconnect && !this.reconnect_id) { + this.reconnect_id = setInterval(() => { + this.connect() + }, this.opts.auto_reconnect_interval) + } + if (this.opts.onDisconnected) { + this.opts.onDisconnected() + } + } + + onError(ev: Event) { + if (this.opts.onError) { + this.opts.onError() + } + } + + onMessage(ev: MessageEvent) { + let res: WuiWebSocketResponse = JSON.parse(ev.data) + + for (let x = 0; x < this.requestQueue.length; x++) { + let reqq = this.requestQueue[x] + if (reqq.req.id === res.id) { + reqq.cbSuccess(res) + this.requestQueue.splice(x, 1) + return + } + } + + if (this.opts.onBroadcast) { + this.opts.onBroadcast(res) + } + } + + onOpen(ev: Event) { + this.isOpen = true + this.error = "" + + if (this.reconnect_id) { + clearInterval(this.reconnect_id) + this.reconnect_id = 0 + } + if (this.opts.onConnected) { + this.opts.onConnected() + } + } +} |
