UNPKG

@firtoz/socka

Version:

Standard Schema–first WebSocket RPC for TypeScript — Bun, Hono, Node ws, Cloudflare Workers, Durable Objects

201 lines (197 loc) • 5.96 kB
import { SockaWebSocketSession, broadcastContractPushToAll } from '../chunk-TTXY7O5P.js'; import { reportSockaError } from '../chunk-IFIGKR3W.js'; import { BaseSession, BaseWebSocketDO } from '@firtoz/websocket-do'; // src/do/SockaDoHost.ts function hasSockaDoHostShape(value) { return "contract" in value && "sessions" in value && "buildSockaSessionConfig" in value && value.sessions instanceof Map && typeof value.buildSockaSessionConfig === "function"; } function isSockaDoHost(value) { return !(value instanceof Map) && hasSockaDoHostShape(value); } // src/do/SockaDoSession.ts function runSockaDoSessionOnAttached(config, session) { const cb = config.onAttached; if (!cb) return; try { const result = cb(session); void Promise.resolve(result).catch((error) => { reportSockaError(config.reportError, { kind: "serverOnAttached", error }); }); } catch (error) { reportSockaError(config.reportError, { kind: "serverOnAttached", error }); } } function wrapHandlersForInnerSockaEngine(contract, userHandlers, outer) { const calls = contract.calls; const out = {}; for (const key of Object.keys(calls)) { const proc = calls[key]; const userFn = userHandlers[key]; if (proc.input) { out[key] = (input, _inner) => userFn(input, outer); } else { out[key] = (_inner) => userFn(outer); } } return out; } function resolveSockaDoSessionInit(sessionsOrHost, configOrAttachCtx) { if (isSockaDoHost(sessionsOrHost)) { const attachCtx = configOrAttachCtx; return { sessions: sessionsOrHost.sessions, config: { contract: sessionsOrHost.contract, ...sessionsOrHost.buildSockaSessionConfig(attachCtx) } }; } return { sessions: sessionsOrHost, config: configOrAttachCtx }; } var SockaDoSession = class extends BaseSession { constructor(websocket, sessionsOrHost, configOrAttachCtx) { const { sessions, config } = resolveSockaDoSessionInit( sessionsOrHost, configOrAttachCtx ); const wireFormat = config.wireFormat ?? "json"; super( websocket, sessions, { createData: config.createData ?? ((_ctx) => ({})), handleMessage: async () => { }, handleBufferMessage: async (message) => { await this.socka.handleBinaryMessage(message); }, handleClose: async (baseSession) => { await config.handleClose( baseSession ); } } ); const sockaConfig = { strictUpgradeRequest: false, contract: config.contract, wireFormat, handlers: wrapHandlersForInnerSockaEngine( config.contract, config.handlers, this ), handleClose: async () => { }, onHandlerError: config.onHandlerError ? (err, rpcName, input, _inner) => { config.onHandlerError?.(err, rpcName, input, this); } : void 0, onValidationError: config.onValidationError, serializeJson: config.serializeJson, deserializeJson: config.deserializeJson }; this.socka = new SockaWebSocketSession( websocket, sessions, sockaConfig ); queueMicrotask(() => { queueMicrotask(() => { runSockaDoSessionOnAttached(config, this); }); }); } async handleRawMessage(rawMessage) { return this.socka.handleRawMessage(rawMessage); } emitWireEvent(event, body) { this.socka.emitWireEvent(event, body); } emitPush(name, body) { return this.socka.emitPush(name, body); } broadcastPush(name, body, excludeSelf = false) { return this.socka.broadcastPush(name, body, excludeSelf); } /** * {@link SockaWebSocketSession.listPeers} for this Durable Object room. */ listPeers(options) { const out = []; for (const [ws, s] of this.sessions) { if (options?.excludeSelf && ws === this.websocket) continue; out.push(s.data); } return out; } /** * Like {@link listPeers} but maps each peer {@link SockaDoSession}. */ listPeersWith(map, options) { const out = []; for (const [ws, s] of this.sessions) { if (options?.excludeSelf && ws === this.websocket) continue; out.push(map(s)); } return out; } peerCount(options) { let n = 0; for (const [ws] of this.sessions) { if (options?.excludeSelf && ws === this.websocket) continue; n += 1; } return n; } hasPeers(options) { return this.peerCount(options) > 0; } }; var SockaWebSocketDOBase = class extends BaseWebSocketDO { constructor(ctx, env, options) { super(ctx, env, { createSession: (sessionCtx, websocket) => this.createSockaSession(sessionCtx, websocket), pairServerWebSocketAcceptOptions: options?.pairServerWebSocketAcceptOptions }); } /** * Factory for each connected WebSocket. Default: {@link SockaDoSession} wired * to this host. Override when you need a {@link SockaDoSession} subclass. */ createSockaSession(ctx, websocket) { const host = { contract: this.contract, sessions: this.sessions, buildSockaSessionConfig: (attachCtx) => this.buildSockaSessionConfig(attachCtx) }; return new SockaDoSession( websocket, host, ctx ); } /** * Broadcast a contract-typed push to **every** connected session in this DO. * * Use from HTTP handlers, alarms, or other code paths with no originating * WebSocket session. No-op when the room is empty. */ broadcastPushToAll(name, body) { return broadcastContractPushToAll(this.sessions, this.contract, name, body); } }; var SockaWebSocketDO = class extends SockaWebSocketDOBase { }; // src/do/dispatch.ts function toErrorReply(id, error) { return { type: "error", id, error }; } export { SockaDoSession, SockaWebSocketDO, SockaWebSocketDOBase, toErrorReply }; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map