1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
| let appUnloading = false;
export type Message = { readonly channel: string; readonly cmd: string; readonly param: unknown; }
function socketUrl(u?: string) { if (!u) { u = "/connect"; } if (u.indexOf("ws") === 0) { return u; } const l = document.location; let protocol = "ws"; if (l.protocol === "https:") { protocol = "wss"; } if (u.indexOf("/") !== 0) { u = "/" + u; } return protocol + `://${l.host}${u}`; }
export class Socket { readonly debug: boolean; private readonly open: () => void; private readonly recv: (m: Message) => void; private readonly err: (svc: string, err: string) => void; readonly url?: string; connected: boolean; pauseSeconds: number; pendingMessages: Message[]; connectTime?: number; sock?: WebSocket;
constructor(debug: boolean, o: () => void, r: (m: Message) => void, e: (svc: string, err: string) => void, url?: string) { this.debug = debug; this.open = o; this.recv = r; this.err = e; this.url = socketUrl(url); this.connected = false; this.pauseSeconds = 1; this.pendingMessages = [];
this.connect(); }
connect() { window.onbeforeunload = () => { appUnloading = true; }; this.connectTime = Date.now(); this.sock = new WebSocket(socketUrl(this.url)); const s = this; // eslint-disable-line @typescript-eslint/no-this-alias this.sock.onopen = () => { s.connected = true; s.pendingMessages.forEach(s.send); s.pendingMessages = []; if (s.debug) { console.log("WebSocket connected"); } s.open(); }; this.sock.onmessage = (event) => { const msg = JSON.parse(event.data) as Message; if (s.debug) { console.debug("[socket]: receive", msg); } s.recv(msg); }; this.sock.onerror = (event) => () => { s.err("socket", event.type); }; this.sock.onclose = () => { if (appUnloading) { return; } s.connected = false; const elapsed = s.connectTime ? Date.now() - s.connectTime : 0; if (elapsed > 0 && elapsed < 2000) { s.pauseSeconds = s.pauseSeconds * 2; if (s.debug) { console.debug(`socket closed immediately, reconnecting in ${s.pauseSeconds} seconds`); } setTimeout(() => { s.connect(); }, s.pauseSeconds * 1000); } else { console.debug("socket closed after [" + elapsed + "ms]"); setTimeout(() => { s.connect(); }, s.pauseSeconds * 500); } }; }
disconnect() { // noop }
send(msg: Message) { if (this.debug) { console.debug("out", msg); } if (!this.sock) { throw new Error("socket not initialized"); } if (this.connected) { const m = JSON.stringify(msg, null, 2); this.sock.send(m); } else { this.pendingMessages.push(msg); } } }
export function socketInit() { return Socket; }
|