@couch-kit/runtime 0.0.0 → 0.2.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 +41 -0
- package/dist/index.cjs +16 -3
- package/dist/index.js +16 -3
- package/lib/broadcast-scheduler.d.ts.map +1 -1
- package/lib/rate-limiter.d.ts.map +1 -1
- package/lib/runtime.d.ts +25 -0
- package/lib/runtime.d.ts.map +1 -1
- package/lib/session-manager.d.ts.map +1 -1
- package/package.json +3 -4
- package/src/runtime.ts +48 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @couch-kit/runtime
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#155](https://github.com/faluciano/react-native-couch-kit/pull/155) [`4f297a1`](https://github.com/faluciano/react-native-couch-kit/commit/4f297a19c442541703c3bee7bee26354ae3476a4) Thanks [@faluciano](https://github.com/faluciano)! - Hidden information can now be hidden for real: `GameHostRuntimeConfig` takes an
|
|
8
|
+
optional `project(state, playerId)` that narrows the authoritative state to what
|
|
9
|
+
one player may see.
|
|
10
|
+
|
|
11
|
+
Without it the runtime broadcasts the same state to everyone, so hiding a hand
|
|
12
|
+
depends on the client choosing not to look — any player could read opponents'
|
|
13
|
+
cards from devtools. With it, the data never reaches their device: the runtime
|
|
14
|
+
sends each connection its own projection, in `WELCOME`, `RECONNECTED`, and every
|
|
15
|
+
state update.
|
|
16
|
+
|
|
17
|
+
Games with no hidden information omit `project` and are unchanged — state is
|
|
18
|
+
still broadcast in one frame.
|
|
19
|
+
|
|
20
|
+
Because a projected client holds a _view_ rather than the whole state, it cannot
|
|
21
|
+
run the game reducer, so `ClientConfig.reducer` is now optional. Omit it and the
|
|
22
|
+
client renders what the host sends (no optimistic updates); a round trip is
|
|
23
|
+
imperceptible for turn-based games, and it is what makes the guarantee real.
|
|
24
|
+
|
|
25
|
+
## 0.1.0
|
|
26
|
+
|
|
27
|
+
### Minor Changes
|
|
28
|
+
|
|
29
|
+
- [#122](https://github.com/faluciano/react-native-couch-kit/pull/122) [`ec8a0ae`](https://github.com/faluciano/react-native-couch-kit/commit/ec8a0ae2626fce135d553e88eca1f0ad59d54e2d) Thanks [@faluciano](https://github.com/faluciano)! - Extract the authoritative game state, sessions, authorization, rate limiting,
|
|
30
|
+
and broadcast scheduling into a transport-neutral runtime package. The React
|
|
31
|
+
Native host now adapts its existing HTTP and WebSocket servers to that runtime
|
|
32
|
+
without changing the public `GameHostProvider` API.
|
|
33
|
+
|
|
34
|
+
The core ESM package metadata and declaration imports are also made compatible
|
|
35
|
+
with Node's ESM and `NodeNext` resolution so the runtime can be consumed from
|
|
36
|
+
supported Node versions.
|
|
37
|
+
|
|
38
|
+
### Patch Changes
|
|
39
|
+
|
|
40
|
+
- Updated dependencies [[`ec8a0ae`](https://github.com/faluciano/react-native-couch-kit/commit/ec8a0ae2626fce135d553e88eca1f0ad59d54e2d)]:
|
|
41
|
+
- @couch-kit/core@0.9.3
|
package/dist/index.cjs
CHANGED
|
@@ -434,7 +434,7 @@ class GameHostRuntime {
|
|
|
434
434
|
type: import_core5.MessageTypes.RECONNECTED,
|
|
435
435
|
payload: {
|
|
436
436
|
playerId,
|
|
437
|
-
state: this.
|
|
437
|
+
state: this.viewFor(playerId)
|
|
438
438
|
}
|
|
439
439
|
});
|
|
440
440
|
} else {
|
|
@@ -442,7 +442,7 @@ class GameHostRuntime {
|
|
|
442
442
|
type: import_core5.MessageTypes.WELCOME,
|
|
443
443
|
payload: {
|
|
444
444
|
playerId,
|
|
445
|
-
state: this.
|
|
445
|
+
state: this.viewFor(playerId),
|
|
446
446
|
serverTime: Date.now()
|
|
447
447
|
}
|
|
448
448
|
});
|
|
@@ -560,13 +560,26 @@ class GameHostRuntime {
|
|
|
560
560
|
}
|
|
561
561
|
this.broadcastScheduler.schedule(this.broadcastState);
|
|
562
562
|
}
|
|
563
|
+
viewFor(playerId) {
|
|
564
|
+
const project = this.config.project;
|
|
565
|
+
return project ? project(this.state, playerId) : this.state;
|
|
566
|
+
}
|
|
563
567
|
broadcastState = () => {
|
|
564
568
|
if (!this.transport)
|
|
565
569
|
return;
|
|
566
570
|
const actions = this.actionQueue;
|
|
567
571
|
this.actionQueue = [];
|
|
568
572
|
this.stateDirty = false;
|
|
569
|
-
this.
|
|
573
|
+
if (!this.config.project) {
|
|
574
|
+
this.transport.broadcast(createStateUpdateMessage(this.state, actions));
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
for (const connectionId of this.joinedConnections) {
|
|
578
|
+
const playerId = this.sessionManager.getPlayerIdForSocket(connectionId);
|
|
579
|
+
if (!playerId)
|
|
580
|
+
continue;
|
|
581
|
+
this.send(connectionId, createStateUpdateMessage(this.viewFor(playerId), actions));
|
|
582
|
+
}
|
|
570
583
|
};
|
|
571
584
|
send(connectionId, message) {
|
|
572
585
|
this.transport?.send(connectionId, message);
|
package/dist/index.js
CHANGED
|
@@ -389,7 +389,7 @@ class GameHostRuntime {
|
|
|
389
389
|
type: MessageTypes3.RECONNECTED,
|
|
390
390
|
payload: {
|
|
391
391
|
playerId,
|
|
392
|
-
state: this.
|
|
392
|
+
state: this.viewFor(playerId)
|
|
393
393
|
}
|
|
394
394
|
});
|
|
395
395
|
} else {
|
|
@@ -397,7 +397,7 @@ class GameHostRuntime {
|
|
|
397
397
|
type: MessageTypes3.WELCOME,
|
|
398
398
|
payload: {
|
|
399
399
|
playerId,
|
|
400
|
-
state: this.
|
|
400
|
+
state: this.viewFor(playerId),
|
|
401
401
|
serverTime: Date.now()
|
|
402
402
|
}
|
|
403
403
|
});
|
|
@@ -515,13 +515,26 @@ class GameHostRuntime {
|
|
|
515
515
|
}
|
|
516
516
|
this.broadcastScheduler.schedule(this.broadcastState);
|
|
517
517
|
}
|
|
518
|
+
viewFor(playerId) {
|
|
519
|
+
const project = this.config.project;
|
|
520
|
+
return project ? project(this.state, playerId) : this.state;
|
|
521
|
+
}
|
|
518
522
|
broadcastState = () => {
|
|
519
523
|
if (!this.transport)
|
|
520
524
|
return;
|
|
521
525
|
const actions = this.actionQueue;
|
|
522
526
|
this.actionQueue = [];
|
|
523
527
|
this.stateDirty = false;
|
|
524
|
-
this.
|
|
528
|
+
if (!this.config.project) {
|
|
529
|
+
this.transport.broadcast(createStateUpdateMessage(this.state, actions));
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
for (const connectionId of this.joinedConnections) {
|
|
533
|
+
const playerId = this.sessionManager.getPlayerIdForSocket(connectionId);
|
|
534
|
+
if (!playerId)
|
|
535
|
+
continue;
|
|
536
|
+
this.send(connectionId, createStateUpdateMessage(this.viewFor(playerId), actions));
|
|
537
|
+
}
|
|
525
538
|
};
|
|
526
539
|
send(connectionId, message) {
|
|
527
540
|
this.transport?.send(connectionId, message);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broadcast-scheduler.d.ts","sourceRoot":"","sources":["../src/broadcast-scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEjE,iDAAiD;AACjD,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C,MAAM,MAAM,kBAAkB,GAAG,OAAO,CACtC,WAAW,EACX;IAAE,IAAI,EAAE,OAAO,YAAY,CAAC,YAAY,CAAA;CAAE,CAC3C,CAAC;AAEF,MAAM,WAAW,cAAc,CAAC,MAAM;IACpC,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1D,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAOD,MAAM,WAAW,yBAAyB,CAAC,MAAM;IAC/C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;CACpC;AAED;;;GAGG;AACH,qBAAa,kBAAkB,CAAC,MAAM,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC;IACpE,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD,OAAO,CAAC,KAAK,CAAuB;
|
|
1
|
+
{"version":3,"file":"broadcast-scheduler.d.ts","sourceRoot":"","sources":["../src/broadcast-scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEjE,iDAAiD;AACjD,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C,MAAM,MAAM,kBAAkB,GAAG,OAAO,CACtC,WAAW,EACX;IAAE,IAAI,EAAE,OAAO,YAAY,CAAC,YAAY,CAAA;CAAE,CAC3C,CAAC;AAEF,MAAM,WAAW,cAAc,CAAC,MAAM;IACpC,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1D,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAOD,MAAM,WAAW,yBAAyB,CAAC,MAAM;IAC/C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;CACpC;AAED;;;GAGG;AACH,qBAAa,kBAAkB,CAAC,MAAM,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC;IACpE,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD,OAAO,CAAC,KAAK,CAAuB;IAEpC,YAAY,OAAO,GAAE,yBAAyB,CAAC,MAAM,CAAM,EAK1D;IAED,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAMnC;IAED,MAAM,IAAI,IAAI,CAKb;IAED,kBAAkB,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAEhD;IAED,mBAAmB,IAAI,OAAO,CAE7B;CACF;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,SAAS,OAAO,EAAE,EAC3B,SAAS,GAAE,MAAmB,GAC7B,kBAAkB,CAapB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,uCAAuC;AACvC,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,uCAAuC;AACvC,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;IAE3D,YAAY,OAAO,GAAE,wBAA6B,EAIjD;IAED,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAexC;IAED,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE5B;IAED,KAAK,IAAI,IAAI,CAEZ;IAED,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAE/C;CACF"}
|
package/lib/runtime.d.ts
CHANGED
|
@@ -8,6 +8,26 @@ export interface GameRuntimeTransport {
|
|
|
8
8
|
export interface GameHostRuntimeConfig<S extends IGameState, A extends IAction> {
|
|
9
9
|
initialState: S;
|
|
10
10
|
reducer: GameReducer<S, A>;
|
|
11
|
+
/**
|
|
12
|
+
* Narrows the authoritative state to what one player is allowed to see.
|
|
13
|
+
*
|
|
14
|
+
* Without it, every player receives the whole state and any hiding is left to
|
|
15
|
+
* the client — which makes hidden information a convention rather than a
|
|
16
|
+
* rule. With it, the data a player must not see never reaches their device:
|
|
17
|
+
* the runtime sends each connection its own projection instead of one shared
|
|
18
|
+
* broadcast.
|
|
19
|
+
*
|
|
20
|
+
* Omit for games with no hidden information; state is broadcast as before.
|
|
21
|
+
*
|
|
22
|
+
* The projection is what clients receive, so their state type is the *view*,
|
|
23
|
+
* not `S`. A client cannot run the game reducer over a partial view, so
|
|
24
|
+
* `useGameClient` must be used without a `reducer` (no optimistic updates)
|
|
25
|
+
* when a projection is configured.
|
|
26
|
+
*
|
|
27
|
+
* @param state - Current authoritative state.
|
|
28
|
+
* @param playerId - Player the projection is for.
|
|
29
|
+
*/
|
|
30
|
+
project?: (state: S, playerId: string) => unknown;
|
|
11
31
|
debug?: boolean;
|
|
12
32
|
/** Timeout before a disconnected player is permanently removed. */
|
|
13
33
|
disconnectTimeout?: number;
|
|
@@ -59,6 +79,11 @@ export declare class GameHostRuntime<S extends IGameState, A extends IAction> {
|
|
|
59
79
|
/** Cancels runtime timers and releases transport-specific state. */
|
|
60
80
|
stop(): void;
|
|
61
81
|
private applyAction;
|
|
82
|
+
/**
|
|
83
|
+
* What `playerId` is allowed to see. Identity unless a projection is
|
|
84
|
+
* configured, so games without hidden information are unaffected.
|
|
85
|
+
*/
|
|
86
|
+
private viewFor;
|
|
62
87
|
private readonly broadcastState;
|
|
63
88
|
private send;
|
|
64
89
|
private invokeLifecycleCallback;
|
package/lib/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,OAAO,EACZ,KAAK,UAAU,EAEhB,MAAM,iBAAiB,CAAC;AAczB,8EAA8E;AAC9E,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IACvD,SAAS,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;CACvC;AAED,4EAA4E;AAC5E,MAAM,WAAW,qBAAqB,CACpC,CAAC,SAAS,UAAU,EACpB,CAAC,SAAS,OAAO;IAEjB,YAAY,EAAE,CAAC,CAAC;IAChB,OAAO,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mEAAmE;IACnE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,sDAAsD;IACtD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED;;;GAGG;AACH,qBAAa,eAAe,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO;IAClE,OAAO,CAAC,MAAM,CAA8B;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwC;IAChE,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,SAAS,CAA8B;IAC/C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA2B;IACvD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAqB;IACxD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA8B;IAC3D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA6B;IAC/D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAqB;IACvD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,WAAW,CAAiB;IACpC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,wBAAwB,CAAK;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,UAAU,EACf,KAAK,cAAc,EACpB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,qBAAqB,CAAC,MAAM;IAC3C,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1D,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AASD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,yBAAyB,CAAC,MAAM;IAC/C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oBAAoB,CAAC,EAAE,MAAM,MAAM,CAAC;IACpC,SAAS,CAAC,EAAE,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC1C,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;CACnD;AAED,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,UAAU,IAAI;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,UAAU,IACpD;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;CAC3B,CAAC;AAEN,KAAK,aAAa,CAAC,CAAC,SAAS,UAAU,IAAI,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;AAE/E;;;GAGG;AACH,qBAAa,kBAAkB,CAAC,MAAM,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC;IACpE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;IACxD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAC3D,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA6B;IAChE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;IAC1D,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAe;IACpD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsC;IACvE,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAA6B;
|
|
1
|
+
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@couch-kit/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"provenance": true
|
|
@@ -52,14 +52,13 @@
|
|
|
52
52
|
"build": "bun build ./src/index.ts --outdir ./dist --target browser --external @couch-kit/core && bun build ./src/index.ts --outfile ./dist/index.cjs --target browser --format cjs --external @couch-kit/core && tsc -p tsconfig.build.json",
|
|
53
53
|
"prepublishOnly": "bun run build",
|
|
54
54
|
"test": "bun test",
|
|
55
|
-
"lint": "eslint src/",
|
|
56
55
|
"typecheck": "tsc --noEmit",
|
|
57
56
|
"clean": "rm -rf dist lib"
|
|
58
57
|
},
|
|
59
58
|
"dependencies": {
|
|
60
|
-
"@couch-kit/core": "0.9.
|
|
59
|
+
"@couch-kit/core": "0.9.3"
|
|
61
60
|
},
|
|
62
61
|
"devDependencies": {
|
|
63
|
-
"typescript": "^
|
|
62
|
+
"typescript": "^7.0.0"
|
|
64
63
|
}
|
|
65
64
|
}
|
package/src/runtime.ts
CHANGED
|
@@ -36,6 +36,26 @@ export interface GameHostRuntimeConfig<
|
|
|
36
36
|
> {
|
|
37
37
|
initialState: S;
|
|
38
38
|
reducer: GameReducer<S, A>;
|
|
39
|
+
/**
|
|
40
|
+
* Narrows the authoritative state to what one player is allowed to see.
|
|
41
|
+
*
|
|
42
|
+
* Without it, every player receives the whole state and any hiding is left to
|
|
43
|
+
* the client — which makes hidden information a convention rather than a
|
|
44
|
+
* rule. With it, the data a player must not see never reaches their device:
|
|
45
|
+
* the runtime sends each connection its own projection instead of one shared
|
|
46
|
+
* broadcast.
|
|
47
|
+
*
|
|
48
|
+
* Omit for games with no hidden information; state is broadcast as before.
|
|
49
|
+
*
|
|
50
|
+
* The projection is what clients receive, so their state type is the *view*,
|
|
51
|
+
* not `S`. A client cannot run the game reducer over a partial view, so
|
|
52
|
+
* `useGameClient` must be used without a `reducer` (no optimistic updates)
|
|
53
|
+
* when a projection is configured.
|
|
54
|
+
*
|
|
55
|
+
* @param state - Current authoritative state.
|
|
56
|
+
* @param playerId - Player the projection is for.
|
|
57
|
+
*/
|
|
58
|
+
project?: (state: S, playerId: string) => unknown;
|
|
39
59
|
debug?: boolean;
|
|
40
60
|
/** Timeout before a disconnected player is permanently removed. */
|
|
41
61
|
disconnectTimeout?: number;
|
|
@@ -215,7 +235,7 @@ export class GameHostRuntime<S extends IGameState, A extends IAction> {
|
|
|
215
235
|
type: MessageTypes.RECONNECTED,
|
|
216
236
|
payload: {
|
|
217
237
|
playerId,
|
|
218
|
-
state: this.
|
|
238
|
+
state: this.viewFor(playerId),
|
|
219
239
|
},
|
|
220
240
|
});
|
|
221
241
|
} else {
|
|
@@ -223,7 +243,7 @@ export class GameHostRuntime<S extends IGameState, A extends IAction> {
|
|
|
223
243
|
type: MessageTypes.WELCOME,
|
|
224
244
|
payload: {
|
|
225
245
|
playerId,
|
|
226
|
-
state: this.
|
|
246
|
+
state: this.viewFor(playerId),
|
|
227
247
|
serverTime: Date.now(),
|
|
228
248
|
},
|
|
229
249
|
});
|
|
@@ -374,13 +394,38 @@ export class GameHostRuntime<S extends IGameState, A extends IAction> {
|
|
|
374
394
|
this.broadcastScheduler.schedule(this.broadcastState);
|
|
375
395
|
}
|
|
376
396
|
|
|
397
|
+
/**
|
|
398
|
+
* What `playerId` is allowed to see. Identity unless a projection is
|
|
399
|
+
* configured, so games without hidden information are unaffected.
|
|
400
|
+
*/
|
|
401
|
+
private viewFor(playerId: string): unknown {
|
|
402
|
+
const project = this.config.project;
|
|
403
|
+
return project ? project(this.state, playerId) : this.state;
|
|
404
|
+
}
|
|
405
|
+
|
|
377
406
|
private readonly broadcastState = (): void => {
|
|
378
407
|
if (!this.transport) return;
|
|
379
408
|
|
|
380
409
|
const actions = this.actionQueue;
|
|
381
410
|
this.actionQueue = [];
|
|
382
411
|
this.stateDirty = false;
|
|
383
|
-
|
|
412
|
+
|
|
413
|
+
if (!this.config.project) {
|
|
414
|
+
this.transport.broadcast(createStateUpdateMessage(this.state, actions));
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Projected games get one message per connection rather than a broadcast:
|
|
419
|
+
// a shared frame cannot carry different views. Costs N sends instead of 1,
|
|
420
|
+
// which is bounded by players-per-room.
|
|
421
|
+
for (const connectionId of this.joinedConnections) {
|
|
422
|
+
const playerId = this.sessionManager.getPlayerIdForSocket(connectionId);
|
|
423
|
+
if (!playerId) continue;
|
|
424
|
+
this.send(
|
|
425
|
+
connectionId,
|
|
426
|
+
createStateUpdateMessage(this.viewFor(playerId), actions),
|
|
427
|
+
);
|
|
428
|
+
}
|
|
384
429
|
};
|
|
385
430
|
|
|
386
431
|
private send(connectionId: string, message: HostMessage): void {
|