partysocket
Advanced tools
Comparing version 0.0.0-9c5df06 to 0.0.0-9cb3fe9
@@ -23,3 +23,4 @@ "use strict"; | ||
__export(src_exports, { | ||
WebSocket: () => WebSocket2, | ||
PartySocket: () => PartySocket, | ||
WebSocket: () => ReconnectingWebSocket, | ||
default: () => PartySocket | ||
@@ -33,17 +34,7 @@ }); | ||
PartySocket requires a global 'EventTarget' class to be available! | ||
You can use the 'event-target-shim' package to polyfill this. See https://www.npmjs.com/package/event-target-shim. | ||
First, run: | ||
You can polyfill this global by adding this to your code before any partysocket imports: | ||
\`\`\` | ||
npm install event-target-shim | ||
import 'partysocket/event-target-polyfill'; | ||
\`\`\` | ||
Then, add this in your code: | ||
\`\`\` | ||
import {Event, EventTarget} from 'event-target-shim'; | ||
if(!globalThis.Event) { | ||
globalThis.Event = Event; | ||
} | ||
if(!globalThis.EventTarget) { | ||
globalThis.EventTarget = EventTarget; | ||
} | ||
\`\`\` | ||
Please file an issue at https://github.com/partykit/partykit if you're still having trouble. | ||
@@ -81,5 +72,29 @@ `); | ||
} | ||
function cloneEvent(e) { | ||
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 = { | ||
@@ -96,2 +111,3 @@ maxReconnectionDelay: 1e4, | ||
}; | ||
var didWarnAboutMissingWebSocket = false; | ||
var ReconnectingWebSocket = class _ReconnectingWebSocket extends EventTarget { | ||
@@ -107,2 +123,3 @@ _ws; | ||
_messageQueue = []; | ||
_debugLogger = console.log.bind(console); | ||
_url; | ||
@@ -119,2 +136,5 @@ _protocols; | ||
} | ||
if (this._options.debugLogger) { | ||
this._debugLogger = this._options.debugLogger; | ||
} | ||
this._connect(); | ||
@@ -283,3 +303,3 @@ } | ||
if (this._options.debug) { | ||
console.log.apply(console, ["RWS>", ...args]); | ||
this._debugLogger("RWS>", ...args); | ||
} | ||
@@ -309,4 +329,3 @@ } | ||
_getNextProtocols(protocolsProvider) { | ||
if (!protocolsProvider) | ||
return Promise.resolve(null); | ||
if (!protocolsProvider) return Promise.resolve(null); | ||
if (typeof protocolsProvider === "string" || Array.isArray(protocolsProvider)) { | ||
@@ -317,4 +336,3 @@ return Promise.resolve(protocolsProvider); | ||
const protocols = protocolsProvider(); | ||
if (!protocols) | ||
return Promise.resolve(null); | ||
if (!protocols) return Promise.resolve(null); | ||
if (typeof protocols === "string" || Array.isArray(protocols)) { | ||
@@ -370,4 +388,22 @@ return Promise.resolve(protocols); | ||
} | ||
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 WebSocket(url, protocols) : new WebSocket(url); | ||
this._ws = protocols ? new WS(url, protocols) : new WS(url); | ||
this._ws.binaryType = this._binaryType; | ||
@@ -477,2 +513,3 @@ this._connectLock = false; | ||
// src/index.ts | ||
var valueIsNotNil = (keyValuePair) => keyValuePair[1] !== null && keyValuePair[1] !== void 0; | ||
function generateUUID() { | ||
@@ -496,33 +533,134 @@ if (typeof crypto !== "undefined" && crypto.randomUUID) { | ||
} | ||
function getPartyInfo(partySocketOptions, defaultProtocol, defaultParams = {}) { | ||
const { | ||
host: rawHost, | ||
path: rawPath, | ||
protocol: rawProtocol, | ||
room, | ||
party, | ||
prefix, | ||
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}/${prefix || `parties/${name}/${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 { | ||
host: rawHost, | ||
room, | ||
party, | ||
protocol, | ||
query, | ||
protocols, | ||
...socketOptions | ||
} = partySocketOptions; | ||
const _pk = partySocketOptions.id || generateUUID(); | ||
const host = rawHost.replace(/^(http|https|ws|wss):\/\//, ""); | ||
let url = `${protocol || (host.startsWith("localhost:") || host.startsWith("127.0.0.1:") ? "ws" : "wss")}://${host}/${party ? `parties/${party}` : "party"}/${room}`; | ||
if (query) { | ||
url += `?${new URLSearchParams({ ...query, _pk }).toString()}`; | ||
} else { | ||
url += `?_pk=${_pk}`; | ||
} | ||
super(url, protocols, socketOptions); | ||
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; | ||
} | ||
_pk; | ||
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); | ||
} | ||
}; | ||
var WebSocket2 = ReconnectingWebSocket; | ||
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 = { | ||
PartySocket, | ||
WebSocket | ||
@@ -529,0 +667,0 @@ }); |
@@ -23,6 +23,7 @@ "use strict"; | ||
__export(react_exports, { | ||
default: () => usePartySocket | ||
default: () => usePartySocket, | ||
usePartySocket: () => usePartySocket, | ||
useWebSocket: () => useWebSocket | ||
}); | ||
module.exports = __toCommonJS(react_exports); | ||
var import_react = require("react"); | ||
@@ -33,17 +34,7 @@ // src/ws.ts | ||
PartySocket requires a global 'EventTarget' class to be available! | ||
You can use the 'event-target-shim' package to polyfill this. See https://www.npmjs.com/package/event-target-shim. | ||
First, run: | ||
You can polyfill this global by adding this to your code before any partysocket imports: | ||
\`\`\` | ||
npm install event-target-shim | ||
import 'partysocket/event-target-polyfill'; | ||
\`\`\` | ||
Then, add this in your code: | ||
\`\`\` | ||
import {Event, EventTarget} from 'event-target-shim'; | ||
if(!globalThis.Event) { | ||
globalThis.Event = Event; | ||
} | ||
if(!globalThis.EventTarget) { | ||
globalThis.EventTarget = EventTarget; | ||
} | ||
\`\`\` | ||
Please file an issue at https://github.com/partykit/partykit if you're still having trouble. | ||
@@ -81,5 +72,29 @@ `); | ||
} | ||
function cloneEvent(e) { | ||
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 = { | ||
@@ -96,2 +111,3 @@ maxReconnectionDelay: 1e4, | ||
}; | ||
var didWarnAboutMissingWebSocket = false; | ||
var ReconnectingWebSocket = class _ReconnectingWebSocket extends EventTarget { | ||
@@ -107,2 +123,3 @@ _ws; | ||
_messageQueue = []; | ||
_debugLogger = console.log.bind(console); | ||
_url; | ||
@@ -119,2 +136,5 @@ _protocols; | ||
} | ||
if (this._options.debugLogger) { | ||
this._debugLogger = this._options.debugLogger; | ||
} | ||
this._connect(); | ||
@@ -283,3 +303,3 @@ } | ||
if (this._options.debug) { | ||
console.log.apply(console, ["RWS>", ...args]); | ||
this._debugLogger("RWS>", ...args); | ||
} | ||
@@ -309,4 +329,3 @@ } | ||
_getNextProtocols(protocolsProvider) { | ||
if (!protocolsProvider) | ||
return Promise.resolve(null); | ||
if (!protocolsProvider) return Promise.resolve(null); | ||
if (typeof protocolsProvider === "string" || Array.isArray(protocolsProvider)) { | ||
@@ -317,4 +336,3 @@ return Promise.resolve(protocolsProvider); | ||
const protocols = protocolsProvider(); | ||
if (!protocols) | ||
return Promise.resolve(null); | ||
if (!protocols) return Promise.resolve(null); | ||
if (typeof protocols === "string" || Array.isArray(protocols)) { | ||
@@ -370,4 +388,22 @@ return Promise.resolve(protocols); | ||
} | ||
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 WebSocket(url, protocols) : new WebSocket(url); | ||
this._ws = protocols ? new WS(url, protocols) : new WS(url); | ||
this._ws.binaryType = this._binaryType; | ||
@@ -477,2 +513,3 @@ this._connectLock = false; | ||
// src/index.ts | ||
var valueIsNotNil = (keyValuePair) => keyValuePair[1] !== null && keyValuePair[1] !== void 0; | ||
function generateUUID() { | ||
@@ -496,77 +533,256 @@ if (typeof crypto !== "undefined" && crypto.randomUUID) { | ||
} | ||
function getPartyInfo(partySocketOptions, defaultProtocol, defaultParams = {}) { | ||
const { | ||
host: rawHost, | ||
path: rawPath, | ||
protocol: rawProtocol, | ||
room, | ||
party, | ||
prefix, | ||
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}/${prefix || `parties/${name}/${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 { | ||
host: rawHost, | ||
room, | ||
party, | ||
protocol, | ||
query, | ||
protocols, | ||
...socketOptions | ||
} = partySocketOptions; | ||
const _pk = partySocketOptions.id || generateUUID(); | ||
const host = rawHost.replace(/^(http|https|ws|wss):\/\//, ""); | ||
let url = `${protocol || (host.startsWith("localhost:") || host.startsWith("127.0.0.1:") ? "ws" : "wss")}://${host}/${party ? `parties/${party}` : "party"}/${room}`; | ||
if (query) { | ||
url += `?${new URLSearchParams({ ...query, _pk }).toString()}`; | ||
} else { | ||
url += `?_pk=${_pk}`; | ||
} | ||
super(url, protocols, socketOptions); | ||
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; | ||
} | ||
_pk; | ||
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/react.ts | ||
function usePartySocket(options) { | ||
const { onOpen, onMessage, onClose, onError, ...partySocketOptions } = options; | ||
const socketRef = (0, import_react.useRef)( | ||
new PartySocket({ | ||
...partySocketOptions, | ||
startClosed: true | ||
// only connect on mount | ||
}) | ||
); | ||
// 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 socket = socketRef.current; | ||
if (onOpen) | ||
socket.addEventListener("open", onOpen); | ||
if (onClose) | ||
socket.addEventListener("close", onClose); | ||
if (onError) | ||
socket.addEventListener("error", onError); | ||
if (onMessage) | ||
socket.addEventListener("message", onMessage); | ||
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 () => { | ||
if (onOpen) | ||
socket.removeEventListener("open", onOpen); | ||
if (onClose) | ||
socket.removeEventListener("close", onClose); | ||
if (onError) | ||
socket.removeEventListener("error", onError); | ||
if (onMessage) | ||
socket.removeEventListener("message", onMessage); | ||
socket.removeEventListener("open", onOpen); | ||
socket.removeEventListener("close", onClose); | ||
socket.removeEventListener("error", onError); | ||
socket.removeEventListener("message", onMessage); | ||
}; | ||
}, [onOpen, onMessage, onClose, onError]); | ||
(0, import_react.useEffect)( | ||
() => { | ||
const socket = socketRef.current; | ||
if (options.startClosed !== true) { | ||
}, [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) { | ||
const { host, ...otherOptions } = options; | ||
const socket = useStableSocket({ | ||
options: { | ||
host: host || (typeof window !== "undefined" ? window.location.host : "dummy-domain.com"), | ||
...otherOptions | ||
}, | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
[] | ||
); | ||
return socketRef.current; | ||
createSocket: (options2) => new PartySocket(options2), | ||
createSocketMemoKey: (options2) => JSON.stringify([ | ||
// NOTE: if query is defined as a function, the socket | ||
// won't reconnect when you change the function identity | ||
options2.query, | ||
options2.id, | ||
options2.host, | ||
options2.room, | ||
options2.party, | ||
options2.path, | ||
options2.protocol, | ||
options2.protocols, | ||
...getOptionsThatShouldCauseRestartWhenChanged(options2) | ||
]) | ||
}); | ||
useAttachWebSocketEventHandlers(socket, options); | ||
return socket; | ||
} | ||
// Annotate the CommonJS export names for ESM import in node: | ||
0 && (module.exports = { | ||
usePartySocket, | ||
useWebSocket | ||
}); | ||
/*! | ||
@@ -573,0 +789,0 @@ * Reconnecting WebSocket |
@@ -31,17 +31,7 @@ "use strict"; | ||
PartySocket requires a global 'EventTarget' class to be available! | ||
You can use the 'event-target-shim' package to polyfill this. See https://www.npmjs.com/package/event-target-shim. | ||
First, run: | ||
You can polyfill this global by adding this to your code before any partysocket imports: | ||
\`\`\` | ||
npm install event-target-shim | ||
import 'partysocket/event-target-polyfill'; | ||
\`\`\` | ||
Then, add this in your code: | ||
\`\`\` | ||
import {Event, EventTarget} from 'event-target-shim'; | ||
if(!globalThis.Event) { | ||
globalThis.Event = Event; | ||
} | ||
if(!globalThis.EventTarget) { | ||
globalThis.EventTarget = EventTarget; | ||
} | ||
\`\`\` | ||
Please file an issue at https://github.com/partykit/partykit if you're still having trouble. | ||
@@ -79,5 +69,29 @@ `); | ||
} | ||
function cloneEvent(e) { | ||
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 = { | ||
@@ -94,2 +108,3 @@ maxReconnectionDelay: 1e4, | ||
}; | ||
var didWarnAboutMissingWebSocket = false; | ||
var ReconnectingWebSocket = class _ReconnectingWebSocket extends EventTarget { | ||
@@ -105,2 +120,3 @@ _ws; | ||
_messageQueue = []; | ||
_debugLogger = console.log.bind(console); | ||
_url; | ||
@@ -117,2 +133,5 @@ _protocols; | ||
} | ||
if (this._options.debugLogger) { | ||
this._debugLogger = this._options.debugLogger; | ||
} | ||
this._connect(); | ||
@@ -281,3 +300,3 @@ } | ||
if (this._options.debug) { | ||
console.log.apply(console, ["RWS>", ...args]); | ||
this._debugLogger("RWS>", ...args); | ||
} | ||
@@ -307,4 +326,3 @@ } | ||
_getNextProtocols(protocolsProvider) { | ||
if (!protocolsProvider) | ||
return Promise.resolve(null); | ||
if (!protocolsProvider) return Promise.resolve(null); | ||
if (typeof protocolsProvider === "string" || Array.isArray(protocolsProvider)) { | ||
@@ -315,4 +333,3 @@ return Promise.resolve(protocolsProvider); | ||
const protocols = protocolsProvider(); | ||
if (!protocols) | ||
return Promise.resolve(null); | ||
if (!protocols) return Promise.resolve(null); | ||
if (typeof protocols === "string" || Array.isArray(protocols)) { | ||
@@ -368,4 +385,22 @@ return Promise.resolve(protocols); | ||
} | ||
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 WebSocket(url, protocols) : new WebSocket(url); | ||
this._ws = protocols ? new WS(url, protocols) : new WS(url); | ||
this._ws.binaryType = this._binaryType; | ||
@@ -372,0 +407,0 @@ this._connectLock = false; |
import ReconnectingWebSocket, { Options } from './ws.js'; | ||
type PartySocketOptions = Omit<Options, "WebSocket" | "constructor"> & { | ||
type Maybe<T> = T | null | undefined; | ||
type Params = Record<string, Maybe<string>>; | ||
type PartySocketOptions = Omit<Options, "constructor"> & { | ||
id?: string; | ||
host: string; | ||
room: string; | ||
room?: string; | ||
party?: string; | ||
protocol?: string; | ||
prefix?: string; | ||
protocol?: "ws" | "wss"; | ||
protocols?: string[]; | ||
query?: Record<string, string>; | ||
path?: string; | ||
query?: Params | (() => Params | Promise<Params>); | ||
}; | ||
type PartyFetchOptions = { | ||
host: string; | ||
room: string; | ||
party?: string; | ||
prefix?: string; | ||
path?: string; | ||
protocol?: "http" | "https"; | ||
query?: Params | (() => Params | Promise<Params>); | ||
fetch?: typeof fetch; | ||
}; | ||
declare class PartySocket extends ReconnectingWebSocket { | ||
readonly partySocketOptions: PartySocketOptions; | ||
_pk: string; | ||
_pkurl: string; | ||
name: string; | ||
room?: string; | ||
host: string; | ||
path: string; | ||
constructor(partySocketOptions: PartySocketOptions); | ||
updateProperties(partySocketOptions: Partial<PartySocketOptions>): void; | ||
private setWSProperties; | ||
reconnect(code?: number | undefined, reason?: string | undefined): void; | ||
get id(): string; | ||
/** | ||
* Exposes the static PartyKit room URL without applying query parameters. | ||
* To access the currently connected WebSocket url, use PartySocket#url. | ||
*/ | ||
get roomUrl(): string; | ||
static fetch(options: PartyFetchOptions, init?: RequestInit): Promise<Response>; | ||
} | ||
declare const WebSocket: typeof ReconnectingWebSocket; | ||
export { PartySocketOptions, WebSocket, PartySocket as default }; | ||
export { type PartyFetchOptions, PartySocket, type PartySocketOptions, ReconnectingWebSocket as WebSocket, PartySocket as default }; |
{ | ||
"name": "partysocket", | ||
"version": "0.0.0-9c5df06", | ||
"description": "party hotline", | ||
"version": "0.0.0-9cb3fe9", | ||
"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", | ||
@@ -22,2 +24,12 @@ "module": "./dist/index.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" | ||
} | ||
@@ -29,3 +41,5 @@ }, | ||
"src/react.ts", | ||
"src/ws.ts" | ||
"src/ws.ts", | ||
"src/use-ws.ts", | ||
"src/event-target-polyfill.ts" | ||
], | ||
@@ -38,9 +52,6 @@ "format": [ | ||
}, | ||
"dependencies": { | ||
"react": "^18.2.0" | ||
}, | ||
"scripts": { | ||
"clean": "shx rm -rf dist *.d.ts", | ||
"post-build": "shx mv dist/*.d.ts* .", | ||
"build": "npm run clean && tsup && npm run post-build", | ||
"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" | ||
@@ -50,7 +61,26 @@ }, | ||
"dist", | ||
"*.d.ts" | ||
"*.d.ts", | ||
"event-target-polyfill.*" | ||
], | ||
"keywords": [], | ||
"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" | ||
"license": "ISC", | ||
"dependencies": { | ||
"event-target-shim": "^6.0.2" | ||
} | ||
} |
@@ -1,12 +0,11 @@ | ||
import PartySocket, { PartySocketOptions } from './index.js'; | ||
import { PartySocket, PartySocketOptions } from './index.js'; | ||
import { E as EventHandlerOptions } from './use-ws-CnrrNZS2.js'; | ||
export { u as useWebSocket } from './use-ws-CnrrNZS2.js'; | ||
import './ws.js'; | ||
type UsePartySocketOptions = PartySocketOptions & { | ||
onOpen?: (event: WebSocketEventMap["open"]) => void; | ||
onMessage?: (event: WebSocketEventMap["message"]) => void; | ||
onClose?: (event: WebSocketEventMap["close"]) => void; | ||
onError?: (event: WebSocketEventMap["error"]) => void; | ||
type UsePartySocketOptions = Omit<PartySocketOptions, "host"> & EventHandlerOptions & { | ||
host?: string | undefined; | ||
}; | ||
declare function usePartySocket(options: UsePartySocketOptions): PartySocket; | ||
export { usePartySocket as default }; | ||
export { usePartySocket as default, usePartySocket }; |
@@ -37,3 +37,3 @@ # PartySocket | ||
const ws = new WebSocket("ws://my.site.com"); | ||
const ws = new WebSocket("wss://my.site.com"); | ||
@@ -50,2 +50,5 @@ ws.addEventListener("open", () => { | ||
// optional: only needed if creating using inside node.js. Run `npm install ws`, and then add: | ||
// import WS from "ws"; | ||
const ws = new PartySocket({ | ||
@@ -56,4 +59,15 @@ host: "project.name.partykit.dev", // or localhost:1999 in dev | ||
// if not provided, a random id will be generated | ||
id: "some-connection-id", | ||
id: "some-connection-id" | ||
// optional: if used from node.js, you need to pass the WebSocket polyfill imported from `ws` | ||
// WebSocket: WS | ||
}); | ||
// optionally, update the properties of the connection | ||
// (e.g. to change the host or room) | ||
ws.updateProperties({ | ||
host: "another-project.username.partykit.dev", | ||
room: "my-new-room" | ||
}); | ||
ws.reconnect(); // make sure to call reconnect() after updating the properties | ||
``` | ||
@@ -72,3 +86,7 @@ | ||
const urls = ["ws://my.site.com", "ws://your.site.com", "ws://their.site.com"]; | ||
const urls = [ | ||
"wss://my.site.com", | ||
"wss://your.site.com", | ||
"wss://their.site.com" | ||
]; | ||
let urlIndex = 0; | ||
@@ -106,3 +124,4 @@ | ||
import { WebSocket } from "partysocket"; | ||
const ws = new WebSocket("ws://your.site.com", "your protocol"); | ||
const ws = new WebSocket("wss://your.site.com", "your protocol"); | ||
``` | ||
@@ -119,3 +138,3 @@ | ||
const ws = new WebSocket('ws://your.site.com', protocolsProvider); | ||
const ws = new WebSocket('wss://your.site.com', protocolsProvider); | ||
``` | ||
@@ -134,5 +153,5 @@ | ||
connectionTimeout: 1000, | ||
maxRetries: 10, | ||
maxRetries: 10 | ||
}; | ||
const ws = new WebSocket("ws://my.site.com", [], options); | ||
const ws = new WebSocket("wss://my.site.com", [], options); | ||
``` | ||
@@ -139,0 +158,0 @@ |
11
ws.d.ts
@@ -36,2 +36,3 @@ type TypedEventTarget<EventMap extends object> = { | ||
type Options = { | ||
WebSocket?: any; | ||
maxReconnectionDelay?: number; | ||
@@ -46,2 +47,3 @@ minReconnectionDelay?: number; | ||
debug?: boolean; | ||
debugLogger?: (...args: any[]) => void; | ||
}; | ||
@@ -62,5 +64,6 @@ type UrlProvider = string | (() => string) | (() => Promise<string>); | ||
private _messageQueue; | ||
private readonly _url; | ||
private readonly _protocols?; | ||
private readonly _options; | ||
private _debugLogger; | ||
protected _url: UrlProvider; | ||
protected _protocols?: ProtocolsProvider; | ||
protected _options: Options; | ||
constructor(url: UrlProvider, protocols?: ProtocolsProvider, options?: Options); | ||
@@ -160,2 +163,2 @@ static get CONNECTING(): number; | ||
export { CloseEvent, ErrorEvent, Message, Options, ProtocolsProvider, UrlProvider, WebSocketEventMap, ReconnectingWebSocket as default }; | ||
export { CloseEvent, ErrorEvent, type Message, type Options, type ProtocolsProvider, type UrlProvider, type WebSocketEventMap, ReconnectingWebSocket as default }; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
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 repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
116067
23
3557
2
230
0
3
+ Addedevent-target-shim@^6.0.2
+ Addedevent-target-shim@6.0.2(transitive)
- Removedreact@^18.2.0
- Removedjs-tokens@4.0.0(transitive)
- Removedloose-envify@1.4.0(transitive)
- Removedreact@18.3.1(transitive)