UNPKG

partysocket

Version:
560 lines (551 loc) • 16.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _class; var _class2; // src/ws.ts if (!globalThis.EventTarget || !globalThis.Event) { console.error(` PartySocket requires a global 'EventTarget' class to be available! You can polyfill this global by adding this to your code before any partysocket imports: \`\`\` import 'partysocket/event-target-polyfill'; \`\`\` Please file an issue at https://github.com/partykit/partykit if you're still having trouble. `); } var ErrorEvent = class extends Event { // biome-ignore lint/suspicious/noExplicitAny: vibes constructor(error, target) { super("error", target); this.message = error.message; this.error = error; } }; var CloseEvent = ((_class = class extends Event { __init() { this.wasClean = true; } // biome-ignore lint/style/useDefaultParameterLast: legacy // biome-ignore lint/suspicious/noExplicitAny: legacy constructor(code = 1e3, reason = "", target) { super("close", target); _class.prototype.__init.call(this); this.code = code; this.reason = reason; } }), _class); var Events = { Event, ErrorEvent, CloseEvent }; function assert(condition, msg) { if (!condition) { throw new Error(msg); } } function cloneEventBrowser(e) { return new e.constructor(e.type, e); } function cloneEventNode(e) { if ("data" in e) { const evt2 = new MessageEvent(e.type, e); return evt2; } if ("code" in e || "reason" in e) { const evt2 = new CloseEvent( // @ts-expect-error we need to fix event/listener types e.code || 1999, // @ts-expect-error we need to fix event/listener types e.reason || "unknown reason", e ); return evt2; } if ("error" in e) { const evt2 = new ErrorEvent(e.error, e); return evt2; } const evt = new Event(e.type, e); return evt; } var _a; var isNode = typeof process !== "undefined" && typeof ((_a = process.versions) == null ? void 0 : _a.node) !== "undefined" && typeof document === "undefined"; var cloneEvent = isNode ? cloneEventNode : cloneEventBrowser; var DEFAULT = { maxReconnectionDelay: 1e4, minReconnectionDelay: 1e3 + Math.random() * 4e3, minUptime: 5e3, reconnectionDelayGrowFactor: 1.3, connectionTimeout: 4e3, maxRetries: Number.POSITIVE_INFINITY, maxEnqueuedMessages: Number.POSITIVE_INFINITY, startClosed: false, debug: false }; var didWarnAboutMissingWebSocket = false; var ReconnectingWebSocket = ((_class2 = class _ReconnectingWebSocket extends EventTarget { __init2() { this._retryCount = -1; } __init3() { this._shouldReconnect = true; } __init4() { this._connectLock = false; } __init5() { this._binaryType = "blob"; } __init6() { this._closeCalled = false; } __init7() { this._messageQueue = []; } __init8() { this._debugLogger = console.log.bind(console); } constructor(url, protocols, options = {}) { super(); _class2.prototype.__init2.call(this); _class2.prototype.__init3.call(this); _class2.prototype.__init4.call(this); _class2.prototype.__init5.call(this); _class2.prototype.__init6.call(this); _class2.prototype.__init7.call(this); _class2.prototype.__init8.call(this); _class2.prototype.__init9.call(this); _class2.prototype.__init10.call(this); _class2.prototype.__init11.call(this); _class2.prototype.__init12.call(this); _class2.prototype.__init13.call(this); _class2.prototype.__init14.call(this); _class2.prototype.__init15.call(this); _class2.prototype.__init16.call(this); this._url = url; this._protocols = protocols; this._options = options; if (this._options.startClosed) { this._shouldReconnect = false; } if (this._options.debugLogger) { this._debugLogger = this._options.debugLogger; } this._connect(); } static get CONNECTING() { return 0; } static get OPEN() { return 1; } static get CLOSING() { return 2; } static get CLOSED() { return 3; } get CONNECTING() { return _ReconnectingWebSocket.CONNECTING; } get OPEN() { return _ReconnectingWebSocket.OPEN; } get CLOSING() { return _ReconnectingWebSocket.CLOSING; } get CLOSED() { return _ReconnectingWebSocket.CLOSED; } get binaryType() { return this._ws ? this._ws.binaryType : this._binaryType; } set binaryType(value) { this._binaryType = value; if (this._ws) { this._ws.binaryType = value; } } /** * Returns the number or connection retries */ get retryCount() { return Math.max(this._retryCount, 0); } /** * The number of bytes of data that have been queued using calls to send() but not yet * transmitted to the network. This value resets to zero once all queued data has been sent. * This value does not reset to zero when the connection is closed; if you keep calling send(), * this will continue to climb. Read only */ get bufferedAmount() { const bytes = this._messageQueue.reduce((acc, message) => { if (typeof message === "string") { acc += message.length; } else if (message instanceof Blob) { acc += message.size; } else { acc += message.byteLength; } return acc; }, 0); return bytes + (this._ws ? this._ws.bufferedAmount : 0); } /** * The extensions selected by the server. This is currently only the empty string or a list of * extensions as negotiated by the connection */ get extensions() { return this._ws ? this._ws.extensions : ""; } /** * A string indicating the name of the sub-protocol the server selected; * this will be one of the strings specified in the protocols parameter when creating the * WebSocket object */ get protocol() { return this._ws ? this._ws.protocol : ""; } /** * The current state of the connection; this is one of the Ready state constants */ get readyState() { if (this._ws) { return this._ws.readyState; } return this._options.startClosed ? _ReconnectingWebSocket.CLOSED : _ReconnectingWebSocket.CONNECTING; } /** * The URL as resolved by the constructor */ get url() { return this._ws ? this._ws.url : ""; } /** * Whether the websocket object is now in reconnectable state */ get shouldReconnect() { return this._shouldReconnect; } /** * An event listener to be called when the WebSocket connection's readyState changes to CLOSED */ __init9() { this.onclose = null; } /** * An event listener to be called when an error occurs */ __init10() { this.onerror = null; } /** * An event listener to be called when a message is received from the server */ __init11() { this.onmessage = null; } /** * An event listener to be called when the WebSocket connection's readyState changes to OPEN; * this indicates that the connection is ready to send and receive data */ __init12() { this.onopen = null; } /** * Closes the WebSocket connection or connection attempt, if any. If the connection is already * CLOSED, this method does nothing */ close(code = 1e3, reason) { this._closeCalled = true; this._shouldReconnect = false; this._clearTimeouts(); if (!this._ws) { this._debug("close enqueued: no ws instance"); return; } if (this._ws.readyState === this.CLOSED) { this._debug("close: already closed"); return; } this._ws.close(code, reason); } /** * Closes the WebSocket connection or connection attempt and connects again. * Resets retry counter; */ reconnect(code, reason) { this._shouldReconnect = true; this._closeCalled = false; this._retryCount = -1; if (!this._ws || this._ws.readyState === this.CLOSED) { this._connect(); } else { this._disconnect(code, reason); this._connect(); } } /** * Enqueue specified data to be transmitted to the server over the WebSocket connection */ send(data) { if (this._ws && this._ws.readyState === this.OPEN) { this._debug("send", data); this._ws.send(data); } else { const { maxEnqueuedMessages = DEFAULT.maxEnqueuedMessages } = this._options; if (this._messageQueue.length < maxEnqueuedMessages) { this._debug("enqueue", data); this._messageQueue.push(data); } } } _debug(...args) { if (this._options.debug) { this._debugLogger("RWS>", ...args); } } _getNextDelay() { const { reconnectionDelayGrowFactor = DEFAULT.reconnectionDelayGrowFactor, minReconnectionDelay = DEFAULT.minReconnectionDelay, maxReconnectionDelay = DEFAULT.maxReconnectionDelay } = this._options; let delay = 0; if (this._retryCount > 0) { delay = minReconnectionDelay * reconnectionDelayGrowFactor ** (this._retryCount - 1); if (delay > maxReconnectionDelay) { delay = maxReconnectionDelay; } } this._debug("next delay", delay); return delay; } _wait() { return new Promise((resolve) => { setTimeout(resolve, this._getNextDelay()); }); } _getNextProtocols(protocolsProvider) { if (!protocolsProvider) return Promise.resolve(null); if ( typeof protocolsProvider === "string" || Array.isArray(protocolsProvider) ) { return Promise.resolve(protocolsProvider); } if (typeof protocolsProvider === "function") { const protocols = protocolsProvider(); if (!protocols) return Promise.resolve(null); if (typeof protocols === "string" || Array.isArray(protocols)) { return Promise.resolve(protocols); } if (protocols.then) { return protocols; } } throw Error("Invalid protocols"); } _getNextUrl(urlProvider) { if (typeof urlProvider === "string") { return Promise.resolve(urlProvider); } if (typeof urlProvider === "function") { const url = urlProvider(); if (typeof url === "string") { return Promise.resolve(url); } if (url.then) { return url; } } throw Error("Invalid URL"); } _connect() { if (this._connectLock || !this._shouldReconnect) { return; } this._connectLock = true; const { maxRetries = DEFAULT.maxRetries, connectionTimeout = DEFAULT.connectionTimeout } = this._options; if (this._retryCount >= maxRetries) { this._debug("max retries reached", this._retryCount, ">=", maxRetries); return; } this._retryCount++; this._debug("connect", this._retryCount); this._removeListeners(); this._wait() .then(() => Promise.all([ this._getNextUrl(this._url), this._getNextProtocols(this._protocols || null) ]) ) .then(([url, protocols]) => { if (this._closeCalled) { this._connectLock = false; return; } if ( !this._options.WebSocket && typeof WebSocket === "undefined" && !didWarnAboutMissingWebSocket ) { console.error(`\u203C\uFE0F No WebSocket implementation available. You should define options.WebSocket. For example, if you're using node.js, run \`npm install ws\`, and then in your code: import PartySocket from 'partysocket'; import WS from 'ws'; const partysocket = new PartySocket({ host: "127.0.0.1:1999", room: "test-room", WebSocket: WS }); `); didWarnAboutMissingWebSocket = true; } const WS = this._options.WebSocket || WebSocket; this._debug("connect", { url, protocols }); this._ws = protocols ? new WS(url, protocols) : new WS(url); this._ws.binaryType = this._binaryType; this._connectLock = false; this._addListeners(); this._connectTimeout = setTimeout( () => this._handleTimeout(), connectionTimeout ); }) .catch((err) => { this._connectLock = false; this._handleError(new Events.ErrorEvent(Error(err.message), this)); }); } _handleTimeout() { this._debug("timeout event"); this._handleError(new Events.ErrorEvent(Error("TIMEOUT"), this)); } _disconnect(code = 1e3, reason) { this._clearTimeouts(); if (!this._ws) { return; } this._removeListeners(); try { if ( this._ws.readyState === this.OPEN || this._ws.readyState === this.CONNECTING ) { this._ws.close(code, reason); } this._handleClose(new Events.CloseEvent(code, reason, this)); } catch (error) {} } _acceptOpen() { this._debug("accept open"); this._retryCount = 0; } __init13() { this._handleOpen = (event) => { this._debug("open event"); const { minUptime = DEFAULT.minUptime } = this._options; clearTimeout(this._connectTimeout); this._uptimeTimeout = setTimeout(() => this._acceptOpen(), minUptime); assert(this._ws, "WebSocket is not defined"); this._ws.binaryType = this._binaryType; this._messageQueue.forEach((message) => { var _a2; return (_a2 = this._ws) == null ? void 0 : _a2.send(message); }); this._messageQueue = []; if (this.onopen) { this.onopen(event); } this.dispatchEvent(cloneEvent(event)); }; } __init14() { this._handleMessage = (event) => { this._debug("message event"); if (this.onmessage) { this.onmessage(event); } this.dispatchEvent(cloneEvent(event)); }; } __init15() { this._handleError = (event) => { this._debug("error event", event.message); this._disconnect( void 0, event.message === "TIMEOUT" ? "timeout" : void 0 ); if (this.onerror) { this.onerror(event); } this._debug("exec error listeners"); this.dispatchEvent(cloneEvent(event)); this._connect(); }; } __init16() { this._handleClose = (event) => { this._debug("close event"); this._clearTimeouts(); if (this._shouldReconnect) { this._connect(); } if (this.onclose) { this.onclose(event); } this.dispatchEvent(cloneEvent(event)); }; } _removeListeners() { if (!this._ws) { return; } this._debug("removeListeners"); this._ws.removeEventListener("open", this._handleOpen); this._ws.removeEventListener("close", this._handleClose); this._ws.removeEventListener("message", this._handleMessage); this._ws.removeEventListener("error", this._handleError); } _addListeners() { if (!this._ws) { return; } this._debug("addListeners"); this._ws.addEventListener("open", this._handleOpen); this._ws.addEventListener("close", this._handleClose); this._ws.addEventListener("message", this._handleMessage); this._ws.addEventListener("error", this._handleError); } _clearTimeouts() { clearTimeout(this._connectTimeout); clearTimeout(this._uptimeTimeout); } }), _class2); exports.ErrorEvent = ErrorEvent; exports.CloseEvent = CloseEvent; exports.ReconnectingWebSocket = ReconnectingWebSocket; /*! * Reconnecting WebSocket * by Pedro Ladaria <pedro.ladaria@gmail.com> * https://github.com/pladaria/reconnecting-websocket * License MIT */ //# sourceMappingURL=chunk-PORMDJB2.js.map