Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | 33x 31x 29x 29x 1x 28x 26x 25x 22x 22x 19x 19x 17x 15x 23x 23x | /**
* Pure translation between socket.io v0.9 event frames and EtherCalc's
* native WS message types.
*
* Scope: we only bridge `type:5` (Event) frames — that's the single event
* channel the legacy server ever used (`@on data` in the LiveScript). Other
* packet types (connect/disconnect/heartbeat) are managed elsewhere and are
* not user-data-bearing.
*
* Legacy wire shape for events:
* Frame: `5::<endpoint>:{"name":"data","args":[{…payload}]}`
* Meaning: emit event named "data" with one argument = the EtherCalc
* message object.
*
* Direction:
* client → server: unwrap `args[0]` and validate it matches the
* ClientMessage discriminated union.
* server → client: wrap the ServerMessage into an `args[0]` slot.
*/
import {
CLIENT_MESSAGE_TYPES,
type ClientMessage,
type ServerMessage,
} from '@ethercalc/shared/messages';
import { encodeFrame, PacketType, type Packet } from './framing.ts';
interface SocketIoEventPayload {
name?: unknown;
args?: unknown;
}
/**
* Translate an inbound socket.io event packet to a native ClientMessage.
*
* Returns `null` (never throws) when:
* - packet is not of type Event (5)
* - data is missing or malformed JSON
* - `name` is not "data"
* - `args` is missing, empty, or the first arg isn't a ClientMessage
*
* We deliberately don't validate payload shape beyond the `type` field —
* the native WS layer owes deeper validation for *its* input, and the
* shim's job is to be a thin translator. Duplicating type checks here
* would drift from the canonical parser in shared/messages.ts.
*/
export function socketIoEventToNative(packet: Packet): ClientMessage | null {
if (packet.type !== PacketType.Event) return null;
if (packet.data === undefined || packet.data === '') return null;
let parsed: SocketIoEventPayload;
try {
parsed = JSON.parse(packet.data) as SocketIoEventPayload;
} catch {
return null;
}
if (!parsed || typeof parsed !== 'object') return null;
if (parsed.name !== 'data') return null;
if (!Array.isArray(parsed.args) || parsed.args.length === 0) return null;
const first = parsed.args[0];
if (!first || typeof first !== 'object') return null;
const type = (first as { type?: unknown }).type;
if (typeof type !== 'string') return null;
if (!(CLIENT_MESSAGE_TYPES as readonly string[]).includes(type)) return null;
return first as ClientMessage;
}
/**
* Wrap an outbound ServerMessage as a socket.io event frame string ready
* to hand to a WebSocket client's `send()` call.
*
* Produces `5::/:{"name":"data","args":[{…msg}]}` — the event name is
* fixed to `"data"` to match the legacy server's single broadcast channel.
* Endpoint is empty (root namespace) which the v0.9 client expects.
*/
export function nativeToSocketIoEvent(msg: ServerMessage): string {
const payload: SocketIoEventPayload = {
name: 'data',
args: [msg],
};
return encodeFrame({
type: PacketType.Event,
endpoint: '',
data: JSON.stringify(payload),
});
}
|