@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
JavaScript
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