diff --git a/AGENTS.md b/AGENTS.md index 0080d3f..91f574a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,7 +38,7 @@ pnpm clean - **packages/loro-protocol**: Protocol types and binary encoders/decoders, bytes utilities, and `%ELO` container/crypto helpers (TypeScript). Key files: `src/{protocol,encoding,bytes,e2ee}.ts` with tests under `src/`. - **packages/loro-websocket**: WebSocket client and a `SimpleServer` (TypeScript). - Features: message fragmentation/reassembly (≤256 KiB), connection‑scoped keepalive frames (`"ping"/"pong"` text), permission hooks, optional persistence hooks. -- **packages/loro-adaptors**: Adaptors that connect the WebSocket client to `loro-crdt` (`LoroAdaptor`, `LoroEphemeralAdaptor`) and `%ELO` (`EloLoroAdaptor`). +- **packages/loro-adaptors**: Adaptors that connect the WebSocket client to `loro-crdt` (`LoroAdaptor`, `LoroEphemeralAdaptor`) and `%ELO` (`EloAdaptor`). - **examples/excalidraw-example**: React demo using `SimpleServer`; syncs a Loro doc and ephemeral presence. - **rust/**: Rust workspace mirroring the TS packages: - `rust/loro-protocol`: Encoder/decoder parity with JS (snapshot tests included). diff --git a/README.md b/README.md index 59391d5..6ccb865 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Tip: For a working reference, see `packages/loro-websocket/src/e2e.test.ts` whic `%ELO` adds end‑to‑end encryption to Loro sync. The server never decrypts; it indexes plaintext headers only to support backfill and routing. Clients encrypt/decrypt using AES‑GCM with a 12‑byte IV and the exact encoded header bytes as AAD. -- TypeScript: use `EloLoroAdaptor` from `loro-adaptors` + `LoroWebsocketClient`. +- TypeScript: use `EloAdaptor` from `loro-adaptors` + `LoroWebsocketClient`. - Provide a `getPrivateKey()` hook that resolves `{ keyId, key }` (Web Crypto CryptoKey or Uint8Array). - The adaptor packages updates into `%ELO` containers and decrypts incoming ones, applying to its internal `LoroDoc`. @@ -77,7 +77,7 @@ Example (Node 18+): ```ts import { LoroWebsocketClient } from "loro-websocket/client"; -import { EloLoroAdaptor } from "loro-adaptors/loro"; +import { EloAdaptor } from "loro-adaptors/loro"; import { WebSocket } from "ws"; (globalThis as any).WebSocket = WebSocket as unknown as typeof globalThis.WebSocket; @@ -89,7 +89,7 @@ const key = new Uint8Array([ const client = new LoroWebsocketClient({ url: "ws://localhost:8787" }); await client.waitConnected(); -const adaptor = new EloLoroAdaptor({ +const adaptor = new EloAdaptor({ getPrivateKey: async () => ({ keyId: "k1", key }), }); const room = await client.join({ roomId: "elo-room", crdtAdaptor: adaptor }); diff --git a/llms.md b/llms.md index c5bf0c8..75c8c50 100644 --- a/llms.md +++ b/llms.md @@ -183,7 +183,7 @@ Adaptors bridge the client to actual CRDT state: - `LoroAdaptor` – Loro document (`const adaptor = new LoroAdaptor()`). - `LoroEphemeralAdaptor` – transient presence (`%EPH`). -- `EloLoroAdaptor` – `%ELO` encrypted Loro with `getPrivateKey()`. +- `EloAdaptor` – `%ELO` encrypted Loro with `getPrivateKey()`. ### 3.2 Constructor Options diff --git a/packages/loro-adaptors/src/elo-adaptor.ts b/packages/loro-adaptors/src/elo-adaptor.ts index d46d020..087f9e9 100644 --- a/packages/loro-adaptors/src/elo-adaptor.ts +++ b/packages/loro-adaptors/src/elo-adaptor.ts @@ -16,9 +16,9 @@ import { } from "loro-protocol"; // Minimal placeholder to avoid requiring DOM lib in this package -interface CryptoKey {} +interface CryptoKey { } -export interface EloLoroAdaptorConfig { +export interface EloAdaptorConfig { getPrivateKey: ( keyId?: string ) => Promise<{ keyId: string; key: CryptoKey | Uint8Array }>; @@ -36,7 +36,7 @@ export class EloAdaptor implements CrdtDocAdaptor { private doc: LoroDoc; private ctx?: CrdtAdaptorContext; private destroyed = false; - private config: EloLoroAdaptorConfig; + private config: EloAdaptorConfig; private localUpdateUnsubscribe?: () => void; private initServerVersion?: VersionVector; private hasReachedServerVersion = false; @@ -48,15 +48,15 @@ export class EloAdaptor implements CrdtDocAdaptor { private lastSentVV?: Record; // Overloads to allow (config) or (doc, config) - constructor(doc: LoroDoc, config: EloLoroAdaptorConfig); - constructor(config: EloLoroAdaptorConfig); + constructor(doc: LoroDoc, config: EloAdaptorConfig); + constructor(config: EloAdaptorConfig); constructor( - docOrConfig: LoroDoc | EloLoroAdaptorConfig, - maybeConfig?: EloLoroAdaptorConfig + docOrConfig: LoroDoc | EloAdaptorConfig, + maybeConfig?: EloAdaptorConfig ) { if (docOrConfig instanceof LoroDoc) { this.doc = docOrConfig; - this.config = maybeConfig as EloLoroAdaptorConfig; + this.config = maybeConfig as EloAdaptorConfig; } else { this.doc = new LoroDoc(); this.config = docOrConfig; diff --git a/packages/loro-adaptors/tests/elo-adaptor.test.ts b/packages/loro-adaptors/tests/elo-adaptor.test.ts index f6b6062..bf59071 100644 --- a/packages/loro-adaptors/tests/elo-adaptor.test.ts +++ b/packages/loro-adaptors/tests/elo-adaptor.test.ts @@ -15,7 +15,7 @@ function hexToBytes(hex: string): Uint8Array { return out; } -describe("EloLoroAdaptor — snapshot join", () => { +describe("EloAdaptor — snapshot join", () => { let doc: LoroDoc; let adaptor: EloAdaptor; const key = hexToBytes(KEY_HEX); @@ -60,7 +60,7 @@ describe("EloLoroAdaptor — snapshot join", () => { }); }); -describe("EloLoroAdaptor — apply snapshot update", () => { +describe("EloAdaptor — apply snapshot update", () => { it("applies a snapshot container and updates the doc", async () => { const key = hexToBytes(KEY_HEX); // Create a source doc with content @@ -99,7 +99,7 @@ describe("EloLoroAdaptor — apply snapshot update", () => { }); }); -describe("EloLoroAdaptor — apply delta update", () => { +describe("EloAdaptor — apply delta update", () => { it("applies a delta container update and updates the doc", async () => { const key = hexToBytes(KEY_HEX); // Create source update plaintext via standard update export diff --git a/packages/loro-protocol/README.md b/packages/loro-protocol/README.md index 9eb2dd0..db54816 100644 --- a/packages/loro-protocol/README.md +++ b/packages/loro-protocol/README.md @@ -27,7 +27,7 @@ import { encode, decode, CrdtType, MessageType } from "loro-protocol"; const join = encode({ type: MessageType.JoinRequest, crdt: CrdtType.Loro, - roomId: new TextEncoder().encode("room-1"), + roomId: "room-1", auth: new Uint8Array(), version: new Uint8Array(), }); diff --git a/packages/loro-protocol/src/encoding.ts b/packages/loro-protocol/src/encoding.ts index 07c9346..d09d324 100644 --- a/packages/loro-protocol/src/encoding.ts +++ b/packages/loro-protocol/src/encoding.ts @@ -88,18 +88,13 @@ export function encode(message: ProtocolMessage): Uint8Array { const crdtBytes = new TextEncoder().encode(message.crdt); writer.pushBytes(crdtBytes); - // Write room ID as varBytes - if (typeof message.roomId === "string") { - if (message.roomId.length > MAX_ROOM_ID_LENGTH) { - throw new Error("Room ID too long"); - } - writer.pushVarString(message.roomId); - } else { - if (message.roomId.length > MAX_ROOM_ID_LENGTH) { - throw new Error("Room ID too long"); - } - writer.pushVarBytes(message.roomId); + // Write room ID as varString + const roomIdBytes = new TextEncoder().encode(message.roomId); + if (roomIdBytes.byteLength > MAX_ROOM_ID_LENGTH) { + throw new Error("Room ID too long"); } + writer.pushVarBytes(roomIdBytes); + // Write message type writer.pushByte(message.type); @@ -209,10 +204,7 @@ export function decode(data: Uint8Array): ProtocolMessage { } // Read room ID - const roomId = reader.readVarBytes(); - if (roomId.length > 128) { - throw new Error("Room ID exceeds maximum length of 128 bytes"); - } + const roomId = reader.readVarString(); // Read message type const typeByte = reader.readByte(); diff --git a/packages/loro-protocol/src/protocol.ts b/packages/loro-protocol/src/protocol.ts index 1bb79c2..781fc37 100644 --- a/packages/loro-protocol/src/protocol.ts +++ b/packages/loro-protocol/src/protocol.ts @@ -66,7 +66,7 @@ export const UpdateErrorCode = { export type UpdateErrorCode = (typeof UpdateErrorCode)[keyof typeof UpdateErrorCode]; -export type RoomId = string | Uint8Array; +export type RoomId = string; export interface MessageBase { crdt: CrdtType; diff --git a/packages/loro-protocol/tests/__snapshots__/encoding.snap.test.ts.snap b/packages/loro-protocol/tests/__snapshots__/encoding.snap.test.ts.snap index 3f34a7d..ab3cff9 100644 --- a/packages/loro-protocol/tests/__snapshots__/encoding.snap.test.ts.snap +++ b/packages/loro-protocol/tests/__snapshots__/encoding.snap.test.ts.snap @@ -1,27 +1,27 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`encoding snapshots > DocUpdate multiple updates 1`] = `"0x25594a53040102030403030301020304040506070108"`; +exports[`encoding snapshots > DocUpdate multiple updates 1`] = `"0x25594a5309726f6f6d2d3132333403030301020304040506070108"`; -exports[`encoding snapshots > DocUpdateFragment 1`] = `"0x254c4f5204010203040500000000000000000320000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"`; +exports[`encoding snapshots > DocUpdateFragment 1`] = `"0x254c4f5209726f6f6d2d313233340500000000000000000320000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"`; -exports[`encoding snapshots > DocUpdateFragmentHeader 1`] = `"0x254c4f52040102030404ff112233445566770a80c03e"`; +exports[`encoding snapshots > DocUpdateFragmentHeader 1`] = `"0x254c4f5209726f6f6d2d3132333404ff112233445566770a80c03e"`; -exports[`encoding snapshots > JoinError app error with appCode 1`] = `"0x25594a530401020304027f1a4170706c69636174696f6e207370656369666963206572726f720e71756f74615f6578636565646564"`; +exports[`encoding snapshots > JoinError app error with appCode 1`] = `"0x25594a5309726f6f6d2d31323334027f1a4170706c69636174696f6e207370656369666963206572726f720e71756f74615f6578636565646564"`; -exports[`encoding snapshots > JoinError auth failed 1`] = `"0x254550480401020304020213496e76616c69642063726564656e7469616c73"`; +exports[`encoding snapshots > JoinError auth failed 1`] = `"0x2545504809726f6f6d2d31323334020213496e76616c69642063726564656e7469616c73"`; -exports[`encoding snapshots > JoinError version unknown with receiver version 1`] = `"0x254c4f52040102030402011056657273696f6e206d69736d61746368026364"`; +exports[`encoding snapshots > JoinError version unknown with receiver version 1`] = `"0x254c4f5209726f6f6d2d3132333402011056657273696f6e206d69736d61746368026364"`; -exports[`encoding snapshots > JoinRequest 1`] = `"0x254c4f52040102030400030a141e0328323c"`; +exports[`encoding snapshots > JoinRequest 1`] = `"0x254c4f5209726f6f6d2d3132333400030a141e0328323c"`; -exports[`encoding snapshots > JoinResponseOk read 1`] = `"0x25594a530401020304010472656164030b162100"`; +exports[`encoding snapshots > JoinResponseOk read 1`] = `"0x25594a5309726f6f6d2d31323334010472656164030b162100"`; -exports[`encoding snapshots > JoinResponseOk write + extra 1`] = `"0x254c4f52040102030401057772697465022c3703424d58"`; +exports[`encoding snapshots > JoinResponseOk write + extra 1`] = `"0x254c4f5209726f6f6d2d3132333401057772697465022c3703424d58"`; -exports[`encoding snapshots > Leave 1`] = `"0x254c4f52040102030407"`; +exports[`encoding snapshots > Leave 1`] = `"0x254c4f5209726f6f6d2d3132333407"`; -exports[`encoding snapshots > UpdateError app error with appCode 1`] = `"0x254c4f520401020304067f10437573746f6d20617070206572726f720f637573746f6d5f636f64655f313233"`; +exports[`encoding snapshots > UpdateError app error with appCode 1`] = `"0x254c4f5209726f6f6d2d31323334067f10437573746f6d20617070206572726f720f637573746f6d5f636f64655f313233"`; -exports[`encoding snapshots > UpdateError fragment timeout with batchId 1`] = `"0x255941570401020304060710467261676d656e742074696d656f75740100000000000000"`; +exports[`encoding snapshots > UpdateError fragment timeout with batchId 1`] = `"0x2559415709726f6f6d2d31323334060710467261676d656e742074696d656f75740100000000000000"`; -exports[`encoding snapshots > UpdateError permission denied 1`] = `"0x254c4f5204010203040603134e6f207772697465207065726d697373696f6e"`; +exports[`encoding snapshots > UpdateError permission denied 1`] = `"0x254c4f5209726f6f6d2d313233340603134e6f207772697465207065726d697373696f6e"`; diff --git a/packages/loro-protocol/tests/encoding.snap.test.ts b/packages/loro-protocol/tests/encoding.snap.test.ts index c6d0b74..1cf16f7 100644 --- a/packages/loro-protocol/tests/encoding.snap.test.ts +++ b/packages/loro-protocol/tests/encoding.snap.test.ts @@ -19,7 +19,7 @@ import { // TODO: REVIEW ensure binary snapshots stay stable across changes to protocol describe("encoding snapshots", () => { - const roomId = new Uint8Array([1, 2, 3, 4]); + const roomId = "room-1234"; it("JoinRequest", () => { const msg: JoinRequest = { diff --git a/packages/loro-protocol/tests/encoding.test.ts b/packages/loro-protocol/tests/encoding.test.ts index 8bd2553..abcf715 100644 --- a/packages/loro-protocol/tests/encoding.test.ts +++ b/packages/loro-protocol/tests/encoding.test.ts @@ -16,7 +16,7 @@ import { } from "../src/protocol"; describe("Message Encoding and Decoding", () => { - const roomId = new Uint8Array([1, 2, 3, 4, 5]); + const roomId = "room-123"; describe("JoinRequest", () => { it("encodes and decodes JoinRequest correctly", () => { @@ -437,7 +437,7 @@ describe("Message Encoding and Decoding", () => { const message: Leave = { type: MessageType.Leave, crdt: CrdtType.Loro, - roomId: new Uint8Array([1, 2, 3]), + roomId: "room-123", }; const encoded = encode(message); @@ -466,7 +466,7 @@ describe("Message Encoding and Decoding", () => { describe("Room ID validation", () => { it("accepts room IDs up to 128 bytes", () => { - const maxRoomId = new Uint8Array(128); + const maxRoomId = "a".repeat(128); const message: Leave = { type: MessageType.Leave, crdt: CrdtType.Loro, @@ -480,7 +480,7 @@ describe("Message Encoding and Decoding", () => { }); it("throws error for room IDs exceeding 128 bytes during encode", () => { - const largeRoomId = new Uint8Array(129); + const largeRoomId = "a".repeat(129); const message: Leave = { type: MessageType.Leave, crdt: CrdtType.Loro, @@ -542,7 +542,7 @@ describe("Message Encoding and Decoding", () => { const message: Leave = { type: MessageType.Leave, crdt: CrdtType.Loro, - roomId: new Uint8Array(0), + roomId: "", }; const encoded = encode(message); diff --git a/packages/loro-websocket/README.md b/packages/loro-websocket/README.md index 4c71127..c2beee5 100644 --- a/packages/loro-websocket/README.md +++ b/packages/loro-websocket/README.md @@ -41,18 +41,18 @@ await room.destroy(); offStatus(); ``` -%ELO (end‑to‑end encrypted Loro) using `EloLoroAdaptor`: +%ELO (end‑to‑end encrypted Loro) using `EloAdaptor`: ```ts import { LoroWebsocketClient } from "loro-websocket"; -import { EloLoroAdaptor } from "loro-adaptors/loro"; +import { EloAdaptor } from "loro-adaptors/loro"; const key = new Uint8Array(32); key[0] = 1; const client = new LoroWebsocketClient({ url: "ws://localhost:8787" }); await client.waitConnected(); -const adaptor = new EloLoroAdaptor({ +const adaptor = new EloAdaptor({ getPrivateKey: async () => ({ keyId: "k1", key }), }); await client.join({ roomId: "secure-room", crdtAdaptor: adaptor }); diff --git a/packages/loro-websocket/src/client/index.ts b/packages/loro-websocket/src/client/index.ts index 85f142d..b867e63 100644 --- a/packages/loro-websocket/src/client/index.ts +++ b/packages/loro-websocket/src/client/index.ts @@ -504,10 +504,7 @@ export class LoroWebsocketClient { } private async handleMessage(msg: ProtocolMessage) { - const roomIdStr = - typeof msg.roomId === "string" - ? msg.roomId - : new TextDecoder().decode(msg.roomId); + const roomIdStr = msg.roomId; const roomId = msg.crdt + roomIdStr; switch (msg.type) { @@ -555,10 +552,7 @@ export class LoroWebsocketClient { if (active) { active.handler.handleUpdateError(msg); } else { - const roomIdStr = - typeof msg.roomId === "string" - ? msg.roomId - : new TextDecoder().decode(msg.roomId); + const roomIdStr = msg.roomId; console.error( `Update error for room ${roomIdStr}: ${msg.code} - ${msg.message}` ); @@ -569,10 +563,7 @@ export class LoroWebsocketClient { } private handleFragmentHeader(msg: DocUpdateFragmentHeader) { - const roomIdStr = - typeof msg.roomId === "string" - ? msg.roomId - : new TextDecoder().decode(msg.roomId); + const roomIdStr = msg.roomId; const batchKey = `${msg.crdt}-${roomIdStr}-${msg.batchId}`; // Clear any existing batch with same ID @@ -608,10 +599,7 @@ export class LoroWebsocketClient { } private handleFragment(msg: DocUpdateFragment) { - const roomIdStr = - typeof msg.roomId === "string" - ? msg.roomId - : new TextDecoder().decode(msg.roomId); + const roomIdStr = msg.roomId; const batchKey = `${msg.crdt}-${roomIdStr}-${msg.batchId}`; const batch = this.fragmentBatches.get(batchKey); diff --git a/packages/loro-websocket/src/server/simple-server.ts b/packages/loro-websocket/src/server/simple-server.ts index 88369ab..c26f3f5 100644 --- a/packages/loro-websocket/src/server/simple-server.ts +++ b/packages/loro-websocket/src/server/simple-server.ts @@ -20,7 +20,6 @@ import { HexString, DocUpdateFragmentHeader, DocUpdateFragment, - bytesToHex, MAX_MESSAGE_SIZE, } from "loro-protocol"; import { @@ -276,7 +275,7 @@ export class SimpleServer { let permission: Permission = "write"; if (this.config.authenticate) { const authResult = await this.config.authenticate( - this.roomIdToString(message.roomId), + message.roomId, message.crdt, message.auth ); @@ -624,7 +623,7 @@ export class SimpleServer { if (descriptor.shouldPersist && this.config.onLoadDocument) { try { const loaded = await this.config.onLoadDocument( - this.roomIdToString(roomId), + roomId, crdtType ); if (loaded) { @@ -703,12 +702,7 @@ export class SimpleServer { } private getRoomKey(roomId: RoomId, crdtType: CrdtType): string { - const roomStr = this.roomIdToString(roomId); - return `${roomStr}:${crdtType}`; - } - - private roomIdToString(roomId: RoomId): string { - return typeof roomId === "string" ? roomId : bytesToHex(roomId); + return `${roomId}:${crdtType}`; } private parseRoomKey( diff --git a/packages/loro-websocket/test-wrappers/recv-elo-doc.ts b/packages/loro-websocket/test-wrappers/recv-elo-doc.ts index 26d6017..f32a582 100644 --- a/packages/loro-websocket/test-wrappers/recv-elo-doc.ts +++ b/packages/loro-websocket/test-wrappers/recv-elo-doc.ts @@ -1,6 +1,6 @@ import { WebSocket as NodeWebSocket } from "ws"; import { LoroWebsocketClient } from "../src/client"; -import { EloLoroAdaptor } from "loro-adaptors"; +import { EloAdaptor } from "loro-adaptors/loro"; import { pathToFileURL } from "node:url"; function hexToBytes(s: string): Uint8Array { @@ -25,7 +25,7 @@ async function main() { const key = hexToBytes( "0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" ); - const adaptor = new EloLoroAdaptor({ + const adaptor = new EloAdaptor({ getPrivateKey: async () => ({ keyId: "k1", key }), }); const client = new LoroWebsocketClient({ url }); diff --git a/packages/loro-websocket/test-wrappers/recv-index.ts b/packages/loro-websocket/test-wrappers/recv-index.ts index 5a599a1..e0f8e4a 100644 --- a/packages/loro-websocket/test-wrappers/recv-index.ts +++ b/packages/loro-websocket/test-wrappers/recv-index.ts @@ -26,7 +26,7 @@ async function main() { const join: ProtocolMessage = { type: MessageType.JoinRequest, crdt: CrdtType.Elo, - roomId: Buffer.from(roomId), + roomId: roomId, auth: new Uint8Array(), version: new Uint8Array(), }; diff --git a/packages/loro-websocket/test-wrappers/send-elo-normative.ts b/packages/loro-websocket/test-wrappers/send-elo-normative.ts index 53da4d6..ef6ce22 100644 --- a/packages/loro-websocket/test-wrappers/send-elo-normative.ts +++ b/packages/loro-websocket/test-wrappers/send-elo-normative.ts @@ -1,8 +1,8 @@ -// Send a real %ELO update using the EloLoroAdaptor and loro-crdt +// Send a real %ELO update using the EloAdaptor and loro-crdt // This ensures proper join ordering and avoids races with the server. import { WebSocket as NodeWebSocket } from "ws"; import { LoroWebsocketClient } from "../src/client"; -import { EloLoroAdaptor } from "loro-adaptors"; +import { EloAdaptor } from "loro-adaptors/loro"; import { pathToFileURL } from "node:url"; function hexToBytes(s: string): Uint8Array { @@ -30,7 +30,7 @@ async function main() { "0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" ); - const adaptor = new EloLoroAdaptor({ + const adaptor = new EloAdaptor({ getPrivateKey: async () => ({ keyId: "k1", key }), }); diff --git a/protocol.md b/protocol.md index 327c6be..cf4b7c7 100644 --- a/protocol.md +++ b/protocol.md @@ -26,7 +26,7 @@ Exception: the protocol also defines two out‑of‑band keepalive frames, "ping - "%YAW": Yjs Awareness - "%FLO": Flock Document - ... -- Followed by a `varBytes` room ID (maximum 128 bytes). +- Followed by a `varString` room ID (maximum 128 bytes). - If two room IDs are the same but the CRDT types differ, they refer to different rooms. - Followed by a byte for the message type. - Payload, which depends on the message type. diff --git a/rust/loro-protocol/src/encoding.rs b/rust/loro-protocol/src/encoding.rs index a4be475..b821ea9 100644 --- a/rust/loro-protocol/src/encoding.rs +++ b/rust/loro-protocol/src/encoding.rs @@ -54,9 +54,10 @@ pub fn encode(message: &ProtocolMessage) -> Result, String> { | ProtocolMessage::DocUpdateFragment { crdt, room_id, batch_id: _, index: _, fragment: _ } | ProtocolMessage::UpdateError { crdt, room_id, code: _, message: _, batch_id: _, app_code: _ } | ProtocolMessage::Leave { crdt, room_id } => { - if room_id.len() > MAX_ROOM_ID_LENGTH { return Err("Room ID too long".into()); } + let room_id_bytes = room_id.as_bytes(); + if room_id_bytes.len() > MAX_ROOM_ID_LENGTH { return Err("Room ID too long".into()); } encode_crdt(&mut w, *crdt); - w.push_var_bytes(room_id); + w.push_var_bytes(room_id_bytes); } } @@ -136,8 +137,8 @@ pub fn decode(buf: &[u8]) -> Result { // CRDT let crdt = decode_crdt(&mut r)?; - // room id (varBytes) - let room_id = r.read_var_bytes()?.to_vec(); + // room id (varString) + let room_id = r.read_var_string()?; if room_id.len() > MAX_ROOM_ID_LENGTH { return Err("Room ID exceeds maximum length of 128 bytes".into()); } @@ -233,7 +234,7 @@ mod tests { fn encode_decode_leave() { let msg = ProtocolMessage::Leave { crdt: CrdtType::Loro, - room_id: vec![1,2,3,4,5], + room_id: "room-123".to_string(), }; let enc = encode(&msg).unwrap(); let dec = decode(&enc).unwrap(); @@ -244,7 +245,7 @@ mod tests { fn encode_join_response_ok_defaults_extra() { let msg = ProtocolMessage::JoinResponseOk { crdt: CrdtType::Yjs, - room_id: vec![1,2,3], + room_id: "room-123".to_string(), permission: Permission::Read, version: vec![10,20], extra: None, diff --git a/rust/loro-protocol/src/lib.rs b/rust/loro-protocol/src/lib.rs index 1a8fa66..7b1d2bf 100644 --- a/rust/loro-protocol/src/lib.rs +++ b/rust/loro-protocol/src/lib.rs @@ -19,7 +19,7 @@ //! // Build a JoinRequest //! let msg = ProtocolMessage::JoinRequest { //! crdt: CrdtType::Loro, -//! room_id: vec![1,2,3,4], +//! room_id: "room-123".to_string(), //! auth: vec![10,20,30], //! version: vec![40,50,60], //! }; diff --git a/rust/loro-protocol/src/protocol.rs b/rust/loro-protocol/src/protocol.rs index 41c9fac..643fd6c 100644 --- a/rust/loro-protocol/src/protocol.rs +++ b/rust/loro-protocol/src/protocol.rs @@ -172,20 +172,20 @@ impl BatchId { pub enum ProtocolMessage { JoinRequest { crdt: CrdtType, - room_id: Vec, + room_id: String, auth: Vec, version: Vec, }, JoinResponseOk { crdt: CrdtType, - room_id: Vec, + room_id: String, permission: Permission, version: Vec, extra: Option>, }, JoinError { crdt: CrdtType, - room_id: Vec, + room_id: String, code: JoinErrorCode, message: String, receiver_version: Option>, @@ -193,26 +193,26 @@ pub enum ProtocolMessage { }, DocUpdate { crdt: CrdtType, - room_id: Vec, + room_id: String, updates: Vec>, }, DocUpdateFragmentHeader { crdt: CrdtType, - room_id: Vec, + room_id: String, batch_id: BatchId, fragment_count: u64, total_size_bytes: u64, }, DocUpdateFragment { crdt: CrdtType, - room_id: Vec, + room_id: String, batch_id: BatchId, index: u64, fragment: Vec, }, UpdateError { crdt: CrdtType, - room_id: Vec, + room_id: String, code: UpdateErrorCode, message: String, batch_id: Option, @@ -220,6 +220,6 @@ pub enum ProtocolMessage { }, Leave { crdt: CrdtType, - room_id: Vec, + room_id: String, }, } diff --git a/rust/loro-protocol/tests/js_snapshots.rs b/rust/loro-protocol/tests/js_snapshots.rs index f00f360..fc9be77 100644 --- a/rust/loro-protocol/tests/js_snapshots.rs +++ b/rust/loro-protocol/tests/js_snapshots.rs @@ -12,12 +12,12 @@ fn snapshot_join_request() { // exports[`encoding snapshots > JoinRequest 1`] let msg = ProtocolMessage::JoinRequest { crdt: CrdtType::Loro, - room_id: vec![1,2,3,4], + room_id: "room-1234".to_string(), auth: vec![10,20,30], version: vec![40,50,60], }; let hex = to_hex(&encode(&msg).unwrap()); - assert_eq!(hex, "0x254c4f52040102030400030a141e0328323c"); + assert_eq!(hex, "0x254c4f5209726f6f6d2d3132333400030a141e0328323c"); } #[test] @@ -25,13 +25,13 @@ fn snapshot_join_response_read() { // exports[`encoding snapshots > JoinResponseOk read 1`] let msg = ProtocolMessage::JoinResponseOk { crdt: CrdtType::Yjs, - room_id: vec![1,2,3,4], + room_id: "room-1234".to_string(), permission: Permission::Read, version: vec![11,22,33], extra: None, }; let hex = to_hex(&encode(&msg).unwrap()); - assert_eq!(hex, "0x25594a530401020304010472656164030b162100"); + assert_eq!(hex, "0x25594a5309726f6f6d2d31323334010472656164030b162100"); } #[test] @@ -39,13 +39,13 @@ fn snapshot_join_response_write_extra() { // exports[`encoding snapshots > JoinResponseOk write + extra 1`] let msg = ProtocolMessage::JoinResponseOk { crdt: CrdtType::Loro, - room_id: vec![1,2,3,4], + room_id: "room-1234".to_string(), permission: Permission::Write, version: vec![44,55], extra: Some(vec![66,77,88]), }; let hex = to_hex(&encode(&msg).unwrap()); - assert_eq!(hex, "0x254c4f52040102030401057772697465022c3703424d58"); + assert_eq!(hex, "0x254c4f5209726f6f6d2d3132333401057772697465022c3703424d58"); } #[test] @@ -53,14 +53,14 @@ fn snapshot_join_error_auth_failed() { // exports[`encoding snapshots > JoinError auth failed 1`] let msg = ProtocolMessage::JoinError { crdt: CrdtType::LoroEphemeralStore, - room_id: vec![1,2,3,4], + room_id: "room-1234".to_string(), code: JoinErrorCode::AuthFailed, message: "Invalid credentials".into(), receiver_version: None, app_code: None, }; let hex = to_hex(&encode(&msg).unwrap()); - assert_eq!(hex, "0x254550480401020304020213496e76616c69642063726564656e7469616c73"); + assert_eq!(hex, "0x2545504809726f6f6d2d31323334020213496e76616c69642063726564656e7469616c73"); } #[test] @@ -68,14 +68,14 @@ fn snapshot_join_error_version_unknown() { // exports[`encoding snapshots > JoinError version unknown with receiver version 1`] let msg = ProtocolMessage::JoinError { crdt: CrdtType::Loro, - room_id: vec![1,2,3,4], + room_id: "room-1234".to_string(), code: JoinErrorCode::VersionUnknown, message: "Version mismatch".into(), receiver_version: Some(vec![99, 100]), app_code: None, }; let hex = to_hex(&encode(&msg).unwrap()); - assert_eq!(hex, "0x254c4f52040102030402011056657273696f6e206d69736d61746368026364"); + assert_eq!(hex, "0x254c4f5209726f6f6d2d3132333402011056657273696f6e206d69736d61746368026364"); } #[test] @@ -83,14 +83,14 @@ fn snapshot_join_error_app_error() { // exports[`encoding snapshots > JoinError app error with appCode 1`] let msg = ProtocolMessage::JoinError { crdt: CrdtType::Yjs, - room_id: vec![1,2,3,4], + room_id: "room-1234".to_string(), code: JoinErrorCode::AppError, message: "Application specific error".into(), receiver_version: None, app_code: Some("quota_exceeded".into()), }; let hex = to_hex(&encode(&msg).unwrap()); - assert_eq!(hex, "0x25594a530401020304027f1a4170706c69636174696f6e207370656369666963206572726f720e71756f74615f6578636565646564"); + assert_eq!(hex, "0x25594a5309726f6f6d2d31323334027f1a4170706c69636174696f6e207370656369666963206572726f720e71756f74615f6578636565646564"); } #[test] @@ -98,11 +98,11 @@ fn snapshot_doc_update_multiple() { // exports[`encoding snapshots > DocUpdate multiple updates 1`] let msg = ProtocolMessage::DocUpdate { crdt: CrdtType::Yjs, - room_id: vec![1,2,3,4], + room_id: "room-1234".to_string(), updates: vec![vec![1,2,3], vec![4,5,6,7], vec![8]], }; let hex = to_hex(&encode(&msg).unwrap()); - assert_eq!(hex, "0x25594a53040102030403030301020304040506070108"); + assert_eq!(hex, "0x25594a5309726f6f6d2d3132333403030301020304040506070108"); } #[test] @@ -110,13 +110,13 @@ fn snapshot_fragment_header() { // exports[`encoding snapshots > DocUpdateFragmentHeader 1`] let msg = ProtocolMessage::DocUpdateFragmentHeader { crdt: CrdtType::Loro, - room_id: vec![1,2,3,4], + room_id: "room-1234".to_string(), batch_id: BatchId::from_hex("0xff11223344556677").unwrap(), fragment_count: 10, total_size_bytes: 1_024_000, }; let hex = to_hex(&encode(&msg).unwrap()); - assert_eq!(hex, "0x254c4f52040102030404ff112233445566770a80c03e"); + assert_eq!(hex, "0x254c4f5209726f6f6d2d3132333404ff112233445566770a80c03e"); } #[test] @@ -126,13 +126,13 @@ fn snapshot_fragment() { for (i, b) in frag.iter_mut().enumerate() { *b = (i & 0xff) as u8; } let msg = ProtocolMessage::DocUpdateFragment { crdt: CrdtType::Loro, - room_id: vec![1,2,3,4], + room_id: "room-1234".to_string(), batch_id: BatchId([0u8; 8]), index: 3, fragment: frag, }; let hex = to_hex(&encode(&msg).unwrap()); - assert_eq!(hex, "0x254c4f5204010203040500000000000000000320000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); + assert_eq!(hex, "0x254c4f5209726f6f6d2d313233340500000000000000000320000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); } #[test] @@ -140,14 +140,14 @@ fn snapshot_update_error_permission_denied() { // exports[`encoding snapshots > UpdateError permission denied 1`] let msg = ProtocolMessage::UpdateError { crdt: CrdtType::Loro, - room_id: vec![1,2,3,4], + room_id: "room-1234".to_string(), code: UpdateErrorCode::PermissionDenied, message: "No write permission".into(), batch_id: None, app_code: None, }; let hex = to_hex(&encode(&msg).unwrap()); - assert_eq!(hex, "0x254c4f5204010203040603134e6f207772697465207065726d697373696f6e"); + assert_eq!(hex, "0x254c4f5209726f6f6d2d313233340603134e6f207772697465207065726d697373696f6e"); } #[test] @@ -155,14 +155,14 @@ fn snapshot_update_error_fragment_timeout() { // exports[`encoding snapshots > UpdateError fragment timeout with batchId 1`] let msg = ProtocolMessage::UpdateError { crdt: CrdtType::YjsAwareness, - room_id: vec![1,2,3,4], + room_id: "room-1234".to_string(), code: UpdateErrorCode::FragmentTimeout, message: "Fragment timeout".into(), batch_id: Some(BatchId([1,0,0,0,0,0,0,0])), app_code: None, }; let hex = to_hex(&encode(&msg).unwrap()); - assert_eq!(hex, "0x255941570401020304060710467261676d656e742074696d656f75740100000000000000"); + assert_eq!(hex, "0x2559415709726f6f6d2d31323334060710467261676d656e742074696d656f75740100000000000000"); } #[test] @@ -170,14 +170,14 @@ fn snapshot_update_error_app_error() { // exports[`encoding snapshots > UpdateError app error with appCode 1`] let msg = ProtocolMessage::UpdateError { crdt: CrdtType::Loro, - room_id: vec![1,2,3,4], + room_id: "room-1234".to_string(), code: UpdateErrorCode::AppError, message: "Custom app error".into(), batch_id: None, app_code: Some("custom_code_123".into()), }; let hex = to_hex(&encode(&msg).unwrap()); - assert_eq!(hex, "0x254c4f520401020304067f10437573746f6d20617070206572726f720f637573746f6d5f636f64655f313233"); + assert_eq!(hex, "0x254c4f5209726f6f6d2d31323334067f10437573746f6d20617070206572726f720f637573746f6d5f636f64655f313233"); } #[test] @@ -185,8 +185,8 @@ fn snapshot_leave() { // exports[`encoding snapshots > Leave 1`] let msg = ProtocolMessage::Leave { crdt: CrdtType::Loro, - room_id: vec![1,2,3,4], + room_id: "room-1234".to_string(), }; let hex = to_hex(&encode(&msg).unwrap()); - assert_eq!(hex, "0x254c4f52040102030407"); + assert_eq!(hex, "0x254c4f5209726f6f6d2d3132333407"); } diff --git a/rust/loro-websocket-client/src/lib.rs b/rust/loro-websocket-client/src/lib.rs index a9ff929..0168e96 100644 --- a/rust/loro-websocket-client/src/lib.rs +++ b/rust/loro-websocket-client/src/lib.rs @@ -13,7 +13,7 @@ //! # let rt = tokio::runtime::Builder::new_current_thread().enable_all().build()?; //! # rt.block_on(async move { //! let mut client = Client::connect("ws://127.0.0.1:9000").await?; -//! client.send(&ProtocolMessage::Leave { crdt: CrdtType::Loro, room_id: vec![1,2,3] }).await?; +//! client.send(&ProtocolMessage::Leave { crdt: CrdtType::Loro, room_id: "room1".to_string() }).await?; //! if let Some(msg) = client.next().await? { //! println!("got: {:?}", msg); //! } @@ -414,7 +414,7 @@ impl ConnectionWorker { #[derive(Clone, Debug, PartialEq, Eq)] struct RoomKey { crdt: CrdtType, - room: Vec, + room: String, } impl Hash for RoomKey { fn hash(&self, state: &mut H) { @@ -545,7 +545,7 @@ impl LoroWebsocketClient { ) -> Result { let key = RoomKey { crdt: CrdtType::Loro, - room: room_id.as_bytes().to_vec(), + room: room_id.to_string(), }; // Register room without subscription first self.rooms.lock().await.insert( @@ -647,7 +647,7 @@ impl LoroWebsocketClient { ) -> Result { let key = RoomKey { crdt: adaptor.crdt_type(), - room: room_id.as_bytes().to_vec(), + room: room_id.to_string(), }; // Register adaptor for this room, but not active until JoinResponseOk completes. @@ -899,7 +899,7 @@ fn msg_crdt(msg: &ProtocolMessage) -> CrdtType { } } -fn msg_room_id(msg: &ProtocolMessage) -> Vec { +fn msg_room_id(msg: &ProtocolMessage) -> String { match msg { ProtocolMessage::JoinRequest { room_id, .. } | ProtocolMessage::JoinResponseOk { room_id, .. } diff --git a/rust/loro-websocket-server/src/lib.rs b/rust/loro-websocket-server/src/lib.rs index 1c066a1..b1d2e2e 100644 --- a/rust/loro-websocket-server/src/lib.rs +++ b/rust/loro-websocket-server/src/lib.rs @@ -53,7 +53,7 @@ const MAX_BATCH_BYTES: u64 = 64 * 1024 * 1024; // 64 MiB per batch #[derive(Clone, Debug, PartialEq, Eq)] struct RoomKey { crdt: CrdtType, - room: Vec, + room: String, } impl Hash for RoomKey { fn hash(&self, state: &mut H) { @@ -480,7 +480,7 @@ impl Hub { if let Some(state) = self.docs.get(&k) { if state.doc.remove_when_last_subscriber_leaves() { self.docs.remove(&k); - debug!(room=?String::from_utf8_lossy(&k.room), "cleaned up ephemeral doc after last subscriber left"); + debug!(room=?k.room, "cleaned up ephemeral doc after last subscriber left"); } } } @@ -508,7 +508,7 @@ impl Hub { } if !dead.is_empty() { list.retain(|(id, _)| !dead.contains(id)); - debug!(room=?String::from_utf8_lossy(&room.room), removed=%dead.len(), "removed dead subscribers"); + debug!(room=?room.room, removed=%dead.len(), "removed dead subscribers"); } } } @@ -521,7 +521,7 @@ impl Hub { CrdtType::Loro => { let mut d = LoroRoomDoc::new(); if let Some(loader) = &self.config.on_load_document { - let room_str = String::from_utf8_lossy(&room.room).to_string(); + let room_str = room.room.clone(); let ws = self.workspace.clone(); match (loader)(ws, room_str, room.crdt).await { Ok(Some(bytes)) => { @@ -529,7 +529,7 @@ impl Hub { } Ok(None) => {} Err(e) => { - warn!(room=?String::from_utf8_lossy(&room.room), %e, "load document failed"); + warn!(room=?room.room, %e, "load document failed"); } } } @@ -554,7 +554,7 @@ impl Hub { CrdtType::LoroEphemeralStorePersisted => { let mut d = PersistentEphemeralRoomDoc::new(Self::EPHEMERAL_TIMEOUT_MS); if let Some(loader) = &self.config.on_load_document { - let room_str = String::from_utf8_lossy(&room.room).to_string(); + let room_str = room.room.clone(); let ws = self.workspace.clone(); match (loader)(ws, room_str, room.crdt).await { Ok(Some(bytes)) => { @@ -562,7 +562,7 @@ impl Hub { } Ok(None) => {} Err(e) => { - warn!(room=?String::from_utf8_lossy(&room.room), %e, "load persisted ephemeral store failed"); + warn!(room=?room.room, %e, "load persisted ephemeral store failed"); } } } @@ -598,7 +598,7 @@ impl Hub { fn apply_updates(&mut self, room: &RoomKey, updates: &[Vec]) { if let Some(state) = self.docs.get_mut(room) { if let Err(e) = state.doc.apply_updates(updates) { - warn!(room=?String::from_utf8_lossy(&room.room), %e, "apply_updates failed"); + warn!(room=?room.room, %e, "apply_updates failed"); } else if state.doc.should_persist() { state.dirty = true; } @@ -724,7 +724,7 @@ impl HubRegistry { if state.dirty && state.doc.should_persist() { let start = std::time::Instant::now(); if let Some(snapshot) = state.doc.export_snapshot() { - let room_str = String::from_utf8_lossy(&room.room).to_string(); + let room_str = room.room.clone(); match (saver)(ws.clone(), room_str.clone(), room.crdt, snapshot) .await { @@ -907,7 +907,7 @@ async fn handle_conn( // authenticate let mut permission = h.config.default_permission; if let Some(auth_fn) = &h.config.authenticate { - let room_str = String::from_utf8_lossy(&room.room).to_string(); + let room_str = room.room.clone(); match (auth_fn)(room_str, room.crdt, auth.clone()).await { Ok(Some(p)) => { permission = p; @@ -924,7 +924,7 @@ async fn handle_conn( if let Ok(bytes) = loro_protocol::encode(&err) { let _ = tx.send(Message::Binary(bytes.into())); } - warn!(room=?String::from_utf8_lossy(&room.room), "join denied by authenticate() returning None"); + warn!(room=?room.room, "join denied by authenticate() returning None"); continue; } Err(e) => { @@ -939,7 +939,7 @@ async fn handle_conn( if let Ok(bytes) = loro_protocol::encode(&err) { let _ = tx.send(Message::Binary(bytes.into())); } - warn!(room=?String::from_utf8_lossy(&room.room), "join denied due to authenticate() error"); + warn!(room=?room.room, "join denied due to authenticate() error"); continue; } } @@ -948,7 +948,7 @@ async fn handle_conn( h.join(conn_id, room.clone(), &tx); h.perms.insert((conn_id, room.clone()), permission); joined_rooms.insert(room.clone()); - info!(workspace=%h.workspace, room=?String::from_utf8_lossy(&room.room), ?permission, "join ok"); + info!(workspace=%h.workspace, room=?room.room, ?permission, "join ok"); // respond ok with current version and empty extra let current_version = h.current_version_bytes(&room); let ok = ProtocolMessage::JoinResponseOk { @@ -971,7 +971,7 @@ async fn handle_conn( }; if let Ok(bytes) = loro_protocol::encode(&du) { let _ = tx.send(Message::Binary(bytes.into())); - debug!(room=?String::from_utf8_lossy(&room.room), "sent initial snapshot after join"); + debug!(room=?room.room, "sent initial snapshot after join"); } } else { // Otherwise, attempt backfill if other clients present or the CRDT allows @@ -1000,7 +1000,7 @@ async fn handle_conn( } } if backfill_cnt > 0 { - debug!(room=?String::from_utf8_lossy(&room.room), cnt=%backfill_cnt, "sent backfill after join"); + debug!(room=?room.room, cnt=%backfill_cnt, "sent backfill after join"); } } } @@ -1191,7 +1191,7 @@ async fn handle_conn( let start = std::time::Instant::now(); h.apply_updates(&room, &updates); let elapsed_ms = start.elapsed().as_millis(); - debug!(room=?String::from_utf8_lossy(&room.room), updates=%updates.len(), ms=%elapsed_ms, "applied reassembled updates"); + debug!(room=?room.room, updates=%updates.len(), ms=%elapsed_ms, "applied reassembled updates"); } } CrdtType::Elo => { @@ -1224,7 +1224,7 @@ async fn handle_conn( if let Ok(bytes) = loro_protocol::encode(&err) { let _ = tx.send(Message::Binary(bytes.into())); } - warn!(room=?String::from_utf8_lossy(&room.room), "update rejected: not joined"); + warn!(room=?room.room, "update rejected: not joined"); } else { // Check permission let perm = hub @@ -1257,13 +1257,13 @@ async fn handle_conn( h.apply_updates(&room, &updates); let elapsed_ms = start.elapsed().as_millis(); h.broadcast(&room, conn_id, Message::Binary(data)); - debug!(room=?String::from_utf8_lossy(&room.room), updates=%updates.len(), ms=%elapsed_ms, "applied and broadcast updates"); + debug!(room=?room.room, updates=%updates.len(), ms=%elapsed_ms, "applied and broadcast updates"); } CrdtType::Elo => { // Index headers only; payload remains opaque to server. h.apply_updates(&room, &updates); h.broadcast(&room, conn_id, Message::Binary(data)); - debug!(room=?String::from_utf8_lossy(&room.room), updates=%updates.len(), "indexed and broadcast ELO updates"); + debug!(room=?room.room, updates=%updates.len(), "indexed and broadcast ELO updates"); } _ => { h.broadcast(&room, conn_id, Message::Binary(data)); diff --git a/rust/loro-websocket-server/tests/elo_accept_broadcast.rs b/rust/loro-websocket-server/tests/elo_accept_broadcast.rs index f7015a7..7d103c8 100644 --- a/rust/loro-websocket-server/tests/elo_accept_broadcast.rs +++ b/rust/loro-websocket-server/tests/elo_accept_broadcast.rs @@ -17,7 +17,7 @@ async fn elo_accepts_join_and_broadcasts_updates() { let mut c1 = Client::connect(&url).await.unwrap(); let mut c2 = Client::connect(&url).await.unwrap(); - let room_id = b"room-elo".to_vec(); + let room_id = "room-elo".to_string(); // Both clients join the same %ELO room let j1 = proto::ProtocolMessage::JoinRequest { crdt: CrdtType::Elo, room_id: room_id.clone(), auth: Vec::new(), version: Vec::new() }; diff --git a/rust/loro-websocket-server/tests/elo_fragment_reassembly.rs b/rust/loro-websocket-server/tests/elo_fragment_reassembly.rs index 8de24f3..30f4fc1 100644 --- a/rust/loro-websocket-server/tests/elo_fragment_reassembly.rs +++ b/rust/loro-websocket-server/tests/elo_fragment_reassembly.rs @@ -27,7 +27,7 @@ async fn elo_fragment_reassembly_broadcasts_original_frames() { let mut c2 = Client::connect(&url).await.unwrap(); // Join %ELO room - let room_id = b"room-frag-elo".to_vec(); + let room_id = "room-frag-elo".to_string(); c1.send(&proto::ProtocolMessage::JoinRequest { crdt: CrdtType::Elo, room_id: room_id.clone(), auth: Vec::new(), version: Vec::new() }).await.unwrap(); c2.send(&proto::ProtocolMessage::JoinRequest { crdt: CrdtType::Elo, room_id: room_id.clone(), auth: Vec::new(), version: Vec::new() }).await.unwrap(); // Await JoinResponseOk for both with timeouts diff --git a/rust/loro-websocket-server/tests/join_snapshot_load.rs b/rust/loro-websocket-server/tests/join_snapshot_load.rs index 78f3ce1..9a6bb9d 100644 --- a/rust/loro-websocket-server/tests/join_snapshot_load.rs +++ b/rust/loro-websocket-server/tests/join_snapshot_load.rs @@ -29,7 +29,7 @@ async fn join_sends_snapshot_from_loader() { let url = format!("ws://{}/ws1?token=secret", addr); let mut c = Client::connect(&url).await.unwrap(); // Join - let room = b"room-load".to_vec(); + let room = "room-load".to_string(); let join = proto::ProtocolMessage::JoinRequest { crdt: CrdtType::Loro, room_id: room.clone(), auth: Vec::new(), version: Vec::new() }; c.send(&join).await.unwrap(); diff --git a/rust/loro-websocket-server/tests/readonly_receive.rs b/rust/loro-websocket-server/tests/readonly_receive.rs index 787936b..0fd1171 100644 --- a/rust/loro-websocket-server/tests/readonly_receive.rs +++ b/rust/loro-websocket-server/tests/readonly_receive.rs @@ -27,7 +27,7 @@ async fn readonly_receives_updates_writer_sends() { let mut writer = Client::connect(&url).await.unwrap(); let mut reader = Client::connect(&url).await.unwrap(); - let room = b"room-ro".to_vec(); + let room = "room-ro".to_string(); // Join writer with auth "writer" let join_w = proto::ProtocolMessage::JoinRequest { crdt: CrdtType::Loro, room_id: room.clone(), auth: b"writer".to_vec(), version: Vec::new() }; writer.send(&join_w).await.unwrap(); diff --git a/rust/loro-websocket-server/tests/reject_update_without_join.rs b/rust/loro-websocket-server/tests/reject_update_without_join.rs index a592379..14d3a9e 100644 --- a/rust/loro-websocket-server/tests/reject_update_without_join.rs +++ b/rust/loro-websocket-server/tests/reject_update_without_join.rs @@ -19,7 +19,7 @@ async fn reject_update_without_join() { let mut c = Client::connect(&url).await.unwrap(); // Send DocUpdate without a prior JoinRequest - let msg = proto::ProtocolMessage::DocUpdate { crdt: CrdtType::Loro, room_id: b"room-no-join".to_vec(), updates: vec![vec![1,2,3]] }; + let msg = proto::ProtocolMessage::DocUpdate { crdt: CrdtType::Loro, room_id: "room-no-join".to_string(), updates: vec![vec![1,2,3]] }; c.send(&msg).await.unwrap(); // Expect UpdateError.PermissionDenied