UNPKG

rwsdk

Version:

Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime

142 lines (141 loc) 5.67 kB
import { MESSAGE_TYPE } from "./shared"; const TEXT_ENCODER = new TextEncoder(); const TEXT_DECODER = new TextDecoder(); const ID_LENGTH = 36; // Length of a UUID string /** * Packs a message object into a Uint8Array for sending over WebSocket. */ export function packMessage(message) { switch (message.type) { case MESSAGE_TYPE.ACTION_REQUEST: { const msg = message; const jsonPayload = JSON.stringify({ id: msg.id, args: msg.args, requestId: msg.requestId, clientUrl: msg.clientUrl, }); const payloadBytes = TEXT_ENCODER.encode(jsonPayload); const packed = new Uint8Array(1 + payloadBytes.length); packed[0] = msg.type; packed.set(payloadBytes, 1); return packed; } case MESSAGE_TYPE.ACTION_START: case MESSAGE_TYPE.RSC_START: { const msg = message; const idBytes = TEXT_ENCODER.encode(msg.id); if (idBytes.length !== ID_LENGTH) { throw new Error("Invalid message ID length for START message"); } const packed = new Uint8Array(1 + 2 + ID_LENGTH); // 1 for type, 2 for status const view = new DataView(packed.buffer); view.setUint8(0, msg.type); view.setUint16(1, msg.status, false); // Big-endian packed.set(idBytes, 3); return packed; } case MESSAGE_TYPE.ACTION_CHUNK: case MESSAGE_TYPE.RSC_CHUNK: { const msg = message; const idBytes = TEXT_ENCODER.encode(msg.id); if (idBytes.length !== ID_LENGTH) { throw new Error("Invalid message ID length for CHUNK message"); } const packed = new Uint8Array(1 + ID_LENGTH + msg.payload.length); packed[0] = msg.type; packed.set(idBytes, 1); packed.set(msg.payload, 1 + ID_LENGTH); return packed; } case MESSAGE_TYPE.ACTION_END: case MESSAGE_TYPE.RSC_END: { const msg = message; const idBytes = TEXT_ENCODER.encode(msg.id); if (idBytes.length !== ID_LENGTH) { throw new Error("Invalid message ID length for END message"); } const packed = new Uint8Array(1 + ID_LENGTH); packed[0] = msg.type; packed.set(idBytes, 1); return packed; } case MESSAGE_TYPE.ACTION_ERROR: { const msg = message; const idBytes = TEXT_ENCODER.encode(msg.id); if (idBytes.length !== ID_LENGTH) { throw new Error("Invalid message ID length for ERROR message"); } const errorPayload = JSON.stringify({ error: msg.error }); const errorBytes = TEXT_ENCODER.encode(errorPayload); const packed = new Uint8Array(1 + ID_LENGTH + errorBytes.length); packed[0] = msg.type; packed.set(idBytes, 1); packed.set(errorBytes, 1 + ID_LENGTH); return packed; } default: // This should be unreachable if all message types are handled throw new Error(`Unknown message type for packing`); } } /** * Unpacks a Uint8Array from WebSocket into a message object. */ export function unpackMessage(data) { if (data.length === 0) { throw new Error("Cannot unpack empty message"); } const messageType = data[0]; switch (messageType) { case MESSAGE_TYPE.ACTION_REQUEST: { const jsonPayload = TEXT_DECODER.decode(data.slice(1)); const parsed = JSON.parse(jsonPayload); return { type: messageType, ...parsed }; } case MESSAGE_TYPE.ACTION_START: case MESSAGE_TYPE.RSC_START: { if (data.length !== 1 + 2 + ID_LENGTH) { throw new Error("Invalid START message length"); } const view = new DataView(data.buffer); const status = view.getUint16(1, false); const id = TEXT_DECODER.decode(data.slice(3)); return { type: messageType, id, status }; } case MESSAGE_TYPE.ACTION_CHUNK: case MESSAGE_TYPE.RSC_CHUNK: { if (data.length < 1 + ID_LENGTH) { throw new Error("Invalid CHUNK message length"); } const id = TEXT_DECODER.decode(data.slice(1, 1 + ID_LENGTH)); const payload = data.slice(1 + ID_LENGTH); return { type: messageType, id, payload }; } case MESSAGE_TYPE.ACTION_END: case MESSAGE_TYPE.RSC_END: { if (data.length !== 1 + ID_LENGTH) { throw new Error("Invalid END message length"); } const id = TEXT_DECODER.decode(data.slice(1, 1 + ID_LENGTH)); return { type: messageType, id }; } case MESSAGE_TYPE.ACTION_ERROR: { if (data.length < 1 + ID_LENGTH) { throw new Error("Invalid ERROR message length"); } const id = TEXT_DECODER.decode(data.slice(1, 1 + ID_LENGTH)); const errorPayload = TEXT_DECODER.decode(data.slice(1 + ID_LENGTH)); let error = "Unknown error"; try { error = JSON.parse(errorPayload).error; } catch (e) { // ignore if it's not a json } return { type: messageType, id, error }; } default: throw new Error(`Unknown message type for unpacking: ${messageType}`); } }