### Install Dependencies and Start Server Source: https://github.com/cloudflare/partykit/blob/main/fixtures/alarm-restart-e2e/README.md Installs project dependencies and starts the development server. This is the initial setup step before running the experiment. ```bash npm install npm run start ``` -------------------------------- ### Install hono-party Source: https://github.com/cloudflare/partykit/blob/main/packages/hono-party/README.md Install the necessary packages for hono-party, hono, and partyserver. ```bash npm install hono-party hono partyserver ``` -------------------------------- ### Install PartyServer Source: https://github.com/cloudflare/partykit/blob/main/packages/partyserver/README.md Install the PartyServer package using npm. ```shell npm install partyserver ``` -------------------------------- ### Install PartySocket Source: https://github.com/cloudflare/partykit/blob/main/packages/partysocket/README.md Install the package via npm. ```bash npm install partysocket ``` -------------------------------- ### Install PartyTracks Source: https://github.com/cloudflare/partykit/blob/main/packages/partytracks/README.md Install the partytracks library using npm. This is the first step to integrating its WebRTC handling capabilities into your project. ```shell npm install partytracks ``` -------------------------------- ### Install Partysub Packages Source: https://github.com/cloudflare/partykit/blob/main/packages/partysub/README.md Install the necessary packages for partyserver, partysub, and partysocket using npm. ```shell npm install partyserver partysub partysocket ``` -------------------------------- ### Initialize PartyTracks and Get Microphone Source: https://github.com/cloudflare/partykit/blob/main/packages/partytracks/README.md Initializes PartyTracks and retrieves the microphone stream. Includes setup for UI elements and event listeners for broadcasting and device selection. Ensure 'webrtc-adapter' is imported for cross-browser compatibility. ```javascript // needed to smooth out cross browser behavior inconsistencies import "webrtc-adapter"; import { PartyTracks, getMic, getCamera, getScreenshare, createAudioSink } from "partytracks/client"; import invariant from "tiny-invariant"; const partyTracks = new PartyTracks(); // MIC SETUP // ===================================================================== const audio = document.getElementById("audio"); const micBroadcastButton = document.getElementById("mic-broadcast-button"); const micEnabledButton = document.getElementById("mic-enabled-button"); const micSelect = document.getElementById("mic-select"); invariant(audio instanceof HTMLAudioElement); invariant(micBroadcastButton instanceof HTMLButtonElement); invariant(micEnabledButton instanceof HTMLButtonElement); invariant(micSelect instanceof HTMLSelectElement); const mic = getMic(); mic.error$.subscribe(console.error); mic.permissionState$.subscribe((ps) => { console.log("Mic permissionState: ", ps); }); micBroadcastButton.addEventListener("click", () => { mic.toggleBroadcasting(); }); mic.isBroadcasting$.subscribe((isBroadcasting) => { micBroadcastButton.innerText = isBroadcasting ? "mic is broadcasting" : "mic is not broadcasting"; }); micEnabledButton.addEventListener("click", () => { mic.toggleIsSourceEnabled(); }); mic.isSourceEnabled$.subscribe((isSourceEnabled) => { micEnabledButton.innerText = isSourceEnabled ? "mic is enabled" : "mic is disabled"; }); mic.devices$.subscribe((mics) => { micSelect.innerHTML = ""; mics.forEach((mic) => { const option = document.createElement("option"); option.value = mic.deviceId; option.innerText = mic.label; option.dataset.mediaDeviceInfo = JSON.stringify(mic); micSelect.appendChild(option); }); }); mic.activeDevice$.subscribe((d) => { micSelect.value = d?.deviceId ?? "default"; }); micSelect.onchange = (e) => { invariant(e.target instanceof HTMLSelectElement); const option = e.target.querySelector(`option[value="${e.target.value}"]`); invariant(option instanceof HTMLOptionElement); invariant(option.dataset.mediaDeviceInfo); mic.setPreferredDevice(JSON.parse(option.dataset.mediaDeviceInfo)); }; // Use localMonitorTrack$ to set up "talking while muted" notifications: // mic.localMonitorTrack$.subscribe((track) => { // /* ... */ // }); const audioTrackMetadata$ = partyTracks.push(mic.broadcastTrack$); // Send track metadata to other users. Something like: // audioTrackMetadata$.subscribe((metadata) => { // websocket.send( // JSON.stringify({ // type: "UserMicTrackUpdate", // userId: "user a", // metadata // }) // ); // }); ``` -------------------------------- ### Setup YServer Source: https://github.com/cloudflare/partykit/blob/main/packages/y-partyserver/README.md Export the YServer class to initialize a Yjs backend within a PartyServer environment. ```ts export { YServer as MyYServer } from "y-partyserver"; // then setup wrangler.jsonc and a default fetch handler // like you would for PartyServer. ``` -------------------------------- ### Basic Party Server Setup in Hono Source: https://github.com/cloudflare/partykit/blob/main/packages/hono-party/README.md Set up multiple PartyKit servers within a Hono application using the partyserverMiddleware. Ensure your Party classes extend the Server class. ```typescript import { Hono } from "hono"; import { partyserverMiddleware } from "hono-party"; import { Server } from "partyserver"; // Multiple party servers export class Chat extends Server {} export class Game extends Server {} export class Document extends Server {} // Basic setup const app = new Hono(); app.use("*", partyserverMiddleware()); export default app; ``` -------------------------------- ### Party Server with Authentication in Hono Source: https://github.com/cloudflare/partykit/blob/main/packages/hono-party/README.md Configure the partyserverMiddleware with authentication by accessing environment bindings for JWT secrets. This example shows how to validate a token before connecting. ```typescript type Env = { Bindings: { JWT_SECRET: string } }; app.use( "*", partyserverMiddleware({ options: { onBeforeConnect: async (req, lobby, c) => { const token = req.headers.get("authorization"); const secret = c.env.JWT_SECRET; // validate token against secret if (!token) return new Response("Unauthorized", { status: 401 }); } } }) ); ``` -------------------------------- ### Error Handling Examples Source: https://github.com/cloudflare/partykit/blob/main/AGENTS.md Illustrates different methods for handling errors and logging in TypeScript. ```typescript // Use console.error for critical errors with helpful context console.error(`‼️ No WebSocket implementation available...`); // Use console.warn for warnings console.warn(`PartySocket: party name "${name}" contains forward slash...`); // For debug logging, use the debug flag pattern private _debug(...args: unknown[]) { if (this._options.debug) { this._debugLogger("RWS>", ...args); } } // Throw errors with descriptive messages throw new Error("path must not start with a slash"); // Use assert for internal invariants function assert(condition: unknown, msg?: string): asserts condition { if (!condition) { throw new Error(msg); } } ``` -------------------------------- ### Initialize Scheduler in Durable Object Source: https://github.com/cloudflare/partykit/blob/main/packages/partywhen/README.md Setup the Scheduler class and configure the Durable Object binding in wrangler.jsonc. ```ts import { Scheduler } from "partywhen"; export { Scheduler }; // also setup wrangler.jsonc to create a durable object binding // let's say you've done it this way: /** { // ... "durable_objects": { "bindings": [ { "name": "SCHEDULER", "class_name": "Scheduler" } ] }, "migrations": [ { "tag": "v1", "new_sqlite_classes": ["Scheduler"] } ] } */ export default { fetch(request: Request, env: Env, ctx: ExecutionContext) { // access a scheduler instance const id = env.SCHEDULER.idFromName("my-scheduler"); const scheduler = env.SCHEDULER.get(id); // now you can use the scheduler } }; ``` -------------------------------- ### TURN Server Configuration Source: https://github.com/cloudflare/partykit/blob/main/packages/partytracks/README.md Configure a TURN server for enhanced NAT traversal capabilities in your PartyKit setup. This is useful when direct peer-to-peer connections are not possible due to restrictive NAT or firewall configurations. ```APIDOC ## Configure TURN Server ### Description Configure a TURN server for enhanced NAT traversal capabilities. This helps establish connections when direct peer-to-peer connections aren't possible due to restrictive NAT or firewall configurations. ### Method Not Applicable (Configuration via function call) ### Endpoint Not Applicable (Configuration via function call) ### Parameters #### Request Body (Implicit via `routePartyTracksRequest`) - **appId** (string) - Required - Your Cloudflare Calls App ID. - **token** (string) - Required - Your Cloudflare Calls App Token. - **request** (Request) - Required - The incoming request object. - **turnServerAppId** (string) - Optional - Your Cloudflare TURN Server App ID. - **turnServerAppToken** (string) - Optional - Your Cloudflare TURN Server App Token. - **turnServerCredentialTTL** (number) - Optional - Credential lifetime in seconds (default: 86400). ### Request Example ```ts routePartyTracksRequest({ appId: c.env.CALLS_APP_ID, token: c.env.CALLS_APP_TOKEN, request: c.req.raw, // TURN server configuration turnServerAppId: c.env.TURN_SERVER_APP_ID, turnServerAppToken: c.env.TURN_SERVER_APP_TOKEN, turnServerCredentialTTL: 86400 // Optional: credential lifetime in seconds (default: 24 hours) }); ``` ### Response #### Success Response (Implicit via `/partytracks/generate-ice-servers` endpoint) When TURN server credentials are provided, the `/partytracks/generate-ice-servers` endpoint will return TURN server configurations for improved connectivity. Without TURN credentials, only STUN servers are provided for basic NAT traversal. #### Response Example (Response structure depends on the `/partytracks/generate-ice-servers` endpoint, which is not detailed here. It will include TURN server configurations if provided.) ``` -------------------------------- ### Party Server with Custom Routing in Hono Source: https://github.com/cloudflare/partykit/blob/main/packages/hono-party/README.md Configure the partyserverMiddleware to handle specific routes by setting a prefix. This ensures the middleware only applies to paths starting with the specified prefix. ```typescript app.use( "*", partyserverMiddleware({ options: { prefix: "/party" // Handles /party/* routes only } }) ); ``` -------------------------------- ### Define a Facet Child Server Source: https://github.com/cloudflare/partykit/blob/main/packages/partyserver/README.md Example of defining a child server that will be used as a Durable Object Facet. Ensure an explicit `id` is passed during facet creation to correctly set `this.name`. ```typescript import { Server } from "partyserver"; export class FacetChild extends Server { // `this.name` here will report `facetName` (NOT the parent's name) // because we passed an explicit `id` at spawn time below. onStart() { console.log("facet started:", this.name); } } ``` -------------------------------- ### Build All Packages Source: https://github.com/cloudflare/partykit/blob/main/AGENTS.md Run this command from the root of the PartyServer monorepo to build all packages. ```bash npm run build ``` -------------------------------- ### Initialize Camera Stream and UI Controls Source: https://github.com/cloudflare/partykit/blob/main/packages/partytracks/README.md Sets up the camera stream, including UI elements for broadcasting and enabling/disabling the camera. It also handles device selection and updates the UI based on the camera's state. ```javascript // CAMERA SETUP // ===================================================================== const cameraBroadcastButton = document.getElementById( "camera-broadcast-button" ); const cameraEnabledButton = document.getElementById("camera-enabled-button"); const cameraSelect = document.getElementById("camera-select"); const localVideo = document.getElementById("local-video"); const remoteVideo = document.getElementById("remote-video"); invariant(localVideo instanceof HTMLVideoElement); invariant(remoteVideo instanceof HTMLVideoElement); invariant(cameraBroadcastButton instanceof HTMLButtonElement); invariant(cameraEnabledButton instanceof HTMLButtonElement); invariant(cameraSelect instanceof HTMLSelectElement); const camera = getCamera(); cameraBroadcastButton.addEventListener("click", () => { camera.toggleBroadcasting(); }); camera.isBroadcasting$.subscribe((isBroadcasting) => { cameraBroadcastButton.innerText = isBroadcasting ? "camera is broadcasting" : "camera is not broadcasting"; }); cameraEnabledButton.addEventListener("click", () => { camera.toggleIsSourceEnabled(); }); camera.isSourceEnabled$.subscribe((enabled) => { cameraEnabledButton.innerText = enabled ? "camera is enabled" : "camera is disabled"; }); camera.devices$.subscribe((cameras) => { cameraSelect.innerHTML = ""; cameras.forEach((c) => { const option = document.createElement("option"); option.value = c.deviceId; option.innerText = c.label; option.dataset.mediaDeviceInfo = JSON.stringify(c); cameraSelect.appendChild(option); }); }); camera.activeDevice$.subscribe((d) => { cameraSelect.value = d?.deviceId ?? "default"; }); cameraSelect.onchange = (e) => { invariant(e.target instanceof HTMLSelectElement); const option = e.target.querySelector(`option[value="${e.target.value}"]`); invariant(option instanceof HTMLOptionElement); invariant(option.dataset.mediaDeviceInfo); camera.setPreferredDevice(JSON.parse(option.dataset.mediaDeviceInfo)); }; const videoTrackMetadata$ = partyTracks.push(camera.broadcastTrack$); // Screenshare Setup // ===================================================================== const localScreenshareVideo = document.getElementById( "local-screenshare-video" ); ``` -------------------------------- ### Initialize PartyTracks Instances Source: https://github.com/cloudflare/partykit/blob/main/packages/partytracks/README.md Various ways to instantiate PartyTracks, ranging from default settings to custom cross-domain configurations and authentication headers. ```js // Default configuration (same domain) const partyTracks = new PartyTracks(); // Custom path prefix for same-domain deployment const partyTracks = new PartyTracks({ prefix: "/api/partytracks" }); // Cross-domain configuration const partyTracks = new PartyTracks({ prefix: "https://api.example.com/partytracks" }); // Custom ICE servers and additional parameters const partyTracks = new PartyTracks({ iceServers: [{ urls: "stun:stun.l.google.com:19302" }], apiExtraParams: "userId=123&roomId=456", maxApiHistory: 50 }); // With custom headers (e.g., for authentication) const partyTracks = new PartyTracks({ prefix: "https://api.example.com/partytracks", headers: new Headers({ Authorization: "Bearer your-token-here", "X-Custom-Header": "value" }) }); ``` -------------------------------- ### Run All Tests in a Package Source: https://github.com/cloudflare/partykit/blob/main/AGENTS.md Change directory to the desired package and run all its tests. ```bash cd packages/partysocket && npm run check:test ``` -------------------------------- ### Create a new PartyKit Server by name Source: https://github.com/cloudflare/partykit/blob/main/packages/partyserver/README.md Use `getServerByName` to instantiate a server with a specific name. You can optionally provide `locationHint` or `jurisdiction` for data placement, and disable retries with `routingRetry: false`. ```typescript getServerByName(namespace, name, {locationHint, jurisdiction, routingRetry}) ``` -------------------------------- ### Define MediaDeviceOptions interface Source: https://github.com/cloudflare/partykit/blob/main/packages/partytracks/README.md Configuration options for initializing microphone or camera devices. ```ts interface MediaDeviceOptions { /** Whether or not the track broadcast by default. Default: false */ broadcasting?: boolean; /** Keeps the track source active regardless of whether there are any subscribers to either localMonitorTrack$ or broadcastTrack$. Defaults to true for mic and false for camera. */ retainIdleTrack?: boolean; /** Initial transformations for the track. */ transformations?: (( track: MediaStreamTrack ) => Observable)[]; /** Whether or not isSourceEnabled should be true initially. Defaults to true. */ activateSource?: boolean; /** Constraints for the device. This is passed into navigator.mediaDevices.getUserMedia(). deviceId and groupId are excluded because _all_ devices will be tried eventually if the preferred device is not available. */ constraints?: Omit; /** A callback to be notified when an individual device fails to produce a healthy track. Useful for potentially surfacing messages to the user, or for optionally deprioritizing the device in the future. */ onDeviceFailure?: (device: MediaDeviceInfo) => void; } ``` -------------------------------- ### Import Conventions Source: https://github.com/cloudflare/partykit/blob/main/AGENTS.md Demonstrates the recommended order and style for imports in TypeScript projects. ```typescript // 1. Import from relative modules first import ReconnectingWebSocket from "./ws"; // 2. Then type imports (prefer `import type`) import type * as RWS from "./ws"; import type { Connection } from "./types"; // 3. Use type-only imports when importing only types // This helps with tree-shaking and clarity ``` -------------------------------- ### Define PartySub Server in Worker Source: https://github.com/cloudflare/partykit/blob/main/packages/partysub/README.md Define a PartySub class in your Cloudflare Worker using createPubSubServer. Configure binding, node count, and optional location hints. This setup routes incoming pub-sub requests. ```typescript import { createPubSubServer } from "partysub/server"; const { PubSubServer, routePubSubRequest } = createPubSubServer({ binding: "PubSub", // the name of the binding nodes: /* number of nodes _PER :id_, default 1 */ 100, locations: { // optionally define locations, and weight them // the weight determines how many nodes are spun up in that location // possible values at https://developers.cloudflare.com/durable-objects/reference/data-location/#provide-a-location-hint // example: eu: 1, wnam: 3 // If a user connects from one of these areas, they will // be connected to a node in that location // If a user connects from an area not listed here, // they will be connected to a random node // Note: location hints are best attempt, not guaranteed // Note: In the future, we will autoscale servers // so this configuration isn't needed anymore }, // The below config doesn't work _yet_, but it's the goal jurisdiction: "eu" /* optional, default undefined */ // Note: You CANNOT define a jurisdiction and locations at the same time }); export { PubSubServer }; // setup your worker handler export default { async fetch(request, env) { const pubSubResponse = await routePubSubRequest(request, env); return pubSubResponse || new Response("Not found", { status: 404 }); } }; ``` -------------------------------- ### PartyKit WebSocket Constructor Source: https://github.com/cloudflare/partykit/blob/main/packages/partysocket/README.md Initializes a new PartyKit WebSocket connection. ```APIDOC ## Constructor ### Description Initializes a new PartyKit WebSocket connection. ### Parameters #### Path Parameters - **url** (UrlProvider) - Required - The URL for the WebSocket server. - **protocols** (ProtocolsProvider) - Optional - A string or array of strings representing sub-protocols. - **options** (Options) - Optional - Configuration options for the WebSocket connection. ``` -------------------------------- ### Run All Checks Source: https://github.com/cloudflare/partykit/blob/main/AGENTS.md Execute this command from the root to perform comprehensive checks including formatting, linting, type-checking, and tests. ```bash npm run check ``` -------------------------------- ### Push and Pull Media Tracks Source: https://github.com/cloudflare/partykit/blob/main/packages/partytracks/README.md Demonstrates how to push local tracks to the network and pull remote tracks for rendering in video elements or audio sinks. ```typescript const screenshareVideoTrackMetadata$ = partyTracks.push( screenshare.video.broadcastTrack$ ); const screenshareAudioTrackMetadata$ = partyTracks.push( screenshare.audio.broadcastTrack$ ); // Pulling tracks // ===================================================================== // On another machine... // // The easiest way to create an observable is by wrapping in of() // import { of } from "rxjs" // const audioTrackMetadata$ = of({ // "trackName": "...", // "sessionId": "...", // "location": "remote" // }) audioTrackMetadata$.subscribe(console.log); const pulledAudioTrack$ = partyTracks.pull(audioTrackMetadata$); const pulledVideoTrack$ = partyTracks.pull(audioTrackMetadata$); const pulledScreenshareVideoTrack$ = partyTracks.pull( screenshareVideoTrackMetadata$ ); const pulledScreenshareAudioTrack$ = partyTracks.pull( screenshareAudioTrackMetadata$ ); camera.broadcastTrack$.subscribe((track) => { const localMediaStream = new MediaStream(); localMediaStream.addTrack(track); localVideo.srcObject = localMediaStream; }); pulledVideoTrack$.subscribe((track) => { const remoteMediaStream = new MediaStream(); remoteMediaStream.addTrack(track); remoteVideo.srcObject = remoteMediaStream; }); pulledScreenshareVideoTrack$.subscribe((track) => { const remoteScreenshareVideoStream = new MediaStream(); remoteScreenshareVideoStream.addTrack(track); remoteScreenshareVideo.srcObject = remoteScreenshareVideoStream; }); const audioSink = createAudioSink({ audioElement: audio }); const pulledTrackSinkSubscription = audioSink.attach(pulledAudioTrack$); const pulledScreenshareAudioTrackSinkSubscription = audioSink.attach( pulledScreenshareAudioTrack$ ); ``` -------------------------------- ### Instantiate and Fetch from a Facet Child Source: https://github.com/cloudflare/partykit/blob/main/packages/partyserver/README.md Demonstrates how a parent server spawns a facet child using `ctx.facets.get`. It's critical to construct the facet's `id` using `idFromName` and pass it explicitly to ensure the facet has its own independent identity. ```typescript import { Server } from "partyserver"; export class FacetChild extends Server { // `this.name` here will report `facetName` (NOT the parent's name) // because we passed an explicit `id` at spawn time below. onStart() { console.log("facet started:", this.name); } } export class ParentServer extends Server { async fetch(request: Request) { const facetName = "child-foo"; // Recommended: construct the id via `ctx.exports[BoundDOClass]`, // which is also a `DurableObjectNamespace`. Any bound DO class // works — the id is opaque + a name; nothing routes through the // namespace at runtime for facets. const id = this.ctx.exports.ParentServer.idFromName(facetName); const facet = this.ctx.facets.get(facetName, () => ({ class: this.ctx.exports.FacetChild, id // <-- the critical bit })); return facet.fetch(request); } } ``` -------------------------------- ### Define a PartyServer with Connection and Message Handling Source: https://github.com/cloudflare/partykit/blob/main/packages/partyserver/README.md Implement a custom Server class with onConnect and onMessage handlers. Use this to define your real-time application logic. ```typescript // index.ts import { routePartykitRequest, Server } from "partyserver"; // Define your Server export class MyServer extends Server { onConnect(connection) { console.log("Connected", connection.id, "to server", this.name); } onMessage(connection, message) { console.log("Message from", connection.id, ":", message); // Send the message to every other connection this.broadcast(message, [connection.id]); } } export default { // Set up your fetch handler to use configured Servers async fetch(request: Request, env: Env): Promise { return ( (await routePartykitRequest(request, env)) || new Response("Not Found", { status: 404 }) ); } } satisfies ExportedHandler; ``` -------------------------------- ### Protocol Configuration Source: https://github.com/cloudflare/partykit/blob/main/packages/partysocket/README.md Configure WebSocket protocols using static values or dynamic providers. ```javascript import { WebSocket } from "partysocket"; const ws = new WebSocket("wss://your.site.com", "your protocol"); ``` ```javascript import WebSocket from 'partysocket`; const protocols = ['p1', 'p2', ['p3.1', 'p3.2']]; let protocolsIndex = 0; // round robin protocols provider const protocolsProvider = () => protocols[protocolsIndex++ % protocols.length]; const ws = new WebSocket('wss://your.site.com', protocolsProvider); ``` -------------------------------- ### Connect Client Provider Source: https://github.com/cloudflare/partykit/blob/main/packages/y-partyserver/README.md Initialize a YProvider to connect the client to the Yjs server. ```ts import YProvider from "y-partyserver/provider"; import * as Y from "yjs"; const yDoc = new Y.Doc(); const provider = new YProvider("localhost:8787", "my-document-name", yDoc); ``` ```tsx const provider = new YProvider( /* host */ "localhost:8787", /* document/room name */ "my-document-name", /* Yjs document instance */ yDoc, { /* whether to connect to the server immediately */ connect: false, /* the party server path to connect to, defaults to "main" */ party: "my-party", /* the path to the Yjs document on the server * This replaces the default path of /parties/:party/:room. */ prefix: "/my/own/path", /* use your own Yjs awareness instance */ awareness: new awarenessProtocol.Awareness(yDoc), /* query params to add to the websocket connection * This can be an object or a function that returns an object */ params: async () => ({ token: await getAuthToken() }), /* the WebSocket implementation to use * This can be a polyfill or a custom implementation */ WebSocketPolyfill: WebSocket, /* the interval at which to resync the document * This is set to -1 by default to disable resyncing by polling */ resyncInterval: -1, /* Maximum amount of time to wait before trying to reconnect * (we try to reconnect using exponential backoff) */ maxBackoffTimeout: 2500, /* Disable cross-tab BroadcastChannel communication */ disableBc: false } ); ``` -------------------------------- ### Custom Options Configuration Source: https://github.com/cloudflare/partykit/blob/main/packages/partysocket/README.md Define custom connection behavior and provide polyfills via the options object. ```javascript import { WebSocket } from "partysocket"; import WS from "ws"; const options = { WebSocket: WS, // custom WebSocket constructor connectionTimeout: 1000, maxRetries: 10 }; const ws = new WebSocket("wss://my.site.com", [], options); ``` -------------------------------- ### PartyKit WebSocket Constructor Source: https://github.com/cloudflare/partykit/blob/main/packages/partysocket/README.md Initializes a new PartyKit WebSocket connection. Requires a URL provider and optionally accepts protocols and options. ```typescript constructor(url: UrlProvider, protocols?: ProtocolsProvider, options?: Options) ``` -------------------------------- ### PartyKit Integration Source: https://github.com/cloudflare/partykit/blob/main/packages/partysocket/README.md Connect to a PartyKit room with optional ID and server configuration. ```javascript import PartySocket from "partysocket"; // optional: only needed if creating using inside node.js. Run `npm install ws`, and then add: // import WS from "ws"; const ws = new PartySocket({ host: "project.name.partykit.dev", // or localhost:1999 in dev room: "my-room", // add an optional id to identify the client // if not provided, a random id will be generated 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 ``` -------------------------------- ### Connect to PartySub Server with PartySocket Source: https://github.com/cloudflare/partykit/blob/main/packages/partysub/README.md Connect to the PartySub server from your application using PartySocket. Configure the host, party, room, and optionally specify topics to subscribe to. Handles incoming messages and allows publishing. ```typescript import { PartySocket } from "partysocket"; const ws = new PartySocket({ host: "...", // the host of the partyserver, defaults to window.location.host party: "pubsub", // the name of the party, use the binding's lowercase form room: "default", // the name of the room/channel query: { // by default, it subscribes to all topics // but you can set it to specific topics topics: [ "topic-abc", // a specific topic "prefix:*" // a prefixed topic, ] } }); // Listen to incoming messages client.addEventListener("message", (event) => { console.log(event.topic, event.data); }); // publish a message to the server ws.send(JSON.stringify({ topic: "topic-abc", data: "hello world" })); ``` -------------------------------- ### Implement SyncServer on the server Source: https://github.com/cloudflare/partykit/blob/main/packages/partysync/README.md Extend SyncServer to manage database tables and handle incoming actions. ```ts // server.ts import { SyncServer } from "partysync"; import type { TodoAction, TodoRecord } from "./shared"; export class MyServer extends SyncServer< Env, { todos: [TodoRecord, TodoAction] } > { onStart() { // setup a database table for your records this.ctx.storage.sql.exec( `CREATE TABLE IF NOT EXISTS todos ( id TEXT PRIMARY KEY NOT NULL UNIQUE, text TEXT NOT NULL, completed INTEGER NOT NULL, created_at INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, deleted_at INTEGER DEFAULT NULL )` ); } // setup a handler for actions onAction(channel: "todos", action: TodoAction) { switch (action.type) { case "create": { const { id, text, completed } = action.payload; // return any changed records return [ ...this.ctx.storage.sql .exec( "INSERT INTO todos (id, text, completed) VALUES (?, ?, ?) RETURNING *", id, text, completed ) .raw() ] as TodoRecord[]; } // etc } } } ``` -------------------------------- ### Run All Tests Source: https://github.com/cloudflare/partykit/blob/main/AGENTS.md Execute all tests for all packages within the monorepo. ```bash npm run check:test ``` -------------------------------- ### Check Code Formatting Source: https://github.com/cloudflare/partykit/blob/main/AGENTS.md Verify code formatting with Oxfmt without making changes. ```bash npm run check:format ``` -------------------------------- ### Run Single Test File Source: https://github.com/cloudflare/partykit/blob/main/AGENTS.md Execute a specific test file using vitest. ```bash npx vitest src/tests/reconnecting.test.ts ``` -------------------------------- ### Run Linter Source: https://github.com/cloudflare/partykit/blob/main/AGENTS.md Execute the Oxlint linter to check for code quality issues. ```bash npm run check:lint ``` -------------------------------- ### Configure Persistence Source: https://github.com/cloudflare/partykit/blob/main/packages/y-partyserver/README.md Implement onLoad and onSave methods within a YServer subclass to persist document state to external storage. ```ts // server.ts import { YServer } from "y-partyserver"; export class MyDocument extends YServer { /* control how often the onSave handler * is called with these options */ static callbackOptions = { // all of these are optional debounceWait: /* number, default = */ 2000, debounceMaxWait: /* number, default = */ 10000, timeout: /* number, default = */ 5000 }; async onLoad() { // load a document from a database, or some remote resource // and apply it on to the Yjs document instance at `this.document` const content = (await fetchDataFromExternalService( this.name )) as Uint8Array; if (content) { Y.applyUpdate(this.document, content); } return; } async onSave() { // called every few seconds after edits, and when the room empties // you can use this to write to a database or some external storage await sendDataToExternalService( this.name, Y.encodeStateAsUpdate(this.document) satisfies Uint8Array ); } } ``` -------------------------------- ### Configure Wrangler for PartyServer Source: https://github.com/cloudflare/partykit/blob/main/packages/partyserver/README.md Configure your wrangler.jsonc file to define your Durable Object bindings and migrations for PartyServer. ```jsonc { "name": "my-partyserver-app", "main": "index.ts", "durable_objects": { "bindings": [ { "name": "MyServer", "class_name": "MyServer" } ] }, "migrations": [ { "tag": "v1", // Should be unique for each entry "new_sqlite_classes": ["MyServer"] } ] } ``` -------------------------------- ### Connect to PartyServer from a Client Source: https://github.com/cloudflare/partykit/blob/main/packages/partyserver/README.md Use PartySocket to establish a WebSocket connection to your PartyServer instance from a client application. ```typescript import { PartySocket } from "partysocket"; const socket = new PartySocket({ host: "https://my-partyserver-app.threepointone.workers.dev", // optional, defaults to window.location.host, party: "my-server", // the server name. if you use routePartykitRequest, it automatically uses the kebab-cased version of the binding name (MyServer -> my-server) room: "my-room" }); ``` -------------------------------- ### Format Code Source: https://github.com/cloudflare/partykit/blob/main/AGENTS.md Use this command to format all code within the monorepo using Oxfmt. ```bash npm run format ``` -------------------------------- ### PartyTracks Push Method Source: https://github.com/cloudflare/partykit/blob/main/packages/partytracks/README.md The `push` method sends a media track to the Realtime SFU. It supports updating the track and encodings, and automatically re-pushes the track if the peer connection is disrupted. ```typescript push( sourceTrack$: Observable, options?: { sendEncodings$?: Observable; } ): Observable; ``` -------------------------------- ### Additional Server Methods Source: https://github.com/cloudflare/partykit/blob/main/packages/partyserver/README.md These methods provide utilities for managing connections and broadcasting messages within the PartyKit server. ```APIDOC ## Additional Methods - `broadcast(message, exclude = [])`: Send a message to all connections, optionally excluding connection IDs listed in the `exclude` array. - `getConnections(tags = [])`: Get all currently connected WebSocket connections, optionally filtered by tags. Returns an iterable list of `Connection`. - `getConnection(id)`: Get a specific connection by its ID. ``` -------------------------------- ### Configure TURN Server for PartyTracks Source: https://github.com/cloudflare/partykit/blob/main/packages/partytracks/README.md Add optional TURN server configuration to your routePartyTracksRequest call to enable enhanced NAT traversal. Credentials can be obtained from the Cloudflare Dashboard. ```typescript routePartyTracksRequest({ appId: c.env.CALLS_APP_ID, token: c.env.CALLS_APP_TOKEN, request: c.req.raw, // TURN server configuration turnServerAppId: c.env.TURN_SERVER_APP_ID, turnServerAppToken: c.env.TURN_SERVER_APP_TOKEN, turnServerCredentialTTL: 86400 // Optional: credential lifetime in seconds (default: 24 hours) }); ``` -------------------------------- ### getMic and getCamera API Source: https://github.com/cloudflare/partykit/blob/main/packages/partytracks/README.md These functions are used to access microphone and camera devices. They accept `MediaDeviceOptions` to customize behavior and return a `MediaDevice` object with observable streams and control methods. ```APIDOC ## `getMic` and `getCamera` These functions allow access to microphone and camera devices, respectively. They both accept `MediaDeviceOptions` for configuration and return a `MediaDevice` object. ### `MediaDeviceOptions` Interface This interface defines the configuration options for accessing media devices: - **broadcasting** (boolean) - Optional - Whether or not the track broadcast by default. Defaults to `false`. - **retainIdleTrack** (boolean) - Optional - Keeps the track source active regardless of whether there are any subscribers. Defaults to `true` for mic and `false` for camera. - **transformations** (Array) - Optional - Initial transformations for the track. Each function takes a `MediaStreamTrack` and returns an `Observable`. - **activateSource** (boolean) - Optional - Whether or not `isSourceEnabled` should be true initially. Defaults to `true`. - **constraints** (Omit) - Optional - Constraints for the device, passed to `navigator.mediaDevices.getUserMedia()`. - **onDeviceFailure** (Function) - Optional - A callback to be notified when an individual device fails to produce a healthy track. ### `MediaDevice` Interface This interface represents a media device and provides observable streams and control methods: - **permissionState$** (Observable) - The permission state of the device. - **devices$** (Observable) - A list of available devices. - **activeDevice$** (Observable) - The active device, if one has been acquired, otherwise the preferred device, otherwise the default device. - **setPreferredDevice** (Function) - Sets the user's preferred device. This preference is persisted to localStorage. - **addTransform** (Function) - Applies a transformation to the content track. - **removeTransform** (Function) - Removes a previously applied transformation. - **isBroadcasting$** (Observable) - Whether or not the source is currently broadcasting content. - **startBroadcasting** (Function) - Starts sending content from the source. - **stopBroadcasting** (Function) - Stops sending content from the source. - **toggleBroadcasting** (Function) - Toggles sending content from the source. - **localMonitorTrack$** (Observable) - A monitor track that is "always on" for this source. - **broadcastTrack$** (Observable) - The track that is to be pushed with PartyTracks.push(). - **isSourceEnabled$** (Observable) - Whether or not the source is enabled. - **enableSource** (Function) - Sets `isSourceEnabled` to true. - **disableSource** (Function) - Sets `isSourceEnabled` to false. - **toggleIsSourceEnabled** (Function) - Toggles `isSourceEnabled`. - **error$** (Observable) - Emits errors encountered when acquiring source. ``` -------------------------------- ### Server Lifecycle Hooks Source: https://github.com/cloudflare/partykit/blob/main/packages/partyserver/README.md These methods can be optionally async and are called at various points in the server's lifecycle to allow for custom behavior and initialization. ```APIDOC ## Lifecycle Hooks These methods can be optionally `async`: - `onStart()`: Called when the server starts for the first time or after waking up from hibernation. Use this to load data from storage and perform other initialization. - `onConnect(connection, context)`: Called when a new websocket connection is established. Use this to set up any connection-specific state. Receives a reference to the connecting `Connection`, and a `ConnectionContext`. - `onMessage(connection, message)`: Called when a message is received on a connection. - `onClose(connection, code, reason, wasClean)`: Called when a connection is closed by a client. The connection is already closed at this point. - `onError(connection, error)`: Called when an error occurs on a connection. - `onRequest(request): Response`: Called when a request is made to the server. Useful for handling HTTP requests. - `onAlarm()`: Called when an alarm is triggered. Alarms can be set using `this.ctx.storage.setAlarm(Date)`. - `getConnectionTags(connection, context): string[]`: Allows setting additional metadata on connections, which can be used to filter connections with `this.getConnections`. ``` -------------------------------- ### getServerByName Source: https://github.com/cloudflare/partykit/blob/main/packages/partyserver/README.md Creates a new Server with a specific name. Allows specifying location hints, jurisdictions, and disabling routing retries. ```APIDOC ## getServerByName ### Description Create a new Server with a specific name. Optionally pass a `locationHint` to specify the [location](https://developers.cloudflare.com/durable-objects/reference/data-location/#provide-a-location-hint), `jurisdiction` to specify the [jurisdiction](https://developers.cloudflare.com/durable-objects/reference/data-location/#restrict-durable-objects-to-a-jurisdiction), or `routingRetry: false` to disable retries for transient Durable Object infrastructure errors. ### Method `getServerByName(namespace, name, {locationHint, jurisdiction, routingRetry}): Promise` ### Parameters #### Path Parameters - `namespace` (string) - The namespace of the server. - `name` (string) - The name of the server. #### Options - `locationHint` (string) - Optional hint for data location. - `jurisdiction` (string) - Optional jurisdiction restriction. - `routingRetry` (boolean) - Disable retries for transient Durable Object infrastructure errors. Defaults to true. ### Returns - `Promise` - A promise that resolves to a DurableObjectStub for the server. ``` -------------------------------- ### Schedule Alarms into Fresh Rooms Source: https://github.com/cloudflare/partykit/blob/main/fixtures/alarm-restart-e2e/README.md Schedules alarms into three different Durable Object types (`RawAlarm`, `StockAlarm`, `FixedAlarm`) within a fresh room. This action primes the DOs for the experiment. ```bash ROOM="cold-strict-$(date +%s)" # Session A: schedule into a fresh room. This is the only entry into # the DO instances during session A. After this, the alarm record on # disk is what carries the DO across the restart. curl -s "http://localhost:5173/raw/$ROOM?schedule=45" curl -s "http://localhost:5173/parties/stock-alarm/$ROOM?schedule=45" curl -s "http://localhost:5173/parties/fixed-alarm/$ROOM?schedule=45" ``` -------------------------------- ### Check Repository Consistency Source: https://github.com/cloudflare/partykit/blob/main/AGENTS.md Run sherif to ensure monorepo consistency. ```bash npm run check:repo ``` -------------------------------- ### Default Configuration Values Source: https://github.com/cloudflare/partykit/blob/main/packages/partysocket/README.md The default settings applied when no options are provided. ```javascript WebSocket: undefined, maxReconnectionDelay: 10000, minReconnectionDelay: 1000 + Math.random() * 4000, reconnectionDelayGrowFactor: 1.3, minUptime: 5000, connectionTimeout: 4000, maxRetries: Infinity, maxEnqueuedMessages: Infinity, startClosed: false, debug: false, ``` -------------------------------- ### PartyKit WebSocket Methods Source: https://github.com/cloudflare/partykit/blob/main/packages/partysocket/README.md Provides methods for managing the WebSocket connection. ```APIDOC ## Methods ### close #### Description Closes the WebSocket connection. #### Parameters - **code** (number) - Optional - A numeric close code. - **reason** (string) - Optional - A string explaining the reason for closing. ``` ```APIDOC ### reconnect #### Description Attempts to re-establish the WebSocket connection. #### Parameters - **code** (number) - Optional - A numeric close code from the previous connection. - **reason** (string) - Optional - A string explaining the reason for the previous closure. ``` ```APIDOC ### send #### Description Sends data to the server over the WebSocket connection. #### Parameters - **data** (string | ArrayBuffer | Blob | ArrayBufferView) - Required - The data to send. ``` ```APIDOC ### addEventListener #### Description Attaches an event listener to the WebSocket object. #### Parameters - **type** ('open' | 'close' | 'message' | 'error') - Required - The type of event to listen for. - **listener** (EventListener) - Required - The function to call when the event is triggered. ``` ```APIDOC ### removeEventListener #### Description Removes an event listener from the WebSocket object. #### Parameters - **type** ('open' | 'close' | 'message' | 'error') - Required - The type of event the listener was attached to. - **listener** (EventListener) - Required - The event listener function to remove. ``` -------------------------------- ### Options Type Definition Source: https://github.com/cloudflare/partykit/blob/main/packages/partysocket/README.md Configuration schema for PartySocket instances. ```typescript type Options = { WebSocket?: any; // WebSocket constructor, if none provided, defaults to global WebSocket maxReconnectionDelay?: number; // max delay in ms between reconnections minReconnectionDelay?: number; // min delay in ms between reconnections reconnectionDelayGrowFactor?: number; // how fast the reconnection delay grows minUptime?: number; // min time in ms to consider connection as stable connectionTimeout?: number; // retry connect if not connected after this time, in ms maxRetries?: number; // maximum number of retries maxEnqueuedMessages?: number; // maximum number of messages to buffer until reconnection startClosed?: boolean; // start websocket in CLOSED state, call `.reconnect()` to connect debug?: boolean; // enables debug output }; ``` -------------------------------- ### ScreenshareOptions Interface Source: https://github.com/cloudflare/partykit/blob/main/packages/partytracks/README.md Defines the configuration options for initializing a screenshare. Use this to control initial state and track retention. ```typescript interface ScreenshareOptions { /** Whether isSourceEnabled should be initially true. Defaults to false. */ activateSource?: boolean; /** Whether or not tracks should be retained even if there are no active subscribers to the content source. (For example, if isBroadcasting$ is false, and localMonitorTrack$ has no subscribers) */ retainIdleTracks?: boolean; audio?: | boolean | { constraints?: MediaTrackConstraints; options?: { /** Whether or not the track should be broadcasting to start */ broadcasting?: boolean; }; }; video?: | boolean | { constraints?: MediaTrackConstraints; options?: { /** Whether or not the track should be broadcasting to start */ broadcasting?: boolean; }; }; } ``` -------------------------------- ### Observe Alarm Behavior After Restart Source: https://github.com/cloudflare/partykit/blob/main/fixtures/alarm-restart-e2e/README.md After restarting the development server, this command retrieves and displays observations from the Durable Objects. It's crucial to wait past the scheduled alarm time before executing this to observe the behavior correctly. ```bash curl -s "http://localhost:5173/raw/$ROOM?snapshot=1" | jq curl -s -i "http://localhost:5173/parties/stock-alarm/$ROOM?snapshot=1" | head -n 12 curl -s "http://localhost:5173/parties/fixed-alarm/$ROOM?snapshot=1" | jq ``` -------------------------------- ### Run Tests with Pattern Source: https://github.com/cloudflare/partykit/blob/main/AGENTS.md Filter and run tests that match a specific pattern using vitest. ```bash npx vitest reconnecting ``` -------------------------------- ### Create Audio Sink for Pulled Audio Tracks Source: https://github.com/cloudflare/partykit/blob/main/packages/partytracks/README.md Use `createAudioSink` to play pulled audio tracks. It handles edge cases for reliable audio playback. Ensure the `audioElement` exists in the DOM. Unsubscribing from the returned subscription handles cleanup. ```typescript import { createAudioSink, PartyTracks } from "partytracks"; import { of } from "rxjs"; const audioElement = document.querySelector("audio"); const audioSink = createAudioSink({ audioElement }); const partyTracks = new PartyTracks(); const audioTrackMetadata$ = of({ // track metadata... }); const pulledAudioTrack$ = partyTracks.pull(audioTrackMetadata$); // No need to "detatch", unsubscribing from pulledTrackSinkSubscription // will do the appropriate cleanup. const pulledTrackSinkSubscription = audioSink.attach(pulledAudioTrack$); ``` -------------------------------- ### Dynamic URL Resolution Source: https://github.com/cloudflare/partykit/blob/main/packages/partysocket/README.md Provide URLs using functions or promises to resolve them before connecting. ```javascript import { WebSocket } from "partysocket"; const urls = [ "wss://my.site.com", "wss://your.site.com", "wss://their.site.com" ]; let urlIndex = 0; // round robin url provider const urlProvider = () => urls[urlIndex++ % urls.length]; const ws = new WebSocket(urlProvider); ``` ```javascript import { WebSocket } from "partysocket"; // async url provider const urlProvider = async () => { const token = await getSessionToken(); return `wss://my.site.com/${token}`; }; const ws = new WebSocket(urlProvider); ``` -------------------------------- ### Basic React PartySocket Connection Source: https://github.com/cloudflare/partykit/blob/main/packages/hono-party/README.md Establish a basic connection to a PartyKit server from a React application using the usePartySocket hook. Specify the party name and room ID for the connection. ```typescript import { usePartySocket } from "partysocket/react"; // Basic connection const socket = usePartySocket({ party: "chat", room: "general" }); ```