UNPKG

convex

Version:

Client for the Convex Cloud

193 lines (192 loc) 6.13 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var web_socket_manager_exports = {}; __export(web_socket_manager_exports, { WebSocketManager: () => WebSocketManager }); module.exports = __toCommonJS(web_socket_manager_exports); var import_protocol = require("./protocol.js"); const CLOSE_NORMAL = 1e3; const CLOSE_NO_STATUS = 1005; function promisePair() { let resolvePromise; const promise = new Promise((resolve) => { resolvePromise = resolve; }); return { promise, resolve: resolvePromise }; } class WebSocketManager { constructor(uri, onOpen, onMessage, webSocketConstructor) { this.webSocketConstructor = webSocketConstructor; this.socket = { state: "disconnected" }; this.connectionCount = 0; this.lastCloseReason = "InitialConnect"; this.initialBackoff = 100; this.maxBackoff = 16e3; this.retries = 0; this.serverInactivityThreshold = 3e4; this.reconnectDueToServerInactivityTimeout = null; this.uri = uri; this.onOpen = onOpen; this.onMessage = onMessage; void this.connect(); } async connect() { if (this.socket.state === "closing" || this.socket.state === "stopping" || this.socket.state === "stopped") { return; } if (this.socket.state !== "disconnected") { throw new Error("Didn't start connection from disconnected state"); } const ws = new this.webSocketConstructor(this.uri); this.socket = { state: "connecting", ws }; ws.onopen = () => { if (this.socket.state !== "connecting") { throw new Error("onopen called with socket not in connecting state"); } this.socket = { state: "ready", ws }; this.onServerActivity(); this.onOpen({ connectionCount: this.connectionCount, lastCloseReason: this.lastCloseReason }); this.connectionCount += 1; this.lastCloseReason = null; }; ws.onerror = (error) => { const message = error.message; console.log(`WebSocket error: ${message}`); this.closeAndReconnect("WebSocketError"); }; ws.onmessage = (message) => { this.retries = 0; this.onServerActivity(); const serverMessage = (0, import_protocol.parseServerMessage)(JSON.parse(message.data)); this.onMessage(serverMessage); }; ws.onclose = (event) => { if (this.lastCloseReason === null) { this.lastCloseReason = event.reason ?? "OnCloseInvoked"; } if (event.code !== CLOSE_NORMAL && event.code !== CLOSE_NO_STATUS) { let msg = `WebSocket closed unexpectedly with code ${event.code}`; if (event.reason) { msg += `: ${event.reason}`; } console.error(msg); } if (this.socket.state === "stopping") { this.socket.promisePair.resolve(null); this.socket = { state: "stopped" }; return; } this.socket = { state: "disconnected" }; const backoff = this.nextBackoff(); console.log(`Attempting reconnect in ${backoff}ms`); setTimeout(() => this.connect(), backoff); }; } socketState() { return this.socket.state; } sendMessage(message) { if (this.socket.state === "ready") { const request = JSON.stringify(message); try { this.socket.ws.send(request); } catch (error) { console.log( `Failed to send message on WebSocket, reconnecting: ${error}` ); this.closeAndReconnect("FailedToSendMessage"); } } } onServerActivity() { if (this.reconnectDueToServerInactivityTimeout !== null) { clearTimeout(this.reconnectDueToServerInactivityTimeout); this.reconnectDueToServerInactivityTimeout = null; } this.reconnectDueToServerInactivityTimeout = setTimeout(() => { this.closeAndReconnect("InactiveServer"); }, this.serverInactivityThreshold); } closeAndReconnect(closeReason) { switch (this.socket.state) { case "disconnected": case "closing": case "stopping": case "stopped": return; case "connecting": case "ready": this.lastCloseReason = closeReason; this.socket.ws.close(); this.socket = { state: "closing" }; return; default: { const _ = this.socket; } } } async stop() { switch (this.socket.state) { case "stopped": return; case "connecting": case "ready": this.socket.ws.close(); this.socket = { state: "stopping", promisePair: promisePair() }; await this.socket.promisePair.promise; return; case "closing": this.socket = { state: "stopping", promisePair: promisePair() }; await this.socket.promisePair.promise; return; case "disconnected": this.socket = { state: "stopped" }; return; case "stopping": await this.socket.promisePair.promise; return; default: { const _ = this.socket; } } } nextBackoff() { const baseBackoff = this.initialBackoff * Math.pow(2, this.retries); this.retries += 1; const actualBackoff = Math.min(baseBackoff, this.maxBackoff); const jitter = actualBackoff * (Math.random() - 0.5); return actualBackoff + jitter; } } //# sourceMappingURL=web_socket_manager.js.map