summaryrefslogtreecommitdiff
path: root/websocket_client.ts
diff options
context:
space:
mode:
Diffstat (limited to 'websocket_client.ts')
-rw-r--r--websocket_client.ts167
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()
+ }
+ }
+}