@couch-kit/client 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # @couch-kit/client
2
2
 
3
+ ## 0.10.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#143](https://github.com/faluciano/react-native-couch-kit/pull/143) [`050239e`](https://github.com/faluciano/react-native-couch-kit/commit/050239e251b32641b0c754016510b70ae713fcaa) Thanks [@faluciano](https://github.com/faluciano)! - Room codes are now case-insensitive end to end.
8
+
9
+ A code created as `6DX8` could not be joined as `6dx8`: the Cloudflare relay
10
+ uppercased the code to route the connection to the right Durable Object, but the
11
+ room registry inside still keyed rooms by the raw string from the
12
+ `CREATE_ROOM` / `JOIN_ROOM` message. The join silently missed and the phone sat
13
+ on "connecting" forever.
14
+
15
+ Room codes get read off a TV and retyped or re-scanned, so case must not matter.
16
+ They are now canonicalised in one place — the routing core — which fixes both the
17
+ Bun relay and the Worker.
18
+
19
+ ## 0.10.0
20
+
21
+ ### Minor Changes
22
+
23
+ - [#141](https://github.com/faluciano/react-native-couch-kit/pull/141) [`5142ccd`](https://github.com/faluciano/react-native-couch-kit/commit/5142ccdb2ef37ab85cc57eac1e50f99b567cde70) Thanks [@faluciano](https://github.com/faluciano)! - Relay connections now address the room in the URL: the socket opens against
24
+ `<relayUrl>/r/<roomId>` instead of `<relayUrl>`, and the new `relayRoomUrl()`
25
+ helper builds it.
26
+
27
+ The `CREATE_ROOM` / `JOIN_ROOM` handshake is unchanged, so relays that hold
28
+ every room in one process (the Bun reference server in `services/relay`) ignore
29
+ the path and keep working. Putting the room in the URL lets a relay route a
30
+ connection _before_ reading any frames, which is what per-room hosting — such as
31
+ a Cloudflare Durable Object — requires.
32
+
33
+ No consumer code changes: both `createRelayTransport` and `RelayDisplayHost`
34
+ already take `roomId`, and build the URL themselves.
35
+
3
36
  ## 0.9.0
4
37
 
5
38
  ### Minor Changes
package/dist/index.js CHANGED
@@ -337,6 +337,12 @@ var RelayErrorCodes = {
337
337
  MESSAGE_TOO_LARGE: "MESSAGE_TOO_LARGE",
338
338
  MALFORMED: "MALFORMED"
339
339
  };
340
+ function relayRoomUrl(url, roomId) {
341
+ const trimmed = url.replace(/\/+$/, "");
342
+ const [base, query] = trimmed.split("?", 2);
343
+ const path = `${base}/r/${encodeURIComponent(roomId)}`;
344
+ return query ? `${path}?${query}` : path;
345
+ }
340
346
  // src/relay-transport.ts
341
347
  var POLICY_CLOSE_CODE = 1008;
342
348
 
@@ -351,7 +357,7 @@ class RelayClientTransport {
351
357
  onerror;
352
358
  constructor(options) {
353
359
  this.roomId = options.roomId;
354
- this.ws = new WebSocket(options.url);
360
+ this.ws = new WebSocket(relayRoomUrl(options.url, options.roomId));
355
361
  this.ws.onopen = () => {
356
362
  this.ws.send(JSON.stringify({
357
363
  type: RelayMessageTypes.JOIN_ROOM,
@@ -555,6 +561,7 @@ export {
555
561
  shouldReconnect,
556
562
  resolveWebSocketUrl,
557
563
  resolveSessionSecret,
564
+ relayRoomUrl,
558
565
  interpretHostMessage,
559
566
  createWebSocketTransport,
560
567
  createRelayTransport,
package/lib/client.d.ts CHANGED
@@ -43,7 +43,7 @@ export interface ClientConfig<S extends IGameState, A extends IAction> {
43
43
  * ```
44
44
  */
45
45
  export declare function useGameClient<S extends IGameState, A extends IAction>(config: ClientConfig<S, A>): {
46
- status: "connecting" | "connected" | "disconnected" | "error";
46
+ status: "connected" | "connecting" | "disconnected" | "error";
47
47
  state: S;
48
48
  playerId: string | null;
49
49
  sendAction: (action: A) => void;
@@ -105,4 +105,18 @@ export type RelayClientMessage = CreateRoomMessage | JoinRoomMessage | DataMessa
105
105
  export type RelayServerMessage = RoomCreatedMessage | RoomJoinedMessage | PeerJoinedMessage | PeerLeftMessage | DataMessage | RelayErrorMessage;
106
106
  /** Every relay wire message. */
107
107
  export type RelayMessage = RelayClientMessage | RelayServerMessage;
108
+ /**
109
+ * Builds the socket URL for a room: the configured relay URL with `/r/<roomId>`
110
+ * appended.
111
+ *
112
+ * The room has to be in the URL, not just in the `CREATE_ROOM` / `JOIN_ROOM`
113
+ * message, so that a relay can route the connection before reading any frames —
114
+ * which is exactly what a per-room Cloudflare Durable Object must do. Relays
115
+ * that keep every room in one process (the Bun reference server) ignore the
116
+ * path, so this is safe to send to either.
117
+ *
118
+ * @param url - Base relay URL, e.g. `wss://relay.example.com`.
119
+ * @param roomId - Room code to address.
120
+ */
121
+ export declare function relayRoomUrl(url: string, roomId: string): string;
108
122
  //# sourceMappingURL=relay-protocol.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"relay-protocol.d.ts","sourceRoot":"","sources":["../src/relay-protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,+EAA+E;AAC/E,eAAO,MAAM,iBAAiB;IAC5B,8DAA8D;;IAE9D,0EAA0E;;IAE1E,4CAA4C;;IAE5C,qEAAqE;;IAErE,gDAAgD;;IAEhD,8CAA8C;;IAE9C,yEAAyE;;IAEzE,6CAA6C;;CAErC,CAAC;AAEX,wEAAwE;AACxE,eAAO,MAAM,eAAe;;;;;;;CAOlB,CAAC;AAEX,MAAM,MAAM,cAAc,GACxB,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,CAAC;AAEzD,+CAA+C;AAC/C,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,iBAAiB,CAAC,WAAW,CAAC;IAC3C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,uEAAuE;AACvE,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,iBAAiB,CAAC,YAAY,CAAC;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,4CAA4C;AAC5C,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,iBAAiB,CAAC,SAAS,CAAC;IACzC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wEAAwE;AACxE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,iBAAiB,CAAC,WAAW,CAAC;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,2EAA2E;AAC3E,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,iBAAiB,CAAC,WAAW,CAAC;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qCAAqC;AACrC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,iBAAiB,CAAC,SAAS,CAAC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,iBAAiB,CAAC,IAAI,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,6CAA6C;AAC7C,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,iBAAiB,CAAC,KAAK,CAAC;IACrC,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,kDAAkD;AAClD,MAAM,MAAM,kBAAkB,GAC1B,iBAAiB,GACjB,eAAe,GACf,WAAW,CAAC;AAEhB,kDAAkD;AAClD,MAAM,MAAM,kBAAkB,GAC1B,kBAAkB,GAClB,iBAAiB,GACjB,iBAAiB,GACjB,eAAe,GACf,WAAW,GACX,iBAAiB,CAAC;AAEtB,gCAAgC;AAChC,MAAM,MAAM,YAAY,GAAG,kBAAkB,GAAG,kBAAkB,CAAC"}
1
+ {"version":3,"file":"relay-protocol.d.ts","sourceRoot":"","sources":["../src/relay-protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,+EAA+E;AAC/E,eAAO,MAAM,iBAAiB;IAC5B,8DAA8D;aAC9D,WAAW,EAAE,aAAa;IAC1B,0EAA0E;aAC1E,YAAY,EAAE,cAAc;IAC5B,4CAA4C;aAC5C,SAAS,EAAE,WAAW;IACtB,qEAAqE;aACrE,WAAW,EAAE,aAAa;IAC1B,gDAAgD;aAChD,WAAW,EAAE,aAAa;IAC1B,8CAA8C;aAC9C,SAAS,EAAE,WAAW;IACtB,yEAAyE;aACzE,IAAI,EAAE,MAAM;IACZ,6CAA6C;aAC7C,KAAK,EAAE,OAAO;CACN,CAAC;AAEX,wEAAwE;AACxE,eAAO,MAAM,eAAe;aAC1B,cAAc,EAAE,gBAAgB;aAChC,WAAW,EAAE,aAAa;aAC1B,SAAS,EAAE,WAAW;aACtB,WAAW,EAAE,aAAa;aAC1B,iBAAiB,EAAE,mBAAmB;aACtC,SAAS,EAAE,WAAW;CACd,CAAC;AAEX,MAAM,MAAM,cAAc,GACxB,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,CAAC;AAEzD,+CAA+C;AAC/C,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,iBAAiB,CAAC,WAAW,CAAC;IAC3C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,uEAAuE;AACvE,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,iBAAiB,CAAC,YAAY,CAAC;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,4CAA4C;AAC5C,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,iBAAiB,CAAC,SAAS,CAAC;IACzC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wEAAwE;AACxE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,iBAAiB,CAAC,WAAW,CAAC;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,2EAA2E;AAC3E,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,iBAAiB,CAAC,WAAW,CAAC;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qCAAqC;AACrC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,iBAAiB,CAAC,SAAS,CAAC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,iBAAiB,CAAC,IAAI,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,6CAA6C;AAC7C,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,iBAAiB,CAAC,KAAK,CAAC;IACrC,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,kDAAkD;AAClD,MAAM,MAAM,kBAAkB,GAC1B,iBAAiB,GACjB,eAAe,GACf,WAAW,CAAC;AAEhB,kDAAkD;AAClD,MAAM,MAAM,kBAAkB,GAC1B,kBAAkB,GAClB,iBAAiB,GACjB,iBAAiB,GACjB,eAAe,GACf,WAAW,GACX,iBAAiB,CAAC;AAEtB,gCAAgC;AAChC,MAAM,MAAM,YAAY,GAAG,kBAAkB,GAAG,kBAAkB,CAAC;AAEnE;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAKhE"}
@@ -1 +1 @@
1
- {"version":3,"file":"relay-transport.d.ts","sourceRoot":"","sources":["../src/relay-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC3B,MAAM,aAAa,CAAC;AAMrB,+EAA+E;AAC/E,MAAM,WAAW,qBAAqB;IACpC,0EAA0E;IAC1E,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;CAChB;AASD;;;;;;;;;GASG;AACH,qBAAa,oBAAqB,YAAW,eAAe;IAC1D,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAY;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,KAAK,CAA0C;IACvD,+EAA+E;IAC/E,OAAO,CAAC,gBAAgB,CAAuB;IAE/C,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;gBAExB,OAAO,EAAE,qBAAqB;IAkC1C,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAWxB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAU3C,OAAO,CAAC,kBAAkB;CAkB3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,qBAAqB,GAC7B,qBAAqB,CAEvB"}
1
+ {"version":3,"file":"relay-transport.d.ts","sourceRoot":"","sources":["../src/relay-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC3B,MAAM,aAAa,CAAC;AAOrB,+EAA+E;AAC/E,MAAM,WAAW,qBAAqB;IACpC,0EAA0E;IAC1E,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;CAChB;AASD;;;;;;;;;GASG;AACH,qBAAa,oBAAqB,YAAW,eAAe;IAC1D,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAY;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,KAAK,CAA0C;IACvD,+EAA+E;IAC/E,OAAO,CAAC,gBAAgB,CAAuB;IAE/C,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAEpC,YAAY,OAAO,EAAE,qBAAqB,EAgCzC;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CASvB;IAED,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAQ1C;IAED,OAAO,CAAC,kBAAkB;CAkB3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,qBAAqB,GAC7B,qBAAqB,CAEvB"}
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB;;;;;CAKtB,CAAC;AAEX;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,kDAAkD;IAClD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;OAIG;IACH,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,qEAAqE;IACrE,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,4EAA4E;IAC5E,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,0CAA0C;IAC1C,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CACrC;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,MAAM,eAAe,CAAC;AAE1D;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAuBrE"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB;aAC9B,UAAU,EAAE,CAAC;aACb,IAAI,EAAE,CAAC;aACP,OAAO,EAAE,CAAC;aACV,MAAM,EAAE,CAAC;CACD,CAAC;AAEX;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,kDAAkD;IAClD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;OAIG;IACH,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,qEAAqE;IACrE,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,4EAA4E;IAC5E,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,0CAA0C;IAC1C,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CACrC;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,MAAM,eAAe,CAAC;AAE1D;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAuBrE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@couch-kit/client",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": true
@@ -45,7 +45,6 @@
45
45
  "build": "bun build ./src/index.ts --outdir ./dist --target browser --external react --external @couch-kit/core && tsc -p tsconfig.build.json",
46
46
  "prepublishOnly": "bun run build",
47
47
  "test": "bun test",
48
- "lint": "eslint src/",
49
48
  "typecheck": "tsc --noEmit",
50
49
  "clean": "rm -rf dist lib"
51
50
  },
@@ -54,7 +53,7 @@
54
53
  },
55
54
  "devDependencies": {
56
55
  "react": "^19.0.0",
57
- "typescript": "^6.0.0"
56
+ "typescript": "^7.0.0"
58
57
  },
59
58
  "peerDependencies": {
60
59
  "react": ">=18.0.0"
@@ -129,3 +129,23 @@ export type RelayServerMessage =
129
129
 
130
130
  /** Every relay wire message. */
131
131
  export type RelayMessage = RelayClientMessage | RelayServerMessage;
132
+
133
+ /**
134
+ * Builds the socket URL for a room: the configured relay URL with `/r/<roomId>`
135
+ * appended.
136
+ *
137
+ * The room has to be in the URL, not just in the `CREATE_ROOM` / `JOIN_ROOM`
138
+ * message, so that a relay can route the connection before reading any frames —
139
+ * which is exactly what a per-room Cloudflare Durable Object must do. Relays
140
+ * that keep every room in one process (the Bun reference server) ignore the
141
+ * path, so this is safe to send to either.
142
+ *
143
+ * @param url - Base relay URL, e.g. `wss://relay.example.com`.
144
+ * @param roomId - Room code to address.
145
+ */
146
+ export function relayRoomUrl(url: string, roomId: string): string {
147
+ const trimmed = url.replace(/\/+$/, "");
148
+ const [base, query] = trimmed.split("?", 2);
149
+ const path = `${base}/r/${encodeURIComponent(roomId)}`;
150
+ return query ? `${path}?${query}` : path;
151
+ }
@@ -5,6 +5,7 @@ import {
5
5
  } from "./transport";
6
6
  import {
7
7
  RelayMessageTypes,
8
+ relayRoomUrl,
8
9
  type RelayServerMessage,
9
10
  } from "./relay-protocol";
10
11
 
@@ -47,7 +48,7 @@ export class RelayClientTransport implements ClientTransport {
47
48
 
48
49
  constructor(options: RelayTransportOptions) {
49
50
  this.roomId = options.roomId;
50
- this.ws = new WebSocket(options.url);
51
+ this.ws = new WebSocket(relayRoomUrl(options.url, options.roomId));
51
52
 
52
53
  this.ws.onopen = () => {
53
54
  // The relay socket is up; ask to join the room. The transport is not yet