···66import { Breaker } from '#common/breaker.js'
77import { ProtocolError } from '#common/errors.js'
8899+import * as protocol_types from './protocol.js'
1010+1111+/**
1212+ * Send some data in JSON format down the wire.
1313+ *
1414+ * @param {WebSocket} ws the socket to send on
1515+ * @param {protocol_types.OkResponse | protocol_types.ErrorResponse} data the data to send
1616+ */
1717+export function sendSocket(ws, data) {
1818+ ws.send(JSON.stringify(data))
1919+}
2020+921/**
1022 * Given a websocket, wait and take a single message off and return it.
1123 *
+85-4
src/server/routes-socket/handler-realm.js
···11-import { streamSocket } from '#common/socket.js'
11+import { normalizeProtocolError, ProtocolError } from '#common/errors.js'
22+import { realmMessageSchema, parseJson } from '#common/protocol.js'
33+import { sendSocket, streamSocket } from '#common/socket.js'
44+import { format } from 'node:util'
55+66+import * as protocol_types from '#common/protocol.js'
27import * as realm_types from '#server/routes-socket/state.js'
3849/**
···1015 * @param {AbortSignal} [signal] an optional signal to abort the blocking loop
1116 */
1217export async function realmHandler(ws, auth, signal) {
1313- ws.send(`welcome ${auth.identid} to ${auth.realmid}`)
1414- for await (const message of streamSocket(ws, { signal })) {
1515- ws.send(`you said: ${message}`)
1818+ respondWithRealmStatus(ws, auth)
1919+ broadcastToRealm(undefined, { ok: true, msg: 'welcome', ident: auth.identid }, auth)
2020+2121+ for await (const data of streamSocket(ws, { signal })) {
2222+ try {
2323+ const msg = await parseJson.pipe(realmMessageSchema).parseAsync(data)
2424+ switch (msg.msg) {
2525+ case 'realm.status':
2626+ respondWithRealmStatus(ws, auth)
2727+ continue
2828+2929+ case 'realm.broadcast':
3030+ broadcastToRealm(msg.recipients, msg.payload, auth)
3131+ continue
3232+3333+ default:
3434+ throw new ProtocolError('unknown message type: ${msg.msg}', 400)
3535+ }
3636+ }
3737+ catch (exc) {
3838+ const error = normalizeProtocolError(exc)
3939+ if (error.status >= 500) throw error
4040+4141+ if (ws.readyState === ws.OPEN) {
4242+ /** @type {protocol_types.ErrorResponse} */
4343+ const resp = {
4444+ ok: false,
4545+ message: format('Error: %s', error.message),
4646+ status: error.status,
4747+ }
4848+4949+ sendSocket(ws, resp)
5050+ }
5151+ }
5252+ }
5353+}
5454+5555+/**
5656+ * @private
5757+ * @param {WebSocket} ws the socket to communicate on
5858+ * @param {realm_types.AuthenticatedConnection} auth the current identity
5959+ */
6060+function respondWithRealmStatus(ws, auth) {
6161+ /** @type {protocol_types.RealmStatusResponse} */
6262+ const resp = {
6363+ ok: true,
6464+ msg: 'realm.status',
6565+ realm: auth.realmid,
6666+ identities: Array.from(auth.realm.identities.keys()),
6767+ }
6868+6969+ sendSocket(ws, resp)
7070+}
7171+7272+/**
7373+ * @private
7474+ * @param {protocol_types.IdentID[] | undefined} recipients array of recips, or undef to send all
7575+ * @param {unknown} payload the payload to send
7676+ * @param {realm_types.AuthenticatedConnection} auth the current identity
7777+ */
7878+function broadcastToRealm(recipients, payload, auth) {
7979+ /** @type {protocol_types.RealmBroadcastResponse} */
8080+ const resp = {
8181+ ok: true,
8282+ msg: 'realm.broadcast',
8383+ sender: auth.identid,
8484+ payload: payload,
8585+ }
8686+8787+ recipients ??= Array.from(auth.realm.identities.keys())
8888+ for (const recip of recipients) {
8989+ if (recip === auth.identid) continue
9090+9191+ const sockets = auth.realm.sockets.get(recip)
9292+ if (sockets == null) continue
9393+9494+ for (const socket of sockets) {
9595+ sendSocket(socket, resp)
9696+ }
1697 }
1798}