partysocket
Advanced tools
Comparing version
@@ -1,660 +0,12 @@ | ||
"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 }); | ||
import { | ||
PartySocket | ||
} from "./chunk-5XUHNIY2.js"; | ||
import { | ||
ReconnectingWebSocket | ||
} from "./chunk-2ESZ2MDZ.js"; | ||
export { | ||
PartySocket, | ||
ReconnectingWebSocket as WebSocket, | ||
PartySocket as default | ||
}; | ||
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); | ||
// src/index.ts | ||
var src_exports = {}; | ||
__export(src_exports, { | ||
WebSocket: () => ReconnectingWebSocket, | ||
default: () => PartySocket | ||
}); | ||
module.exports = __toCommonJS(src_exports); | ||
// 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 { | ||
message; | ||
error; | ||
constructor(error, target) { | ||
super("error", target); | ||
this.message = error.message; | ||
this.error = error; | ||
} | ||
}; | ||
var CloseEvent = class extends Event { | ||
code; | ||
reason; | ||
wasClean = true; | ||
constructor(code = 1e3, reason = "", target) { | ||
super("close", target); | ||
this.code = code; | ||
this.reason = reason; | ||
} | ||
}; | ||
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 isNode = typeof process !== "undefined" && typeof process.versions?.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: Infinity, | ||
maxEnqueuedMessages: Infinity, | ||
startClosed: false, | ||
debug: false | ||
}; | ||
var didWarnAboutMissingWebSocket = false; | ||
var ReconnectingWebSocket = class _ReconnectingWebSocket extends EventTarget { | ||
_ws; | ||
_retryCount = -1; | ||
_uptimeTimeout; | ||
_connectTimeout; | ||
_shouldReconnect = true; | ||
_connectLock = false; | ||
_binaryType = "blob"; | ||
_closeCalled = false; | ||
_messageQueue = []; | ||
_debugLogger = console.log.bind(console); | ||
_url; | ||
_protocols; | ||
_options; | ||
constructor(url, protocols, options = {}) { | ||
super(); | ||
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 | ||
*/ | ||
onclose = null; | ||
/** | ||
* An event listener to be called when an error occurs | ||
*/ | ||
onerror = null; | ||
/** | ||
* An event listener to be called when a message is received from the server | ||
*/ | ||
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 | ||
*/ | ||
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 * Math.pow(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 { | ||
this._ws.close(code, reason); | ||
this._handleClose(new Events.CloseEvent(code, reason, this)); | ||
} catch (error) { | ||
} | ||
} | ||
_acceptOpen() { | ||
this._debug("accept open"); | ||
this._retryCount = 0; | ||
} | ||
_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) => this._ws?.send(message)); | ||
this._messageQueue = []; | ||
if (this.onopen) { | ||
this.onopen(event); | ||
} | ||
this.dispatchEvent(cloneEvent(event)); | ||
}; | ||
_handleMessage = (event) => { | ||
this._debug("message event"); | ||
if (this.onmessage) { | ||
this.onmessage(event); | ||
} | ||
this.dispatchEvent(cloneEvent(event)); | ||
}; | ||
_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(); | ||
}; | ||
_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); | ||
} | ||
}; | ||
// src/index.ts | ||
var valueIsNotNil = (keyValuePair) => keyValuePair[1] !== null && keyValuePair[1] !== void 0; | ||
function generateUUID() { | ||
if (typeof crypto !== "undefined" && crypto.randomUUID) { | ||
return crypto.randomUUID(); | ||
} | ||
let d = (/* @__PURE__ */ new Date()).getTime(); | ||
let d2 = typeof performance !== "undefined" && performance.now && performance.now() * 1e3 || 0; | ||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { | ||
let r = Math.random() * 16; | ||
if (d > 0) { | ||
r = (d + r) % 16 | 0; | ||
d = Math.floor(d / 16); | ||
} else { | ||
r = (d2 + r) % 16 | 0; | ||
d2 = Math.floor(d2 / 16); | ||
} | ||
return (c === "x" ? r : r & 3 | 8).toString(16); | ||
}); | ||
} | ||
function getPartyInfo(partySocketOptions, defaultProtocol, defaultParams = {}) { | ||
const { | ||
host: rawHost, | ||
path: rawPath, | ||
protocol: rawProtocol, | ||
room, | ||
party, | ||
query | ||
} = partySocketOptions; | ||
let host = rawHost.replace(/^(http|https|ws|wss):\/\//, ""); | ||
if (host.endsWith("/")) { | ||
host = host.slice(0, -1); | ||
} | ||
if (rawPath && rawPath.startsWith("/")) { | ||
throw new Error("path must not start with a slash"); | ||
} | ||
const name = party ?? "main"; | ||
const path = rawPath ? `/${rawPath}` : ""; | ||
const protocol = rawProtocol || (host.startsWith("localhost:") || host.startsWith("127.0.0.1:") || host.startsWith("192.168.") || host.startsWith("10.") || host.startsWith("172.") && host.split(".")[1] >= "16" && host.split(".")[1] <= "31" || host.startsWith("[::ffff:7f00:1]:") ? ( | ||
// http / ws | ||
defaultProtocol | ||
) : ( | ||
// https / wss | ||
defaultProtocol + "s" | ||
)); | ||
const baseUrl = `${protocol}://${host}/${party ? `parties/${party}` : "party"}/${room}${path}`; | ||
const makeUrl = (query2 = {}) => `${baseUrl}?${new URLSearchParams([ | ||
...Object.entries(defaultParams), | ||
...Object.entries(query2).filter(valueIsNotNil) | ||
])}`; | ||
const urlProvider = typeof query === "function" ? async () => makeUrl(await query()) : makeUrl(query); | ||
return { | ||
host, | ||
path, | ||
room, | ||
name, | ||
protocol, | ||
partyUrl: baseUrl, | ||
urlProvider | ||
}; | ||
} | ||
var PartySocket = class extends ReconnectingWebSocket { | ||
constructor(partySocketOptions) { | ||
const wsOptions = getWSOptions(partySocketOptions); | ||
super(wsOptions.urlProvider, wsOptions.protocols, wsOptions.socketOptions); | ||
this.partySocketOptions = partySocketOptions; | ||
this.setWSProperties(wsOptions); | ||
} | ||
_pk; | ||
_pkurl; | ||
name; | ||
room; | ||
host; | ||
path; | ||
updateProperties(partySocketOptions) { | ||
const wsOptions = getWSOptions({ | ||
...this.partySocketOptions, | ||
...partySocketOptions, | ||
host: partySocketOptions.host ?? this.host, | ||
room: partySocketOptions.room ?? this.room, | ||
path: partySocketOptions.path ?? this.path | ||
}); | ||
this._url = wsOptions.urlProvider; | ||
this._protocols = wsOptions.protocols; | ||
this._options = wsOptions.socketOptions; | ||
this.setWSProperties(wsOptions); | ||
} | ||
setWSProperties(wsOptions) { | ||
const { _pk, _pkurl, name, room, host, path } = wsOptions; | ||
this._pk = _pk; | ||
this._pkurl = _pkurl; | ||
this.name = name; | ||
this.room = room; | ||
this.host = host; | ||
this.path = path; | ||
} | ||
reconnect(code, reason) { | ||
if (!this.room || !this.host) { | ||
throw new Error( | ||
"The room and host must be set before connecting, use `updateProperties` method to set them or pass them to the constructor." | ||
); | ||
} | ||
super.reconnect(code, reason); | ||
} | ||
get id() { | ||
return this._pk; | ||
} | ||
/** | ||
* Exposes the static PartyKit room URL without applying query parameters. | ||
* To access the currently connected WebSocket url, use PartySocket#url. | ||
*/ | ||
get roomUrl() { | ||
return this._pkurl; | ||
} | ||
// a `fetch` method that uses (almost) the same options as `PartySocket` | ||
static async fetch(options, init) { | ||
const party = getPartyInfo(options, "http"); | ||
const url = typeof party.urlProvider === "string" ? party.urlProvider : await party.urlProvider(); | ||
const doFetch = options.fetch ?? fetch; | ||
return doFetch(url, init); | ||
} | ||
}; | ||
function getWSOptions(partySocketOptions) { | ||
const { | ||
id, | ||
host: _host, | ||
path: _path, | ||
party: _party, | ||
room: _room, | ||
protocol: _protocol, | ||
query: _query, | ||
protocols, | ||
...socketOptions | ||
} = partySocketOptions; | ||
const _pk = id || generateUUID(); | ||
const party = getPartyInfo(partySocketOptions, "ws", { _pk }); | ||
return { | ||
_pk, | ||
_pkurl: party.partyUrl, | ||
name: party.name, | ||
room: party.room, | ||
host: party.host, | ||
path: party.path, | ||
protocols, | ||
socketOptions, | ||
urlProvider: party.urlProvider | ||
}; | ||
} | ||
// Annotate the CommonJS export names for ESM import in node: | ||
0 && (module.exports = { | ||
WebSocket | ||
}); | ||
/*! | ||
* Reconnecting WebSocket | ||
* by Pedro Ladaria <pedro.ladaria@gmail.com> | ||
* https://github.com/pladaria/reconnecting-websocket | ||
* License MIT | ||
*/ | ||
//# sourceMappingURL=index.js.map |
@@ -1,747 +0,13 @@ | ||
"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); | ||
import { | ||
PartySocket | ||
} from "./chunk-5XUHNIY2.js"; | ||
import { | ||
getOptionsThatShouldCauseRestartWhenChanged, | ||
useAttachWebSocketEventHandlers, | ||
useStableSocket, | ||
useWebSocket | ||
} from "./chunk-LREFFSFP.js"; | ||
import "./chunk-2ESZ2MDZ.js"; | ||
// src/react.ts | ||
var react_exports = {}; | ||
__export(react_exports, { | ||
default: () => usePartySocket, | ||
usePartySocket: () => usePartySocket, | ||
useWebSocket: () => useWebSocket | ||
}); | ||
module.exports = __toCommonJS(react_exports); | ||
// 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 { | ||
message; | ||
error; | ||
constructor(error, target) { | ||
super("error", target); | ||
this.message = error.message; | ||
this.error = error; | ||
} | ||
}; | ||
var CloseEvent = class extends Event { | ||
code; | ||
reason; | ||
wasClean = true; | ||
constructor(code = 1e3, reason = "", target) { | ||
super("close", target); | ||
this.code = code; | ||
this.reason = reason; | ||
} | ||
}; | ||
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 isNode = typeof process !== "undefined" && typeof process.versions?.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: Infinity, | ||
maxEnqueuedMessages: Infinity, | ||
startClosed: false, | ||
debug: false | ||
}; | ||
var didWarnAboutMissingWebSocket = false; | ||
var ReconnectingWebSocket = class _ReconnectingWebSocket extends EventTarget { | ||
_ws; | ||
_retryCount = -1; | ||
_uptimeTimeout; | ||
_connectTimeout; | ||
_shouldReconnect = true; | ||
_connectLock = false; | ||
_binaryType = "blob"; | ||
_closeCalled = false; | ||
_messageQueue = []; | ||
_debugLogger = console.log.bind(console); | ||
_url; | ||
_protocols; | ||
_options; | ||
constructor(url, protocols, options = {}) { | ||
super(); | ||
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 | ||
*/ | ||
onclose = null; | ||
/** | ||
* An event listener to be called when an error occurs | ||
*/ | ||
onerror = null; | ||
/** | ||
* An event listener to be called when a message is received from the server | ||
*/ | ||
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 | ||
*/ | ||
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 * Math.pow(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 { | ||
this._ws.close(code, reason); | ||
this._handleClose(new Events.CloseEvent(code, reason, this)); | ||
} catch (error) { | ||
} | ||
} | ||
_acceptOpen() { | ||
this._debug("accept open"); | ||
this._retryCount = 0; | ||
} | ||
_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) => this._ws?.send(message)); | ||
this._messageQueue = []; | ||
if (this.onopen) { | ||
this.onopen(event); | ||
} | ||
this.dispatchEvent(cloneEvent(event)); | ||
}; | ||
_handleMessage = (event) => { | ||
this._debug("message event"); | ||
if (this.onmessage) { | ||
this.onmessage(event); | ||
} | ||
this.dispatchEvent(cloneEvent(event)); | ||
}; | ||
_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(); | ||
}; | ||
_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); | ||
} | ||
}; | ||
// src/index.ts | ||
var valueIsNotNil = (keyValuePair) => keyValuePair[1] !== null && keyValuePair[1] !== void 0; | ||
function generateUUID() { | ||
if (typeof crypto !== "undefined" && crypto.randomUUID) { | ||
return crypto.randomUUID(); | ||
} | ||
let d = (/* @__PURE__ */ new Date()).getTime(); | ||
let d2 = typeof performance !== "undefined" && performance.now && performance.now() * 1e3 || 0; | ||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { | ||
let r = Math.random() * 16; | ||
if (d > 0) { | ||
r = (d + r) % 16 | 0; | ||
d = Math.floor(d / 16); | ||
} else { | ||
r = (d2 + r) % 16 | 0; | ||
d2 = Math.floor(d2 / 16); | ||
} | ||
return (c === "x" ? r : r & 3 | 8).toString(16); | ||
}); | ||
} | ||
function getPartyInfo(partySocketOptions, defaultProtocol, defaultParams = {}) { | ||
const { | ||
host: rawHost, | ||
path: rawPath, | ||
protocol: rawProtocol, | ||
room, | ||
party, | ||
query | ||
} = partySocketOptions; | ||
let host = rawHost.replace(/^(http|https|ws|wss):\/\//, ""); | ||
if (host.endsWith("/")) { | ||
host = host.slice(0, -1); | ||
} | ||
if (rawPath && rawPath.startsWith("/")) { | ||
throw new Error("path must not start with a slash"); | ||
} | ||
const name = party ?? "main"; | ||
const path = rawPath ? `/${rawPath}` : ""; | ||
const protocol = rawProtocol || (host.startsWith("localhost:") || host.startsWith("127.0.0.1:") || host.startsWith("192.168.") || host.startsWith("10.") || host.startsWith("172.") && host.split(".")[1] >= "16" && host.split(".")[1] <= "31" || host.startsWith("[::ffff:7f00:1]:") ? ( | ||
// http / ws | ||
defaultProtocol | ||
) : ( | ||
// https / wss | ||
defaultProtocol + "s" | ||
)); | ||
const baseUrl = `${protocol}://${host}/${party ? `parties/${party}` : "party"}/${room}${path}`; | ||
const makeUrl = (query2 = {}) => `${baseUrl}?${new URLSearchParams([ | ||
...Object.entries(defaultParams), | ||
...Object.entries(query2).filter(valueIsNotNil) | ||
])}`; | ||
const urlProvider = typeof query === "function" ? async () => makeUrl(await query()) : makeUrl(query); | ||
return { | ||
host, | ||
path, | ||
room, | ||
name, | ||
protocol, | ||
partyUrl: baseUrl, | ||
urlProvider | ||
}; | ||
} | ||
var PartySocket = class extends ReconnectingWebSocket { | ||
constructor(partySocketOptions) { | ||
const wsOptions = getWSOptions(partySocketOptions); | ||
super(wsOptions.urlProvider, wsOptions.protocols, wsOptions.socketOptions); | ||
this.partySocketOptions = partySocketOptions; | ||
this.setWSProperties(wsOptions); | ||
} | ||
_pk; | ||
_pkurl; | ||
name; | ||
room; | ||
host; | ||
path; | ||
updateProperties(partySocketOptions) { | ||
const wsOptions = getWSOptions({ | ||
...this.partySocketOptions, | ||
...partySocketOptions, | ||
host: partySocketOptions.host ?? this.host, | ||
room: partySocketOptions.room ?? this.room, | ||
path: partySocketOptions.path ?? this.path | ||
}); | ||
this._url = wsOptions.urlProvider; | ||
this._protocols = wsOptions.protocols; | ||
this._options = wsOptions.socketOptions; | ||
this.setWSProperties(wsOptions); | ||
} | ||
setWSProperties(wsOptions) { | ||
const { _pk, _pkurl, name, room, host, path } = wsOptions; | ||
this._pk = _pk; | ||
this._pkurl = _pkurl; | ||
this.name = name; | ||
this.room = room; | ||
this.host = host; | ||
this.path = path; | ||
} | ||
reconnect(code, reason) { | ||
if (!this.room || !this.host) { | ||
throw new Error( | ||
"The room and host must be set before connecting, use `updateProperties` method to set them or pass them to the constructor." | ||
); | ||
} | ||
super.reconnect(code, reason); | ||
} | ||
get id() { | ||
return this._pk; | ||
} | ||
/** | ||
* Exposes the static PartyKit room URL without applying query parameters. | ||
* To access the currently connected WebSocket url, use PartySocket#url. | ||
*/ | ||
get roomUrl() { | ||
return this._pkurl; | ||
} | ||
// a `fetch` method that uses (almost) the same options as `PartySocket` | ||
static async fetch(options, init) { | ||
const party = getPartyInfo(options, "http"); | ||
const url = typeof party.urlProvider === "string" ? party.urlProvider : await party.urlProvider(); | ||
const doFetch = options.fetch ?? fetch; | ||
return doFetch(url, init); | ||
} | ||
}; | ||
function getWSOptions(partySocketOptions) { | ||
const { | ||
id, | ||
host: _host, | ||
path: _path, | ||
party: _party, | ||
room: _room, | ||
protocol: _protocol, | ||
query: _query, | ||
protocols, | ||
...socketOptions | ||
} = partySocketOptions; | ||
const _pk = id || generateUUID(); | ||
const party = getPartyInfo(partySocketOptions, "ws", { _pk }); | ||
return { | ||
_pk, | ||
_pkurl: party.partyUrl, | ||
name: party.name, | ||
room: party.room, | ||
host: party.host, | ||
path: party.path, | ||
protocols, | ||
socketOptions, | ||
urlProvider: party.urlProvider | ||
}; | ||
} | ||
// src/use-handlers.ts | ||
var import_react = require("react"); | ||
var useAttachWebSocketEventHandlers = (socket, options) => { | ||
const handlersRef = (0, import_react.useRef)(options); | ||
handlersRef.current = options; | ||
(0, import_react.useEffect)(() => { | ||
const onOpen = (event) => handlersRef.current?.onOpen?.(event); | ||
const onMessage = (event) => handlersRef.current?.onMessage?.(event); | ||
const onClose = (event) => handlersRef.current?.onClose?.(event); | ||
const onError = (event) => handlersRef.current?.onError?.(event); | ||
socket.addEventListener("open", onOpen); | ||
socket.addEventListener("close", onClose); | ||
socket.addEventListener("error", onError); | ||
socket.addEventListener("message", onMessage); | ||
return () => { | ||
socket.removeEventListener("open", onOpen); | ||
socket.removeEventListener("close", onClose); | ||
socket.removeEventListener("error", onError); | ||
socket.removeEventListener("message", onMessage); | ||
}; | ||
}, [socket]); | ||
}; | ||
// src/use-socket.ts | ||
var import_react2 = require("react"); | ||
var getOptionsThatShouldCauseRestartWhenChanged = (options) => [ | ||
options.startClosed, | ||
options.minUptime, | ||
options.maxRetries, | ||
options.connectionTimeout, | ||
options.maxEnqueuedMessages, | ||
options.maxReconnectionDelay, | ||
options.minReconnectionDelay, | ||
options.reconnectionDelayGrowFactor, | ||
options.debug | ||
]; | ||
function useStableSocket({ | ||
options, | ||
createSocket, | ||
createSocketMemoKey: createOptionsMemoKey | ||
}) { | ||
const shouldReconnect = createOptionsMemoKey(options); | ||
const socketOptions = (0, import_react2.useMemo)(() => { | ||
return options; | ||
}, [shouldReconnect]); | ||
const [socket, setSocket] = (0, import_react2.useState)( | ||
() => ( | ||
// only connect on first mount | ||
createSocket({ ...socketOptions, startClosed: true }) | ||
) | ||
); | ||
const socketInitializedRef = (0, import_react2.useRef)(null); | ||
const createSocketRef = (0, import_react2.useRef)(createSocket); | ||
createSocketRef.current = createSocket; | ||
(0, import_react2.useEffect)(() => { | ||
if (socketInitializedRef.current === socket) { | ||
const newSocket = createSocketRef.current({ | ||
...socketOptions, | ||
// when reconnecting because of options change, we always reconnect | ||
// (startClosed only applies to initial mount) | ||
startClosed: false | ||
}); | ||
setSocket(newSocket); | ||
} else { | ||
if (!socketInitializedRef.current && socketOptions.startClosed !== true) { | ||
socket.reconnect(); | ||
} | ||
socketInitializedRef.current = socket; | ||
return () => { | ||
socket.close(); | ||
}; | ||
} | ||
}, [socket, socketOptions]); | ||
return socket; | ||
} | ||
// src/use-ws.ts | ||
function useWebSocket(url, protocols, options = {}) { | ||
const socket = useStableSocket({ | ||
options, | ||
createSocket: (options2) => new ReconnectingWebSocket(url, protocols, options2), | ||
createSocketMemoKey: (options2) => JSON.stringify([ | ||
// will reconnect if url or protocols are specified as a string. | ||
// if they are functions, the WebSocket will handle reconnection | ||
url, | ||
protocols, | ||
...getOptionsThatShouldCauseRestartWhenChanged(options2) | ||
]) | ||
}); | ||
useAttachWebSocketEventHandlers(socket, options); | ||
return socket; | ||
} | ||
// src/react.ts | ||
function usePartySocket(options) { | ||
@@ -766,2 +32,4 @@ const { host, ...otherOptions } = options; | ||
options2.protocols, | ||
options2.basePath, | ||
options2.prefix, | ||
...getOptionsThatShouldCauseRestartWhenChanged(options2) | ||
@@ -773,12 +41,7 @@ ]) | ||
} | ||
// Annotate the CommonJS export names for ESM import in node: | ||
0 && (module.exports = { | ||
export { | ||
usePartySocket as default, | ||
usePartySocket, | ||
useWebSocket | ||
}); | ||
/*! | ||
* Reconnecting WebSocket | ||
* by Pedro Ladaria <pedro.ladaria@gmail.com> | ||
* https://github.com/pladaria/reconnecting-websocket | ||
* License MIT | ||
*/ | ||
}; | ||
//# sourceMappingURL=react.js.map |
@@ -1,599 +0,8 @@ | ||
"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 }); | ||
import { | ||
useWebSocket | ||
} from "./chunk-LREFFSFP.js"; | ||
import "./chunk-2ESZ2MDZ.js"; | ||
export { | ||
useWebSocket as default | ||
}; | ||
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); | ||
// src/use-ws.ts | ||
var use_ws_exports = {}; | ||
__export(use_ws_exports, { | ||
default: () => useWebSocket | ||
}); | ||
module.exports = __toCommonJS(use_ws_exports); | ||
// src/use-handlers.ts | ||
var import_react = require("react"); | ||
var useAttachWebSocketEventHandlers = (socket, options) => { | ||
const handlersRef = (0, import_react.useRef)(options); | ||
handlersRef.current = options; | ||
(0, import_react.useEffect)(() => { | ||
const onOpen = (event) => handlersRef.current?.onOpen?.(event); | ||
const onMessage = (event) => handlersRef.current?.onMessage?.(event); | ||
const onClose = (event) => handlersRef.current?.onClose?.(event); | ||
const onError = (event) => handlersRef.current?.onError?.(event); | ||
socket.addEventListener("open", onOpen); | ||
socket.addEventListener("close", onClose); | ||
socket.addEventListener("error", onError); | ||
socket.addEventListener("message", onMessage); | ||
return () => { | ||
socket.removeEventListener("open", onOpen); | ||
socket.removeEventListener("close", onClose); | ||
socket.removeEventListener("error", onError); | ||
socket.removeEventListener("message", onMessage); | ||
}; | ||
}, [socket]); | ||
}; | ||
// src/use-socket.ts | ||
var import_react2 = require("react"); | ||
var getOptionsThatShouldCauseRestartWhenChanged = (options) => [ | ||
options.startClosed, | ||
options.minUptime, | ||
options.maxRetries, | ||
options.connectionTimeout, | ||
options.maxEnqueuedMessages, | ||
options.maxReconnectionDelay, | ||
options.minReconnectionDelay, | ||
options.reconnectionDelayGrowFactor, | ||
options.debug | ||
]; | ||
function useStableSocket({ | ||
options, | ||
createSocket, | ||
createSocketMemoKey: createOptionsMemoKey | ||
}) { | ||
const shouldReconnect = createOptionsMemoKey(options); | ||
const socketOptions = (0, import_react2.useMemo)(() => { | ||
return options; | ||
}, [shouldReconnect]); | ||
const [socket, setSocket] = (0, import_react2.useState)( | ||
() => ( | ||
// only connect on first mount | ||
createSocket({ ...socketOptions, startClosed: true }) | ||
) | ||
); | ||
const socketInitializedRef = (0, import_react2.useRef)(null); | ||
const createSocketRef = (0, import_react2.useRef)(createSocket); | ||
createSocketRef.current = createSocket; | ||
(0, import_react2.useEffect)(() => { | ||
if (socketInitializedRef.current === socket) { | ||
const newSocket = createSocketRef.current({ | ||
...socketOptions, | ||
// when reconnecting because of options change, we always reconnect | ||
// (startClosed only applies to initial mount) | ||
startClosed: false | ||
}); | ||
setSocket(newSocket); | ||
} else { | ||
if (!socketInitializedRef.current && socketOptions.startClosed !== true) { | ||
socket.reconnect(); | ||
} | ||
socketInitializedRef.current = socket; | ||
return () => { | ||
socket.close(); | ||
}; | ||
} | ||
}, [socket, socketOptions]); | ||
return socket; | ||
} | ||
// 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 { | ||
message; | ||
error; | ||
constructor(error, target) { | ||
super("error", target); | ||
this.message = error.message; | ||
this.error = error; | ||
} | ||
}; | ||
var CloseEvent = class extends Event { | ||
code; | ||
reason; | ||
wasClean = true; | ||
constructor(code = 1e3, reason = "", target) { | ||
super("close", target); | ||
this.code = code; | ||
this.reason = reason; | ||
} | ||
}; | ||
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 isNode = typeof process !== "undefined" && typeof process.versions?.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: Infinity, | ||
maxEnqueuedMessages: Infinity, | ||
startClosed: false, | ||
debug: false | ||
}; | ||
var didWarnAboutMissingWebSocket = false; | ||
var ReconnectingWebSocket = class _ReconnectingWebSocket extends EventTarget { | ||
_ws; | ||
_retryCount = -1; | ||
_uptimeTimeout; | ||
_connectTimeout; | ||
_shouldReconnect = true; | ||
_connectLock = false; | ||
_binaryType = "blob"; | ||
_closeCalled = false; | ||
_messageQueue = []; | ||
_debugLogger = console.log.bind(console); | ||
_url; | ||
_protocols; | ||
_options; | ||
constructor(url, protocols, options = {}) { | ||
super(); | ||
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 | ||
*/ | ||
onclose = null; | ||
/** | ||
* An event listener to be called when an error occurs | ||
*/ | ||
onerror = null; | ||
/** | ||
* An event listener to be called when a message is received from the server | ||
*/ | ||
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 | ||
*/ | ||
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 * Math.pow(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 { | ||
this._ws.close(code, reason); | ||
this._handleClose(new Events.CloseEvent(code, reason, this)); | ||
} catch (error) { | ||
} | ||
} | ||
_acceptOpen() { | ||
this._debug("accept open"); | ||
this._retryCount = 0; | ||
} | ||
_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) => this._ws?.send(message)); | ||
this._messageQueue = []; | ||
if (this.onopen) { | ||
this.onopen(event); | ||
} | ||
this.dispatchEvent(cloneEvent(event)); | ||
}; | ||
_handleMessage = (event) => { | ||
this._debug("message event"); | ||
if (this.onmessage) { | ||
this.onmessage(event); | ||
} | ||
this.dispatchEvent(cloneEvent(event)); | ||
}; | ||
_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(); | ||
}; | ||
_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); | ||
} | ||
}; | ||
// src/use-ws.ts | ||
function useWebSocket(url, protocols, options = {}) { | ||
const socket = useStableSocket({ | ||
options, | ||
createSocket: (options2) => new ReconnectingWebSocket(url, protocols, options2), | ||
createSocketMemoKey: (options2) => JSON.stringify([ | ||
// will reconnect if url or protocols are specified as a string. | ||
// if they are functions, the WebSocket will handle reconnection | ||
url, | ||
protocols, | ||
...getOptionsThatShouldCauseRestartWhenChanged(options2) | ||
]) | ||
}); | ||
useAttachWebSocketEventHandlers(socket, options); | ||
return socket; | ||
} | ||
/*! | ||
* Reconnecting WebSocket | ||
* by Pedro Ladaria <pedro.ladaria@gmail.com> | ||
* https://github.com/pladaria/reconnecting-websocket | ||
* License MIT | ||
*/ | ||
//# sourceMappingURL=use-ws.js.map |
518
dist/ws.js
@@ -1,511 +0,11 @@ | ||
"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); | ||
// src/ws.ts | ||
var ws_exports = {}; | ||
__export(ws_exports, { | ||
CloseEvent: () => CloseEvent, | ||
ErrorEvent: () => ErrorEvent, | ||
default: () => ReconnectingWebSocket | ||
}); | ||
module.exports = __toCommonJS(ws_exports); | ||
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 { | ||
message; | ||
error; | ||
constructor(error, target) { | ||
super("error", target); | ||
this.message = error.message; | ||
this.error = error; | ||
} | ||
}; | ||
var CloseEvent = class extends Event { | ||
code; | ||
reason; | ||
wasClean = true; | ||
constructor(code = 1e3, reason = "", target) { | ||
super("close", target); | ||
this.code = code; | ||
this.reason = reason; | ||
} | ||
}; | ||
var Events = { | ||
Event, | ||
import { | ||
CloseEvent, | ||
ErrorEvent, | ||
CloseEvent | ||
ReconnectingWebSocket | ||
} from "./chunk-2ESZ2MDZ.js"; | ||
export { | ||
CloseEvent, | ||
ErrorEvent, | ||
ReconnectingWebSocket as default | ||
}; | ||
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 isNode = typeof process !== "undefined" && typeof process.versions?.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: Infinity, | ||
maxEnqueuedMessages: Infinity, | ||
startClosed: false, | ||
debug: false | ||
}; | ||
var didWarnAboutMissingWebSocket = false; | ||
var ReconnectingWebSocket = class _ReconnectingWebSocket extends EventTarget { | ||
_ws; | ||
_retryCount = -1; | ||
_uptimeTimeout; | ||
_connectTimeout; | ||
_shouldReconnect = true; | ||
_connectLock = false; | ||
_binaryType = "blob"; | ||
_closeCalled = false; | ||
_messageQueue = []; | ||
_debugLogger = console.log.bind(console); | ||
_url; | ||
_protocols; | ||
_options; | ||
constructor(url, protocols, options = {}) { | ||
super(); | ||
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 | ||
*/ | ||
onclose = null; | ||
/** | ||
* An event listener to be called when an error occurs | ||
*/ | ||
onerror = null; | ||
/** | ||
* An event listener to be called when a message is received from the server | ||
*/ | ||
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 | ||
*/ | ||
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 * Math.pow(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 { | ||
this._ws.close(code, reason); | ||
this._handleClose(new Events.CloseEvent(code, reason, this)); | ||
} catch (error) { | ||
} | ||
} | ||
_acceptOpen() { | ||
this._debug("accept open"); | ||
this._retryCount = 0; | ||
} | ||
_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) => this._ws?.send(message)); | ||
this._messageQueue = []; | ||
if (this.onopen) { | ||
this.onopen(event); | ||
} | ||
this.dispatchEvent(cloneEvent(event)); | ||
}; | ||
_handleMessage = (event) => { | ||
this._debug("message event"); | ||
if (this.onmessage) { | ||
this.onmessage(event); | ||
} | ||
this.dispatchEvent(cloneEvent(event)); | ||
}; | ||
_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(); | ||
}; | ||
_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); | ||
} | ||
}; | ||
// Annotate the CommonJS export names for ESM import in node: | ||
0 && (module.exports = { | ||
CloseEvent, | ||
ErrorEvent | ||
}); | ||
/*! | ||
* Reconnecting WebSocket | ||
* by Pedro Ladaria <pedro.ladaria@gmail.com> | ||
* https://github.com/pladaria/reconnecting-websocket | ||
* License MIT | ||
*/ | ||
//# sourceMappingURL=ws.js.map |
148
package.json
{ | ||
"name": "partysocket", | ||
"version": "0.0.0-60d6b17", | ||
"description": "A better WebSocket that Just Works™", | ||
"homepage": "https://docs.partykit.io/reference/partysocket-api", | ||
"bugs": "https://github.com/partykit/partykit/issues", | ||
"main": "./dist/index.js", | ||
"module": "./dist/index.mjs", | ||
"exports": { | ||
".": { | ||
"types": "./index.d.ts", | ||
"import": "./dist/index.mjs", | ||
"require": "./dist/index.js" | ||
}, | ||
"./ws": { | ||
"types": "./ws.d.ts", | ||
"import": "./dist/ws.mjs", | ||
"require": "./dist/ws.js" | ||
}, | ||
"./react": { | ||
"types": "./react.d.ts", | ||
"import": "./dist/react.mjs", | ||
"require": "./dist/react.js" | ||
}, | ||
"./use-ws": { | ||
"types": "./use-ws.d.ts", | ||
"import": "./dist/use-ws.mjs", | ||
"require": "./dist/use-ws.js" | ||
}, | ||
"./event-target-polyfill": { | ||
"types": "./event-target-polyfill.d.ts", | ||
"import": "./dist/event-target-polyfill.mjs", | ||
"require": "./dist/event-target-polyfill.js" | ||
} | ||
}, | ||
"tsup": { | ||
"entry": [ | ||
"src/index.ts", | ||
"src/react.ts", | ||
"src/ws.ts", | ||
"src/use-ws.ts", | ||
"src/event-target-polyfill.ts" | ||
], | ||
"format": [ | ||
"esm", | ||
"cjs" | ||
], | ||
"dts": true | ||
}, | ||
"scripts": { | ||
"clean": "shx rm -rf dist *.d.ts *.d.mts event-target-polyfill.*", | ||
"post-build": "shx mv dist/*.d.ts dist/*.d.mts* . && shx mv dist/event-target-polyfill.* .", | ||
"build": "npm run clean && tsup --external react && npm run post-build", | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"files": [ | ||
"dist", | ||
"*.d.ts", | ||
"event-target-polyfill.*" | ||
], | ||
"keywords": [ | ||
"websocket", | ||
"client", | ||
"reconnecting", | ||
"reconnection", | ||
"reconnect", | ||
"forever", | ||
"persistent", | ||
"forever", | ||
"automatic" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/partykit/partykit.git", | ||
"directory": "packages/partykit" | ||
}, | ||
"author": "", | ||
"license": "ISC", | ||
"dependencies": { | ||
"event-target-shim": "^6.0.2" | ||
} | ||
"name": "partysocket", | ||
"version": "0.0.0-60e4afa", | ||
"type": "module", | ||
"description": "A better WebSocket that Just Works™", | ||
"homepage": "https://docs.partykit.io/reference/partysocket-api", | ||
"bugs": "https://github.com/threepointone/partyserver/issues", | ||
"main": "./dist/index.js", | ||
"module": "./dist/index.mjs", | ||
"exports": { | ||
".": { | ||
"types": "./dist/index.d.ts", | ||
"import": "./dist/index.mjs", | ||
"require": "./dist/index.js" | ||
}, | ||
"./ws": { | ||
"types": "./dist/ws.d.ts", | ||
"import": "./dist/ws.mjs", | ||
"require": "./dist/ws.js" | ||
}, | ||
"./react": { | ||
"types": "./dist/react.d.ts", | ||
"import": "./dist/react.mjs", | ||
"require": "./dist/react.js" | ||
}, | ||
"./use-ws": { | ||
"types": "./dist/use-ws.d.ts", | ||
"import": "./dist/use-ws.mjs", | ||
"require": "./dist/use-ws.js" | ||
}, | ||
"./event-target-polyfill": { | ||
"types": "./dist/event-target-polyfill.d.ts", | ||
"import": "./dist/event-target-polyfill.mjs", | ||
"require": "./dist/event-target-polyfill.js" | ||
} | ||
}, | ||
"scripts": { | ||
"build": "npx tsx scripts/build.ts", | ||
"check:test": "vitest --no-watch" | ||
}, | ||
"files": [ | ||
"dist", | ||
"*.d.ts", | ||
"event-target-polyfill.*" | ||
], | ||
"keywords": [ | ||
"websocket", | ||
"client", | ||
"reconnecting", | ||
"reconnection", | ||
"reconnect", | ||
"forever", | ||
"persistent", | ||
"forever", | ||
"automatic" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/threepointone/partyserver.git", | ||
"directory": "packages/partysocket" | ||
}, | ||
"author": "", | ||
"license": "ISC", | ||
"dependencies": { | ||
"event-target-polyfill": "^0.0.4" | ||
}, | ||
"devDependencies": { | ||
"@types/ws": "^8.18.0" | ||
} | ||
} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
180764
55.82%47
104.35%1
-50%Yes
NaN1
Infinity%1887
-46.98%+ Added
+ Added
- Removed
- Removed