first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* WebSocket message protocol constants.
|
||||
*
|
||||
* Following the y-websocket protocol, messages are prefixed with a type byte
|
||||
* to multiplex different message types over the same connection.
|
||||
*
|
||||
* Server protocol format: [messageType: u8, ...payload]
|
||||
* Worker protocol format: [messageType: u8, docIdLen: u16, docId: utf8, ...payload]
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// Message Types
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Sync message type - contains CRDT document updates.
|
||||
* Payload is the raw CRDT update bytes.
|
||||
*/
|
||||
export const MESSAGE_SYNC = 0;
|
||||
|
||||
/**
|
||||
* Awareness message type - contains ephemeral presence/cursor data.
|
||||
* Payload is awareness update bytes.
|
||||
*/
|
||||
export const MESSAGE_AWARENESS = 1;
|
||||
|
||||
/**
|
||||
* Subscribe message type - request to subscribe to a document.
|
||||
* Payload is serverUrl as UTF-8 string (empty for local-only docs).
|
||||
*/
|
||||
export const MESSAGE_SUBSCRIBE = 2;
|
||||
|
||||
/**
|
||||
* Unsubscribe message type - request to unsubscribe from a document.
|
||||
* Payload is empty.
|
||||
*/
|
||||
export const MESSAGE_UNSUBSCRIBE = 3;
|
||||
|
||||
/**
|
||||
* Connected message type - notification that server connection is established.
|
||||
* Sent from worker to frontend. Payload is empty.
|
||||
*/
|
||||
export const MESSAGE_CONNECTED = 4;
|
||||
|
||||
/**
|
||||
* Disconnected message type - notification that server connection was lost.
|
||||
* Sent from worker to frontend. Payload is empty.
|
||||
*/
|
||||
export const MESSAGE_DISCONNECTED = 5;
|
||||
|
||||
/**
|
||||
* Initial sync message type - notification that initial sync is complete.
|
||||
* Sent from worker to frontend after first server data is received. Payload is empty.
|
||||
*/
|
||||
export const MESSAGE_INITIAL_SYNC = 6;
|
||||
|
||||
/**
|
||||
* Encode a message with type prefix.
|
||||
* @param messageType - The message type (MESSAGE_SYNC or MESSAGE_AWARENESS)
|
||||
* @param payload - The message payload bytes
|
||||
* @returns Encoded message with type prefix
|
||||
*/
|
||||
export function encodeMessage(messageType: number, payload: Uint8Array): Uint8Array {
|
||||
const result = new Uint8Array(1 + payload.length);
|
||||
result[0] = messageType;
|
||||
result.set(payload, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a message to extract type and payload.
|
||||
* @param data - The encoded message bytes
|
||||
* @returns Object with messageType and payload
|
||||
*/
|
||||
export function decodeMessage(data: Uint8Array): { messageType: number; payload: Uint8Array } {
|
||||
if (data.length === 0) {
|
||||
throw new Error('Empty message');
|
||||
}
|
||||
return {
|
||||
messageType: data[0],
|
||||
payload: data.subarray(1),
|
||||
};
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Worker Protocol (with docId for multiplexing)
|
||||
// =============================================================================
|
||||
|
||||
const textEncoder = new TextEncoder();
|
||||
const textDecoder = new TextDecoder();
|
||||
|
||||
/**
|
||||
* Encode a message with docId prefix for worker routing.
|
||||
* Format: [messageType: u8, docIdLen: u16, docId: utf8, ...payload]
|
||||
*
|
||||
* @param messageType - The message type
|
||||
* @param docId - Document ID for routing
|
||||
* @param payload - Optional payload bytes (default: empty)
|
||||
* @returns Encoded message with docId prefix
|
||||
*/
|
||||
export function encodeWithDocId(
|
||||
messageType: number,
|
||||
docId: string,
|
||||
payload: Uint8Array = new Uint8Array(0),
|
||||
): Uint8Array {
|
||||
const docIdBytes = textEncoder.encode(docId);
|
||||
const docIdLen = docIdBytes.length;
|
||||
|
||||
// Format: [type: 1][docIdLen: 2][docId: N][payload: M]
|
||||
const result = new Uint8Array(1 + 2 + docIdLen + payload.length);
|
||||
|
||||
result[0] = messageType;
|
||||
// Write docIdLen as big-endian u16
|
||||
result[1] = (docIdLen >> 8) & 0xff;
|
||||
result[2] = docIdLen & 0xff;
|
||||
result.set(docIdBytes, 3);
|
||||
result.set(payload, 3 + docIdLen);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a message with docId prefix.
|
||||
* @param data - The encoded message bytes
|
||||
* @returns Object with messageType, docId, and payload
|
||||
*/
|
||||
export function decodeWithDocId(data: Uint8Array): {
|
||||
messageType: number;
|
||||
docId: string;
|
||||
payload: Uint8Array;
|
||||
} {
|
||||
if (data.length < 3) {
|
||||
throw new Error('Message too short to contain docId');
|
||||
}
|
||||
|
||||
const messageType = data[0];
|
||||
// Read docIdLen as big-endian u16
|
||||
const docIdLen = (data[1] << 8) | data[2];
|
||||
|
||||
if (data.length < 3 + docIdLen) {
|
||||
throw new Error('Message too short for declared docId length');
|
||||
}
|
||||
|
||||
const docId = textDecoder.decode(data.subarray(3, 3 + docIdLen));
|
||||
const payload = data.subarray(3 + docIdLen);
|
||||
|
||||
return { messageType, docId, payload };
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip docId from a worker message, returning server-format message.
|
||||
* Converts [type, docIdLen, docId, payload] → [type, payload]
|
||||
*
|
||||
* @param data - Worker-format message with docId
|
||||
* @returns Server-format message without docId
|
||||
*/
|
||||
export function stripDocId(data: Uint8Array): Uint8Array {
|
||||
const { messageType, payload } = decodeWithDocId(data);
|
||||
return encodeMessage(messageType, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add docId to a server message, returning worker-format message.
|
||||
* Converts [type, payload] → [type, docIdLen, docId, payload]
|
||||
*
|
||||
* @param docId - Document ID to add
|
||||
* @param data - Server-format message
|
||||
* @returns Worker-format message with docId
|
||||
*/
|
||||
export function addDocId(docId: string, data: Uint8Array): Uint8Array {
|
||||
const { messageType, payload } = decodeMessage(data);
|
||||
return encodeWithDocId(messageType, docId, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a string as UTF-8 bytes.
|
||||
* Useful for encoding serverUrl in SUBSCRIBE messages.
|
||||
*/
|
||||
export function encodeString(str: string): Uint8Array {
|
||||
return textEncoder.encode(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode UTF-8 bytes as a string.
|
||||
* Useful for decoding serverUrl from SUBSCRIBE messages.
|
||||
*/
|
||||
export function decodeString(data: Uint8Array): string {
|
||||
return textDecoder.decode(data);
|
||||
}
|
||||
Reference in New Issue
Block a user