@couch-kit/display 0.2.2 → 0.3.0

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,47 @@
1
1
  # @couch-kit/display
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#158](https://github.com/faluciano/react-native-couch-kit/pull/158) [`509ea7c`](https://github.com/faluciano/react-native-couch-kit/commit/509ea7c02aa6f2e56ebf01e781d6de74f0ded021) Thanks [@faluciano](https://github.com/faluciano)! - Let the relay assign room codes
8
+
9
+ `RelayDisplayHost`'s `roomId` is now optional. Omit it and the relay mints an
10
+ unused six-character code, reported through the new `onRoomCode` callback and
11
+ the `roomCode` getter.
12
+
13
+ A display could never check its own code for collisions — only the relay knows
14
+ which codes are live — so a self-chosen code could land on a game already in
15
+ progress, and did so only after it was on screen. Minted codes are drawn from
16
+ the CSPRNG over a 32-character alphabet without `O`/`0` or `I`/`1`, giving about
17
+ 1.07 billion codes.
18
+
19
+ Existing callers that pass `roomId` keep their current behaviour, including
20
+ `ROOM_EXISTS` when the code is taken. New callers should expect the code to
21
+ arrive one round trip after connecting rather than being known up front:
22
+
23
+ ```ts
24
+ const [roomCode, setRoomCode] = useState<string | null>(null);
25
+ new RelayDisplayHost({ url, onRoomCode: setRoomCode, reducer, initialState });
26
+ ```
27
+
28
+ Relays need a matching update to mint: both bundled implementations
29
+ (`services/relay`, `services/relay-worker`) support it. A display that omits
30
+ `roomId` against an older relay gets `MALFORMED`.
31
+
32
+ ### Patch Changes
33
+
34
+ - Updated dependencies [[`509ea7c`](https://github.com/faluciano/react-native-couch-kit/commit/509ea7c02aa6f2e56ebf01e781d6de74f0ded021)]:
35
+ - @couch-kit/client@0.13.0
36
+
37
+ ## 0.2.3
38
+
39
+ ### Patch Changes
40
+
41
+ - Updated dependencies [[`4f297a1`](https://github.com/faluciano/react-native-couch-kit/commit/4f297a19c442541703c3bee7bee26354ae3476a4)]:
42
+ - @couch-kit/runtime@0.2.0
43
+ - @couch-kit/client@0.12.0
44
+
3
45
  ## 0.2.2
4
46
 
5
47
  ### Patch Changes
package/dist/index.js CHANGED
@@ -12,17 +12,19 @@ import {
12
12
  class RelayDisplayHost {
13
13
  runtime;
14
14
  ws;
15
- roomId;
15
+ assignedRoomId;
16
+ onRoomCode;
16
17
  peers = new Set;
17
18
  constructor(options) {
18
- const { url, roomId, ...runtimeConfig } = options;
19
- this.roomId = roomId;
19
+ const { url, roomId, onRoomCode, ...runtimeConfig } = options;
20
+ this.assignedRoomId = roomId ?? null;
21
+ this.onRoomCode = onRoomCode;
20
22
  this.runtime = new GameHostRuntime(runtimeConfig);
21
23
  this.ws = new WebSocket(relayRoomUrl(url, roomId));
22
24
  this.ws.onopen = () => {
23
25
  this.ws.send(JSON.stringify({
24
26
  type: RelayMessageTypes.CREATE_ROOM,
25
- roomId: this.roomId
27
+ roomId: this.assignedRoomId ?? undefined
26
28
  }));
27
29
  };
28
30
  this.ws.onmessage = (event) => {
@@ -41,6 +43,9 @@ class RelayDisplayHost {
41
43
  };
42
44
  this.runtime.setTransport(transport);
43
45
  }
46
+ get roomCode() {
47
+ return this.assignedRoomId;
48
+ }
44
49
  getState = () => this.runtime.getState();
45
50
  subscribe = (listener) => this.runtime.subscribe(listener);
46
51
  dispatch = (action) => this.runtime.dispatch(action);
@@ -52,7 +57,7 @@ class RelayDisplayHost {
52
57
  sendEnvelope(message, to) {
53
58
  const envelope = {
54
59
  type: RelayMessageTypes.DATA,
55
- roomId: this.roomId,
60
+ roomId: this.assignedRoomId ?? undefined,
56
61
  data: JSON.stringify(message)
57
62
  };
58
63
  if (to !== undefined)
@@ -83,6 +88,10 @@ class RelayDisplayHost {
83
88
  this.runtime.handleMessage(msg.from, parsed).catch((err) => this.runtime.handleError(err instanceof Error ? err : new Error(String(err))));
84
89
  break;
85
90
  }
91
+ case RelayMessageTypes.ROOM_CREATED:
92
+ this.assignedRoomId = msg.roomId;
93
+ this.onRoomCode?.(msg.roomId);
94
+ break;
86
95
  case RelayMessageTypes.ERROR:
87
96
  this.runtime.handleError(new Error(msg.message));
88
97
  break;
@@ -10,8 +10,23 @@ import type { IGameState, IAction } from "@couch-kit/core";
10
10
  export interface RelayDisplayHostOptions<S extends IGameState, A extends IAction> extends GameHostRuntimeConfig<S, A> {
11
11
  /** WebSocket URL of the shared relay server. */
12
12
  url: string;
13
- /** Room code phones will use to reach this display. */
14
- roomId: string;
13
+ /**
14
+ * Room code phones will use to reach this display.
15
+ *
16
+ * Omit it — the normal case — and the relay allocates one, reporting it via
17
+ * {@link RelayDisplayHostOptions.onRoomCode} and {@link RelayDisplayHost.roomCode}.
18
+ * Only the relay can tell whether a code is already in use, so a code chosen
19
+ * here may be rejected as `ROOM_EXISTS`; supply one only when something
20
+ * outside the relay already fixed it.
21
+ */
22
+ roomId?: string;
23
+ /**
24
+ * Called once the room exists and its code is known.
25
+ *
26
+ * A minted code is not available synchronously, so a display renders a
27
+ * placeholder until this fires — roughly a round trip to the relay.
28
+ */
29
+ onRoomCode?: (roomCode: string) => void;
15
30
  }
16
31
  /**
17
32
  * Browser **display host** for the cross-network relay transport.
@@ -33,10 +48,18 @@ export interface RelayDisplayHostOptions<S extends IGameState, A extends IAction
33
48
  export declare class RelayDisplayHost<S extends IGameState, A extends IAction> {
34
49
  private readonly runtime;
35
50
  private readonly ws;
36
- private readonly roomId;
51
+ /** Null until the relay confirms the room, when the code is relay-assigned. */
52
+ private assignedRoomId;
53
+ private readonly onRoomCode?;
37
54
  /** Connected phone connection ids (relay peer ids). */
38
55
  private readonly peers;
39
56
  constructor(options: RelayDisplayHostOptions<S, A>);
57
+ /**
58
+ * The room code phones join with, or `null` before the relay has assigned
59
+ * one. See {@link RelayDisplayHostOptions.onRoomCode} to be told when it
60
+ * arrives.
61
+ */
62
+ get roomCode(): string | null;
40
63
  /** Current authoritative game state. */
41
64
  getState: () => S;
42
65
  /** Subscribe to state changes (for `useSyncExternalStore` or manual render). */
@@ -1 +1 @@
1
- {"version":3,"file":"relay-display-host.d.ts","sourceRoot":"","sources":["../src/relay-display-host.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,qBAAqB,EAE3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAe,MAAM,iBAAiB,CAAC;AAOxE;;;;;;GAMG;AACH,MAAM,WAAW,uBAAuB,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,CAC9E,SAAQ,qBAAqB,CAAC,CAAC,EAAE,CAAC,CAAC;IACnC,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,gBAAgB,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO;IACnE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAY;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,uDAAuD;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAE3C,YAAY,OAAO,EAAE,uBAAuB,CAAC,CAAC,EAAE,CAAC,CAAC,EAoCjD;IAED,wCAAwC;IACxC,QAAQ,QAAO,CAAC,CAA4B;IAE5C,gFAAgF;IAChF,SAAS,aAAc,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CACb;IAEnC,8CAA8C;IAC9C,QAAQ,WAAY,CAAC,KAAG,IAAI,CAAkC;IAE9D,8CAA8C;IAC9C,IAAI,IAAI,IAAI,CAIX;IAED,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,kBAAkB;CAqC3B"}
1
+ {"version":3,"file":"relay-display-host.d.ts","sourceRoot":"","sources":["../src/relay-display-host.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,qBAAqB,EAE3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAe,MAAM,iBAAiB,CAAC;AAOxE;;;;;;GAMG;AACH,MAAM,WAAW,uBAAuB,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,CAC9E,SAAQ,qBAAqB,CAAC,CAAC,EAAE,CAAC,CAAC;IACnC,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,gBAAgB,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO;IACnE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAY;IAC/B,+EAA+E;IAC/E,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAA6B;IACzD,uDAAuD;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAE3C,YAAY,OAAO,EAAE,uBAAuB,CAAC,CAAC,EAAE,CAAC,CAAC,EAwCjD;IAED;;;;OAIG;IACH,IAAI,QAAQ,IAAI,MAAM,GAAG,IAAI,CAE5B;IAED,wCAAwC;IACxC,QAAQ,QAAO,CAAC,CAA4B;IAE5C,gFAAgF;IAChF,SAAS,aAAc,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CACb;IAEnC,8CAA8C;IAC9C,QAAQ,WAAY,CAAC,KAAG,IAAI,CAAkC;IAE9D,8CAA8C;IAC9C,IAAI,IAAI,IAAI,CAIX;IAED,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,kBAAkB;CA2C3B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@couch-kit/display",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": true
@@ -51,9 +51,9 @@
51
51
  "clean": "rm -rf dist lib"
52
52
  },
53
53
  "dependencies": {
54
- "@couch-kit/client": "0.11.0",
54
+ "@couch-kit/client": "0.13.0",
55
55
  "@couch-kit/core": "0.9.3",
56
- "@couch-kit/runtime": "0.1.0"
56
+ "@couch-kit/runtime": "0.2.0"
57
57
  },
58
58
  "devDependencies": {
59
59
  "typescript": "^7.0.0"
@@ -23,8 +23,23 @@ export interface RelayDisplayHostOptions<S extends IGameState, A extends IAction
23
23
  extends GameHostRuntimeConfig<S, A> {
24
24
  /** WebSocket URL of the shared relay server. */
25
25
  url: string;
26
- /** Room code phones will use to reach this display. */
27
- roomId: string;
26
+ /**
27
+ * Room code phones will use to reach this display.
28
+ *
29
+ * Omit it — the normal case — and the relay allocates one, reporting it via
30
+ * {@link RelayDisplayHostOptions.onRoomCode} and {@link RelayDisplayHost.roomCode}.
31
+ * Only the relay can tell whether a code is already in use, so a code chosen
32
+ * here may be rejected as `ROOM_EXISTS`; supply one only when something
33
+ * outside the relay already fixed it.
34
+ */
35
+ roomId?: string;
36
+ /**
37
+ * Called once the room exists and its code is known.
38
+ *
39
+ * A minted code is not available synchronously, so a display renders a
40
+ * placeholder until this fires — roughly a round trip to the relay.
41
+ */
42
+ onRoomCode?: (roomCode: string) => void;
28
43
  }
29
44
 
30
45
  /**
@@ -47,21 +62,27 @@ export interface RelayDisplayHostOptions<S extends IGameState, A extends IAction
47
62
  export class RelayDisplayHost<S extends IGameState, A extends IAction> {
48
63
  private readonly runtime: GameHostRuntime<S, A>;
49
64
  private readonly ws: WebSocket;
50
- private readonly roomId: string;
65
+ /** Null until the relay confirms the room, when the code is relay-assigned. */
66
+ private assignedRoomId: string | null;
67
+ private readonly onRoomCode?: (roomCode: string) => void;
51
68
  /** Connected phone connection ids (relay peer ids). */
52
69
  private readonly peers = new Set<string>();
53
70
 
54
71
  constructor(options: RelayDisplayHostOptions<S, A>) {
55
- const { url, roomId, ...runtimeConfig } = options;
56
- this.roomId = roomId;
72
+ const { url, roomId, onRoomCode, ...runtimeConfig } = options;
73
+ this.assignedRoomId = roomId ?? null;
74
+ this.onRoomCode = onRoomCode;
57
75
  this.runtime = new GameHostRuntime<S, A>(runtimeConfig);
58
76
  this.ws = new WebSocket(relayRoomUrl(url, roomId));
59
77
 
60
78
  this.ws.onopen = () => {
79
+ // No roomId asks the relay to allocate one. Sending the field as
80
+ // undefined omits it from the JSON, which is what the relay reads as
81
+ // "you pick".
61
82
  this.ws.send(
62
83
  JSON.stringify({
63
84
  type: RelayMessageTypes.CREATE_ROOM,
64
- roomId: this.roomId,
85
+ roomId: this.assignedRoomId ?? undefined,
65
86
  }),
66
87
  );
67
88
  };
@@ -89,6 +110,15 @@ export class RelayDisplayHost<S extends IGameState, A extends IAction> {
89
110
  this.runtime.setTransport(transport);
90
111
  }
91
112
 
113
+ /**
114
+ * The room code phones join with, or `null` before the relay has assigned
115
+ * one. See {@link RelayDisplayHostOptions.onRoomCode} to be told when it
116
+ * arrives.
117
+ */
118
+ get roomCode(): string | null {
119
+ return this.assignedRoomId;
120
+ }
121
+
92
122
  /** Current authoritative game state. */
93
123
  getState = (): S => this.runtime.getState();
94
124
 
@@ -109,7 +139,10 @@ export class RelayDisplayHost<S extends IGameState, A extends IAction> {
109
139
  private sendEnvelope(message: HostMessage, to?: string): void {
110
140
  const envelope: Record<string, unknown> = {
111
141
  type: RelayMessageTypes.DATA,
112
- roomId: this.roomId,
142
+ // The relay routes by the sender's membership, not this field, so it is
143
+ // only ever informational — and nothing is sent before a peer joins,
144
+ // which cannot happen until the room exists.
145
+ roomId: this.assignedRoomId ?? undefined,
113
146
  data: JSON.stringify(message),
114
147
  };
115
148
  if (to !== undefined) envelope.to = to;
@@ -147,10 +180,16 @@ export class RelayDisplayHost<S extends IGameState, A extends IAction> {
147
180
  );
148
181
  break;
149
182
  }
183
+ case RelayMessageTypes.ROOM_CREATED:
184
+ // Carries the code when the relay chose it, and confirms the code when
185
+ // the caller supplied one.
186
+ this.assignedRoomId = msg.roomId;
187
+ this.onRoomCode?.(msg.roomId);
188
+ break;
150
189
  case RelayMessageTypes.ERROR:
151
190
  this.runtime.handleError(new Error(msg.message));
152
191
  break;
153
- // ROOM_CREATED / ROOM_JOINED are acknowledgements; no action needed.
192
+ // ROOM_JOINED is an acknowledgement; no action needed.
154
193
  }
155
194
  }
156
195
  }