@mnbroatch/boardgame.io 0.0.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/LICENSE +21 -0
- package/README.md +102 -0
- package/ai/package.json +7 -0
- package/client/package.json +7 -0
- package/core/package.json +7 -0
- package/debug/package.json +7 -0
- package/dist/boardgameio.es.js +14238 -0
- package/dist/boardgameio.js +14277 -0
- package/dist/boardgameio.min.js +16 -0
- package/dist/cjs/Debug-9d141c06.js +9586 -0
- package/dist/cjs/ai-e0e8a768.js +377 -0
- package/dist/cjs/ai.js +20 -0
- package/dist/cjs/client-76dec77b.js +258 -0
- package/dist/cjs/client-a22d7500.js +524 -0
- package/dist/cjs/client.js +26 -0
- package/dist/cjs/core.js +52 -0
- package/dist/cjs/debug.js +18 -0
- package/dist/cjs/filter-player-view-bb02e2f6.js +89 -0
- package/dist/cjs/initialize-267fcd69.js +61 -0
- package/dist/cjs/internal.js +25 -0
- package/dist/cjs/master-2904879d.js +320 -0
- package/dist/cjs/master.js +18 -0
- package/dist/cjs/multiplayer.js +23 -0
- package/dist/cjs/plugin-random-7425844d.js +229 -0
- package/dist/cjs/plugins.js +59 -0
- package/dist/cjs/react-native.js +182 -0
- package/dist/cjs/react.js +727 -0
- package/dist/cjs/reducer-16eec232.js +1203 -0
- package/dist/cjs/server.js +4087 -0
- package/dist/cjs/socketio-7a0837eb.js +478 -0
- package/dist/cjs/testing.js +30 -0
- package/dist/cjs/transport-b1874dfa.js +37 -0
- package/dist/cjs/turn-order-b2ff8740.js +1136 -0
- package/dist/cjs/util-fcfd8fb8.js +140 -0
- package/dist/esm/Debug-0141fe2d.js +9577 -0
- package/dist/esm/ai-5c06e761.js +371 -0
- package/dist/esm/ai.js +8 -0
- package/dist/esm/client-2e653027.js +522 -0
- package/dist/esm/client-5f57c3f2.js +255 -0
- package/dist/esm/client.js +16 -0
- package/dist/esm/core.js +40 -0
- package/dist/esm/debug.js +10 -0
- package/dist/esm/filter-player-view-2c6cc96f.js +87 -0
- package/dist/esm/initialize-11d626ca.js +59 -0
- package/dist/esm/internal.js +10 -0
- package/dist/esm/master-fa8f2e43.js +318 -0
- package/dist/esm/master.js +10 -0
- package/dist/esm/multiplayer.js +14 -0
- package/dist/esm/plugin-random-087f861e.js +226 -0
- package/dist/esm/plugins.js +55 -0
- package/dist/esm/react-native.js +173 -0
- package/dist/esm/react.js +716 -0
- package/dist/esm/reducer-c46da7e5.js +1198 -0
- package/dist/esm/socketio-c22ffa65.js +455 -0
- package/dist/esm/testing.js +26 -0
- package/dist/esm/transport-ce07b771.js +35 -0
- package/dist/esm/turn-order-376d315e.js +1091 -0
- package/dist/esm/util-b6147cef.js +135 -0
- package/dist/types/packages/ai.d.ts +5 -0
- package/dist/types/packages/client.d.ts +3 -0
- package/dist/types/packages/core.d.ts +5 -0
- package/dist/types/packages/debug.d.ts +2 -0
- package/dist/types/packages/internal.d.ts +8 -0
- package/dist/types/packages/master.d.ts +2 -0
- package/dist/types/packages/multiplayer.d.ts +3 -0
- package/dist/types/packages/plugins.d.ts +3 -0
- package/dist/types/packages/react-native.d.ts +2 -0
- package/dist/types/packages/react.d.ts +3 -0
- package/dist/types/packages/server.d.ts +6 -0
- package/dist/types/packages/testing.d.ts +1 -0
- package/dist/types/src/ai/ai.d.ts +53 -0
- package/dist/types/src/ai/ai.test.d.ts +1 -0
- package/dist/types/src/ai/bot.d.ts +40 -0
- package/dist/types/src/ai/mcts-bot.d.ts +60 -0
- package/dist/types/src/ai/random-bot.d.ts +27 -0
- package/dist/types/src/client/client.d.ts +104 -0
- package/dist/types/src/client/client.test.d.ts +1 -0
- package/dist/types/src/client/debug/tests/debug.test.d.ts +1 -0
- package/dist/types/src/client/manager.d.ts +61 -0
- package/dist/types/src/client/react.d.ts +75 -0
- package/dist/types/src/client/react.ssr.test.d.ts +4 -0
- package/dist/types/src/client/react.test.d.ts +1 -0
- package/dist/types/src/client/transport/dummy.d.ts +18 -0
- package/dist/types/src/client/transport/local.d.ts +59 -0
- package/dist/types/src/client/transport/local.test.d.ts +1 -0
- package/dist/types/src/client/transport/socketio.d.ts +45 -0
- package/dist/types/src/client/transport/socketio.test.d.ts +1 -0
- package/dist/types/src/client/transport/transport.d.ts +50 -0
- package/dist/types/src/client/transport/transport.test.d.ts +1 -0
- package/dist/types/src/core/action-creators.d.ts +144 -0
- package/dist/types/src/core/action-types.d.ts +10 -0
- package/dist/types/src/core/backwards-compatibility.d.ts +12 -0
- package/dist/types/src/core/constants.d.ts +6 -0
- package/dist/types/src/core/errors.d.ts +15 -0
- package/dist/types/src/core/flow.d.ts +28 -0
- package/dist/types/src/core/flow.test.d.ts +1 -0
- package/dist/types/src/core/game-methods.d.ts +9 -0
- package/dist/types/src/core/game.d.ts +26 -0
- package/dist/types/src/core/game.test.d.ts +1 -0
- package/dist/types/src/core/initialize.d.ts +9 -0
- package/dist/types/src/core/logger.d.ts +2 -0
- package/dist/types/src/core/player-view.d.ts +7 -0
- package/dist/types/src/core/player-view.test.d.ts +1 -0
- package/dist/types/src/core/reducer.d.ts +155 -0
- package/dist/types/src/core/reducer.test.d.ts +1 -0
- package/dist/types/src/core/turn-order.d.ts +179 -0
- package/dist/types/src/core/turn-order.test.d.ts +8 -0
- package/dist/types/src/lobby/client.d.ts +194 -0
- package/dist/types/src/lobby/client.test.d.ts +1 -0
- package/dist/types/src/lobby/connection.d.ts +44 -0
- package/dist/types/src/lobby/connection.test.d.ts +1 -0
- package/dist/types/src/lobby/create-match-form.d.ts +26 -0
- package/dist/types/src/lobby/login-form.d.ts +23 -0
- package/dist/types/src/lobby/match-instance.d.ts +31 -0
- package/dist/types/src/lobby/react.d.ts +113 -0
- package/dist/types/src/lobby/react.ssr.test.d.ts +4 -0
- package/dist/types/src/lobby/react.test.d.ts +1 -0
- package/dist/types/src/master/filter-player-view.d.ts +96 -0
- package/dist/types/src/master/filter-player-view.test.d.ts +1 -0
- package/dist/types/src/master/master.d.ts +94 -0
- package/dist/types/src/master/master.test.d.ts +1 -0
- package/dist/types/src/plugins/events/events.d.ts +54 -0
- package/dist/types/src/plugins/events/events.test.d.ts +1 -0
- package/dist/types/src/plugins/main.d.ts +75 -0
- package/dist/types/src/plugins/main.test.d.ts +1 -0
- package/dist/types/src/plugins/plugin-events.d.ts +5 -0
- package/dist/types/src/plugins/plugin-immer.d.ts +7 -0
- package/dist/types/src/plugins/plugin-immer.test.d.ts +1 -0
- package/dist/types/src/plugins/plugin-log.d.ts +14 -0
- package/dist/types/src/plugins/plugin-log.test.d.ts +1 -0
- package/dist/types/src/plugins/plugin-player.d.ts +29 -0
- package/dist/types/src/plugins/plugin-player.test.d.ts +1 -0
- package/dist/types/src/plugins/plugin-random.d.ts +4 -0
- package/dist/types/src/plugins/plugin-serializable.d.ts +7 -0
- package/dist/types/src/plugins/plugin-serializable.test.d.ts +1 -0
- package/dist/types/src/plugins/random/random.alea.d.ts +19 -0
- package/dist/types/src/plugins/random/random.d.ts +54 -0
- package/dist/types/src/plugins/random/random.test.d.ts +1 -0
- package/dist/types/src/server/api.d.ts +13 -0
- package/dist/types/src/server/api.test.d.ts +1 -0
- package/dist/types/src/server/auth.d.ts +38 -0
- package/dist/types/src/server/auth.test.d.ts +1 -0
- package/dist/types/src/server/cors.d.ts +4 -0
- package/dist/types/src/server/cors.test.d.ts +1 -0
- package/dist/types/src/server/db/base.d.ts +192 -0
- package/dist/types/src/server/db/flatfile.d.ts +44 -0
- package/dist/types/src/server/db/flatfile.test.d.ts +1 -0
- package/dist/types/src/server/db/index.d.ts +4 -0
- package/dist/types/src/server/db/index.test.d.ts +1 -0
- package/dist/types/src/server/db/inmemory.d.ts +43 -0
- package/dist/types/src/server/db/inmemory.test.d.ts +1 -0
- package/dist/types/src/server/db/localstorage.d.ts +7 -0
- package/dist/types/src/server/db/localstorage.test.d.ts +1 -0
- package/dist/types/src/server/index.d.ts +68 -0
- package/dist/types/src/server/index.test.d.ts +1 -0
- package/dist/types/src/server/transport/pubsub/generic-pub-sub.d.ts +6 -0
- package/dist/types/src/server/transport/pubsub/in-memory-pub-sub.d.ts +7 -0
- package/dist/types/src/server/transport/pubsub/in-memory-pub-sub.test.d.ts +1 -0
- package/dist/types/src/server/transport/socketio-simultaneous.test.d.ts +1 -0
- package/dist/types/src/server/transport/socketio.d.ts +65 -0
- package/dist/types/src/server/transport/socketio.test.d.ts +1 -0
- package/dist/types/src/server/util.d.ts +35 -0
- package/dist/types/src/testing/mock-random.d.ts +15 -0
- package/dist/types/src/testing/mock-random.test.d.ts +1 -0
- package/dist/types/src/types.d.ts +387 -0
- package/internal/package.json +7 -0
- package/master/package.json +7 -0
- package/multiplayer/package.json +7 -0
- package/package.json +211 -0
- package/plugins/package.json +7 -0
- package/react/package.json +7 -0
- package/react-native/package.json +7 -0
- package/server/package.json +6 -0
- package/src/ai/ai.test.ts +433 -0
- package/src/ai/ai.ts +84 -0
- package/src/ai/bot.ts +122 -0
- package/src/ai/mcts-bot.ts +331 -0
- package/src/ai/random-bot.ts +20 -0
- package/src/client/client.test.ts +993 -0
- package/src/client/client.ts +588 -0
- package/src/client/debug/Debug.svelte +239 -0
- package/src/client/debug/Menu.svelte +65 -0
- package/src/client/debug/ai/AI.svelte +215 -0
- package/src/client/debug/ai/Options.svelte +48 -0
- package/src/client/debug/info/Info.svelte +22 -0
- package/src/client/debug/info/Item.svelte +24 -0
- package/src/client/debug/log/Log.svelte +157 -0
- package/src/client/debug/log/LogEvent.svelte +149 -0
- package/src/client/debug/log/LogMetadata.svelte +7 -0
- package/src/client/debug/log/PhaseMarker.svelte +27 -0
- package/src/client/debug/log/TurnMarker.svelte +23 -0
- package/src/client/debug/main/ClientSwitcher.svelte +59 -0
- package/src/client/debug/main/Controls.svelte +58 -0
- package/src/client/debug/main/Hotkey.svelte +84 -0
- package/src/client/debug/main/InteractiveFunction.svelte +85 -0
- package/src/client/debug/main/Main.svelte +121 -0
- package/src/client/debug/main/Move.svelte +68 -0
- package/src/client/debug/main/PlayerInfo.svelte +70 -0
- package/src/client/debug/mcts/Action.svelte +22 -0
- package/src/client/debug/mcts/MCTS.svelte +78 -0
- package/src/client/debug/mcts/Table.svelte +98 -0
- package/src/client/debug/tests/JSONTree.mock.svelte +3 -0
- package/src/client/debug/tests/debug.test.ts +183 -0
- package/src/client/debug/utils/shortcuts.js +50 -0
- package/src/client/debug/utils/shortcuts.test.js +49 -0
- package/src/client/manager.ts +177 -0
- package/src/client/react-native.js +136 -0
- package/src/client/react-native.test.js +229 -0
- package/src/client/react.ssr.test.tsx +24 -0
- package/src/client/react.test.tsx +213 -0
- package/src/client/react.tsx +192 -0
- package/src/client/transport/dummy.ts +19 -0
- package/src/client/transport/local.test.ts +353 -0
- package/src/client/transport/local.ts +230 -0
- package/src/client/transport/socketio.test.ts +328 -0
- package/src/client/transport/socketio.ts +210 -0
- package/src/client/transport/transport.test.ts +27 -0
- package/src/client/transport/transport.ts +95 -0
- package/src/core/action-creators.ts +159 -0
- package/src/core/action-types.ts +18 -0
- package/src/core/backwards-compatibility.ts +23 -0
- package/src/core/constants.ts +6 -0
- package/src/core/errors.ts +35 -0
- package/src/core/flow.test.ts +2433 -0
- package/src/core/flow.ts +897 -0
- package/src/core/game-methods.ts +9 -0
- package/src/core/game.test.ts +286 -0
- package/src/core/game.ts +114 -0
- package/src/core/initialize.ts +77 -0
- package/src/core/logger.test.js +90 -0
- package/src/core/logger.ts +18 -0
- package/src/core/player-view.test.ts +50 -0
- package/src/core/player-view.ts +39 -0
- package/src/core/reducer.test.ts +991 -0
- package/src/core/reducer.ts +532 -0
- package/src/core/turn-order.test.ts +1123 -0
- package/src/core/turn-order.ts +473 -0
- package/src/lobby/client.test.ts +385 -0
- package/src/lobby/client.ts +358 -0
- package/src/lobby/connection.test.ts +207 -0
- package/src/lobby/connection.ts +162 -0
- package/src/lobby/create-match-form.tsx +122 -0
- package/src/lobby/login-form.tsx +75 -0
- package/src/lobby/match-instance.tsx +135 -0
- package/src/lobby/react.ssr.test.tsx +22 -0
- package/src/lobby/react.test.tsx +594 -0
- package/src/lobby/react.tsx +402 -0
- package/src/master/filter-player-view.test.ts +381 -0
- package/src/master/filter-player-view.ts +102 -0
- package/src/master/master.test.ts +1068 -0
- package/src/master/master.ts +492 -0
- package/src/plugins/events/events.test.ts +108 -0
- package/src/plugins/events/events.ts +209 -0
- package/src/plugins/main.test.ts +411 -0
- package/src/plugins/main.ts +314 -0
- package/src/plugins/plugin-events.ts +40 -0
- package/src/plugins/plugin-immer.test.ts +86 -0
- package/src/plugins/plugin-immer.ts +37 -0
- package/src/plugins/plugin-log.test.ts +37 -0
- package/src/plugins/plugin-log.ts +40 -0
- package/src/plugins/plugin-player.test.ts +172 -0
- package/src/plugins/plugin-player.ts +100 -0
- package/src/plugins/plugin-random.ts +40 -0
- package/src/plugins/plugin-serializable.test.ts +40 -0
- package/src/plugins/plugin-serializable.ts +55 -0
- package/src/plugins/random/random.alea.ts +109 -0
- package/src/plugins/random/random.test.ts +167 -0
- package/src/plugins/random/random.ts +198 -0
- package/src/server/api.test.ts +1699 -0
- package/src/server/api.ts +527 -0
- package/src/server/auth.test.ts +275 -0
- package/src/server/auth.ts +89 -0
- package/src/server/cors.test.ts +121 -0
- package/src/server/cors.ts +7 -0
- package/src/server/db/base.ts +296 -0
- package/src/server/db/flatfile.test.ts +221 -0
- package/src/server/db/flatfile.ts +228 -0
- package/src/server/db/index.test.ts +8 -0
- package/src/server/db/index.ts +12 -0
- package/src/server/db/inmemory.test.ts +143 -0
- package/src/server/db/inmemory.ts +143 -0
- package/src/server/db/localstorage.test.ts +73 -0
- package/src/server/db/localstorage.ts +44 -0
- package/src/server/index.test.ts +265 -0
- package/src/server/index.ts +175 -0
- package/src/server/transport/pubsub/generic-pub-sub.ts +11 -0
- package/src/server/transport/pubsub/in-memory-pub-sub.test.ts +47 -0
- package/src/server/transport/pubsub/in-memory-pub-sub.ts +28 -0
- package/src/server/transport/socketio-simultaneous.test.ts +603 -0
- package/src/server/transport/socketio.test.ts +303 -0
- package/src/server/transport/socketio.ts +279 -0
- package/src/server/util.ts +85 -0
- package/src/testing/mock-random.test.ts +45 -0
- package/src/testing/mock-random.ts +27 -0
- package/src/types.ts +511 -0
- package/testing/package.json +7 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import type { LobbyAPI } from '../types';
|
|
2
|
+
|
|
3
|
+
const assertString = (str: unknown, label: string) => {
|
|
4
|
+
if (!str || typeof str !== 'string') {
|
|
5
|
+
throw new Error(`Expected ${label} string, got "${str}".`);
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
const assertGameName = (name?: string) => assertString(name, 'game name');
|
|
9
|
+
const assertMatchID = (id?: string) => assertString(id, 'match ID');
|
|
10
|
+
|
|
11
|
+
type JSType =
|
|
12
|
+
| 'string'
|
|
13
|
+
| 'number'
|
|
14
|
+
| 'bigint'
|
|
15
|
+
| 'object'
|
|
16
|
+
| 'boolean'
|
|
17
|
+
| 'symbol'
|
|
18
|
+
| 'function'
|
|
19
|
+
| 'undefined';
|
|
20
|
+
|
|
21
|
+
const validateBody = (
|
|
22
|
+
body: { [key: string]: any } | undefined,
|
|
23
|
+
schema: { [key: string]: JSType | JSType[] }
|
|
24
|
+
) => {
|
|
25
|
+
if (!body) throw new Error(`Expected body, got “${body}”.`);
|
|
26
|
+
for (const key in schema) {
|
|
27
|
+
const propSchema = schema[key];
|
|
28
|
+
const types = Array.isArray(propSchema) ? propSchema : [propSchema];
|
|
29
|
+
const received = body[key];
|
|
30
|
+
if (!types.includes(typeof received)) {
|
|
31
|
+
const union = types.join('|');
|
|
32
|
+
throw new TypeError(
|
|
33
|
+
`Expected body.${key} to be of type ${union}, got “${received}”.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export class LobbyClientError extends Error {
|
|
40
|
+
readonly details: any;
|
|
41
|
+
|
|
42
|
+
constructor(message: string, details: any) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.details = details;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create a boardgame.io Lobby API client.
|
|
50
|
+
* @param server The API’s base URL, e.g. `http://localhost:8000`.
|
|
51
|
+
*/
|
|
52
|
+
export class LobbyClient {
|
|
53
|
+
private server: string;
|
|
54
|
+
|
|
55
|
+
constructor({ server = '' }: { server?: string } = {}) {
|
|
56
|
+
// strip trailing slash if passed
|
|
57
|
+
this.server = server.replace(/\/$/, '');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private async request(route: string, init?: RequestInit) {
|
|
61
|
+
const response = await fetch(this.server + route, init);
|
|
62
|
+
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
let details: any;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
details = await response.clone().json();
|
|
68
|
+
} catch {
|
|
69
|
+
try {
|
|
70
|
+
details = await response.text();
|
|
71
|
+
} catch (error) {
|
|
72
|
+
details = error.message;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
throw new LobbyClientError(`HTTP status ${response.status}`, details);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return response.json();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private async post(route: string, opts: { body?: any; init?: RequestInit }) {
|
|
83
|
+
let init: RequestInit = {
|
|
84
|
+
method: 'post',
|
|
85
|
+
body: JSON.stringify(opts.body),
|
|
86
|
+
headers: { 'Content-Type': 'application/json' },
|
|
87
|
+
};
|
|
88
|
+
if (opts.init)
|
|
89
|
+
init = {
|
|
90
|
+
...init,
|
|
91
|
+
...opts.init,
|
|
92
|
+
headers: { ...init.headers, ...opts.init.headers },
|
|
93
|
+
};
|
|
94
|
+
return this.request(route, init);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get a list of the game names available on this server.
|
|
99
|
+
* @param init Optional RequestInit interface to override defaults.
|
|
100
|
+
* @return Array of game names.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* lobbyClient.listGames()
|
|
104
|
+
* .then(console.log); // => ['chess', 'tic-tac-toe']
|
|
105
|
+
*/
|
|
106
|
+
async listGames(init?: RequestInit): Promise<string[]> {
|
|
107
|
+
return this.request('/games', init);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get a list of the matches for a specific game type on the server.
|
|
112
|
+
* @param gameName The game to list for, e.g. 'tic-tac-toe'.
|
|
113
|
+
* @param where Options to filter matches by update time or gameover state
|
|
114
|
+
* @param init Optional RequestInit interface to override defaults.
|
|
115
|
+
* @return Array of match metadata objects.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* lobbyClient.listMatches('tic-tac-toe', where: { isGameover: false })
|
|
119
|
+
* .then(data => console.log(data.matches));
|
|
120
|
+
* // => [
|
|
121
|
+
* // {
|
|
122
|
+
* // matchID: 'xyz',
|
|
123
|
+
* // gameName: 'tic-tac-toe',
|
|
124
|
+
* // players: [{ id: 0, name: 'Alice' }, { id: 1 }]
|
|
125
|
+
* // },
|
|
126
|
+
* // ...
|
|
127
|
+
* // ]
|
|
128
|
+
*/
|
|
129
|
+
async listMatches(
|
|
130
|
+
gameName: string,
|
|
131
|
+
where?: {
|
|
132
|
+
/**
|
|
133
|
+
* If true, only games that have ended will be returned.
|
|
134
|
+
* If false, only games that have not yet ended will be returned.
|
|
135
|
+
* Leave undefined to receive both finished and unfinished games.
|
|
136
|
+
*/
|
|
137
|
+
isGameover?: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* List matches last updated before a specific time.
|
|
140
|
+
* Value should be a timestamp in milliseconds after January 1, 1970.
|
|
141
|
+
*/
|
|
142
|
+
updatedBefore?: number;
|
|
143
|
+
/**
|
|
144
|
+
* List matches last updated after a specific time.
|
|
145
|
+
* Value should be a timestamp in milliseconds after January 1, 1970.
|
|
146
|
+
*/
|
|
147
|
+
updatedAfter?: number;
|
|
148
|
+
},
|
|
149
|
+
init?: RequestInit
|
|
150
|
+
): Promise<LobbyAPI.MatchList> {
|
|
151
|
+
assertGameName(gameName);
|
|
152
|
+
let query = '';
|
|
153
|
+
if (where) {
|
|
154
|
+
const queries = [];
|
|
155
|
+
const { isGameover, updatedBefore, updatedAfter } = where;
|
|
156
|
+
if (isGameover !== undefined) queries.push(`isGameover=${isGameover}`);
|
|
157
|
+
if (updatedBefore) queries.push(`updatedBefore=${updatedBefore}`);
|
|
158
|
+
if (updatedAfter) queries.push(`updatedAfter=${updatedAfter}`);
|
|
159
|
+
if (queries.length > 0) query = '?' + queries.join('&');
|
|
160
|
+
}
|
|
161
|
+
return this.request(`/games/${gameName}${query}`, init);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get metadata for a specific match.
|
|
166
|
+
* @param gameName The match’s game type, e.g. 'tic-tac-toe'.
|
|
167
|
+
* @param matchID Match ID for the match to fetch.
|
|
168
|
+
* @param init Optional RequestInit interface to override defaults.
|
|
169
|
+
* @return A match metadata object.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* lobbyClient.getMatch('tic-tac-toe', 'xyz').then(console.log);
|
|
173
|
+
* // => {
|
|
174
|
+
* // matchID: 'xyz',
|
|
175
|
+
* // gameName: 'tic-tac-toe',
|
|
176
|
+
* // players: [{ id: 0, name: 'Alice' }, { id: 1 }]
|
|
177
|
+
* // }
|
|
178
|
+
*/
|
|
179
|
+
async getMatch(
|
|
180
|
+
gameName: string,
|
|
181
|
+
matchID: string,
|
|
182
|
+
init?: RequestInit
|
|
183
|
+
): Promise<LobbyAPI.Match> {
|
|
184
|
+
assertGameName(gameName);
|
|
185
|
+
assertMatchID(matchID);
|
|
186
|
+
return this.request(`/games/${gameName}/${matchID}`, init);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Create a new match for a specific game type.
|
|
191
|
+
* @param gameName The game to create a match for, e.g. 'tic-tac-toe'.
|
|
192
|
+
* @param body Options required to configure match creation.
|
|
193
|
+
* @param init Optional RequestInit interface to override defaults.
|
|
194
|
+
* @return An object containing the created `matchID`.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* lobbyClient.createMatch('tic-tac-toe', { numPlayers: 2 })
|
|
198
|
+
* .then(console.log);
|
|
199
|
+
* // => { matchID: 'xyz' }
|
|
200
|
+
*/
|
|
201
|
+
async createMatch(
|
|
202
|
+
gameName: string,
|
|
203
|
+
body: {
|
|
204
|
+
numPlayers: number;
|
|
205
|
+
setupData?: any;
|
|
206
|
+
unlisted?: boolean;
|
|
207
|
+
[key: string]: any;
|
|
208
|
+
},
|
|
209
|
+
init?: RequestInit
|
|
210
|
+
): Promise<LobbyAPI.CreatedMatch> {
|
|
211
|
+
assertGameName(gameName);
|
|
212
|
+
validateBody(body, { numPlayers: 'number' });
|
|
213
|
+
return this.post(`/games/${gameName}/create`, { body, init });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Join a match using its matchID.
|
|
218
|
+
* @param gameName The match’s game type, e.g. 'tic-tac-toe'.
|
|
219
|
+
* @param matchID Match ID for the match to join.
|
|
220
|
+
* @param body Options required to join match.
|
|
221
|
+
* @param init Optional RequestInit interface to override defaults.
|
|
222
|
+
* @return Object containing `playerCredentials` for the player who joined.
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* lobbyClient.joinMatch('tic-tac-toe', 'xyz', {
|
|
226
|
+
* playerID: '1',
|
|
227
|
+
* playerName: 'Bob',
|
|
228
|
+
* }).then(console.log);
|
|
229
|
+
* // => { playerID: '1', playerCredentials: 'random-string' }
|
|
230
|
+
*/
|
|
231
|
+
async joinMatch(
|
|
232
|
+
gameName: string,
|
|
233
|
+
matchID: string,
|
|
234
|
+
body: {
|
|
235
|
+
playerID?: string;
|
|
236
|
+
playerName: string;
|
|
237
|
+
data?: any;
|
|
238
|
+
[key: string]: any;
|
|
239
|
+
},
|
|
240
|
+
init?: RequestInit
|
|
241
|
+
): Promise<LobbyAPI.JoinedMatch> {
|
|
242
|
+
assertGameName(gameName);
|
|
243
|
+
assertMatchID(matchID);
|
|
244
|
+
validateBody(body, {
|
|
245
|
+
playerID: ['string', 'undefined'],
|
|
246
|
+
playerName: 'string',
|
|
247
|
+
});
|
|
248
|
+
return this.post(`/games/${gameName}/${matchID}/join`, { body, init });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Leave a previously joined match.
|
|
253
|
+
* @param gameName The match’s game type, e.g. 'tic-tac-toe'.
|
|
254
|
+
* @param matchID Match ID for the match to leave.
|
|
255
|
+
* @param body Options required to leave match.
|
|
256
|
+
* @param init Optional RequestInit interface to override defaults.
|
|
257
|
+
* @return Promise resolves if successful.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* lobbyClient.leaveMatch('tic-tac-toe', 'xyz', {
|
|
261
|
+
* playerID: '1',
|
|
262
|
+
* credentials: 'credentials-returned-when-joining',
|
|
263
|
+
* })
|
|
264
|
+
* .then(() => console.log('Left match.'))
|
|
265
|
+
* .catch(error => console.error('Error leaving match', error));
|
|
266
|
+
*/
|
|
267
|
+
async leaveMatch(
|
|
268
|
+
gameName: string,
|
|
269
|
+
matchID: string,
|
|
270
|
+
body: {
|
|
271
|
+
playerID: string;
|
|
272
|
+
credentials: string;
|
|
273
|
+
[key: string]: any;
|
|
274
|
+
},
|
|
275
|
+
init?: RequestInit
|
|
276
|
+
): Promise<void> {
|
|
277
|
+
assertGameName(gameName);
|
|
278
|
+
assertMatchID(matchID);
|
|
279
|
+
validateBody(body, { playerID: 'string', credentials: 'string' });
|
|
280
|
+
await this.post(`/games/${gameName}/${matchID}/leave`, { body, init });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Update a player’s name or custom metadata.
|
|
285
|
+
* @param gameName The match’s game type, e.g. 'tic-tac-toe'.
|
|
286
|
+
* @param matchID Match ID for the match to update.
|
|
287
|
+
* @param body Options required to update player.
|
|
288
|
+
* @param init Optional RequestInit interface to override defaults.
|
|
289
|
+
* @return Promise resolves if successful.
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* lobbyClient.updatePlayer('tic-tac-toe', 'xyz', {
|
|
293
|
+
* playerID: '0',
|
|
294
|
+
* credentials: 'credentials-returned-when-joining',
|
|
295
|
+
* newName: 'Al',
|
|
296
|
+
* })
|
|
297
|
+
* .then(() => console.log('Updated player data.'))
|
|
298
|
+
* .catch(error => console.error('Error updating data', error));
|
|
299
|
+
*/
|
|
300
|
+
async updatePlayer(
|
|
301
|
+
gameName: string,
|
|
302
|
+
matchID: string,
|
|
303
|
+
body: {
|
|
304
|
+
playerID: string;
|
|
305
|
+
credentials: string;
|
|
306
|
+
newName?: string;
|
|
307
|
+
data?: any;
|
|
308
|
+
[key: string]: any;
|
|
309
|
+
},
|
|
310
|
+
init?: RequestInit
|
|
311
|
+
): Promise<void> {
|
|
312
|
+
assertGameName(gameName);
|
|
313
|
+
assertMatchID(matchID);
|
|
314
|
+
validateBody(body, { playerID: 'string', credentials: 'string' });
|
|
315
|
+
await this.post(`/games/${gameName}/${matchID}/update`, { body, init });
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Create a new match based on the configuration of the current match.
|
|
320
|
+
* @param gameName The match’s game type, e.g. 'tic-tac-toe'.
|
|
321
|
+
* @param matchID Match ID for the match to play again.
|
|
322
|
+
* @param body Options required to configure match.
|
|
323
|
+
* @param init Optional RequestInit interface to override defaults.
|
|
324
|
+
* @return Object containing `nextMatchID`.
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* lobbyClient.playAgain('tic-tac-toe', 'xyz', {
|
|
328
|
+
* playerID: '0',
|
|
329
|
+
* credentials: 'credentials-returned-when-joining',
|
|
330
|
+
* })
|
|
331
|
+
* .then(({ nextMatchID }) => {
|
|
332
|
+
* return lobbyClient.joinMatch('tic-tac-toe', nextMatchID, {
|
|
333
|
+
* playerID: '0',
|
|
334
|
+
* playerName: 'Al',
|
|
335
|
+
* })
|
|
336
|
+
* })
|
|
337
|
+
* .then({ playerCredentials } => {
|
|
338
|
+
* console.log(playerCredentials);
|
|
339
|
+
* })
|
|
340
|
+
* .catch(console.error);
|
|
341
|
+
*/
|
|
342
|
+
async playAgain(
|
|
343
|
+
gameName: string,
|
|
344
|
+
matchID: string,
|
|
345
|
+
body: {
|
|
346
|
+
playerID: string;
|
|
347
|
+
credentials: string;
|
|
348
|
+
unlisted?: boolean;
|
|
349
|
+
[key: string]: any;
|
|
350
|
+
},
|
|
351
|
+
init?: RequestInit
|
|
352
|
+
): Promise<LobbyAPI.NextMatch> {
|
|
353
|
+
assertGameName(gameName);
|
|
354
|
+
assertMatchID(matchID);
|
|
355
|
+
validateBody(body, { playerID: 'string', credentials: 'string' });
|
|
356
|
+
return this.post(`/games/${gameName}/${matchID}/playAgain`, { body, init });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2018 The boardgame.io Authors
|
|
3
|
+
*
|
|
4
|
+
* Use of this source code is governed by a MIT-style
|
|
5
|
+
* license that can be found in the LICENSE file or at
|
|
6
|
+
* https://opensource.org/licenses/MIT.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { LobbyConnection } from './connection';
|
|
10
|
+
import type { LobbyAPI } from '../types';
|
|
11
|
+
|
|
12
|
+
describe('lobby', () => {
|
|
13
|
+
let lobby: ReturnType<typeof LobbyConnection>;
|
|
14
|
+
let match1: LobbyAPI.Match, match2: LobbyAPI.Match;
|
|
15
|
+
let jsonResult = [];
|
|
16
|
+
let nextStatus = 200;
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
match1 = {
|
|
20
|
+
gameName: 'game1',
|
|
21
|
+
matchID: 'matchID_1',
|
|
22
|
+
players: [{ id: 0 }],
|
|
23
|
+
createdAt: 1,
|
|
24
|
+
updatedAt: 4,
|
|
25
|
+
};
|
|
26
|
+
match2 = {
|
|
27
|
+
gameName: 'game2',
|
|
28
|
+
matchID: 'matchID_2',
|
|
29
|
+
players: [{ id: 1 }],
|
|
30
|
+
createdAt: 2,
|
|
31
|
+
updatedAt: 3,
|
|
32
|
+
};
|
|
33
|
+
// result of connection requests
|
|
34
|
+
jsonResult = [
|
|
35
|
+
() => ['game1', 'game2'],
|
|
36
|
+
() => {
|
|
37
|
+
return { matches: [match1] };
|
|
38
|
+
},
|
|
39
|
+
() => {
|
|
40
|
+
return { matches: [match2] };
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
const nextResult = jsonResult.shift.bind(jsonResult);
|
|
44
|
+
nextStatus = 200;
|
|
45
|
+
(global as any).fetch = jest.fn(async () => ({
|
|
46
|
+
ok: nextStatus === 200,
|
|
47
|
+
status: nextStatus,
|
|
48
|
+
json: nextResult(),
|
|
49
|
+
}));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('handling all games', () => {
|
|
53
|
+
beforeEach(async () => {
|
|
54
|
+
lobby = LobbyConnection({
|
|
55
|
+
server: 'localhost',
|
|
56
|
+
gameComponents: [
|
|
57
|
+
{
|
|
58
|
+
board: () => null,
|
|
59
|
+
game: { name: 'game1', minPlayers: 2, maxPlayers: 4 },
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
board: () => null,
|
|
63
|
+
game: { name: 'game2' },
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
playerName: 'Bob',
|
|
67
|
+
});
|
|
68
|
+
await lobby.refresh();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('get list of matches', () => {
|
|
72
|
+
test('when the server requests succeed', async () => {
|
|
73
|
+
expect(fetch).toHaveBeenCalledTimes(3);
|
|
74
|
+
expect(lobby.matches).toEqual([match1, match2]);
|
|
75
|
+
});
|
|
76
|
+
test('when the server request fails', async () => {
|
|
77
|
+
nextStatus = 404;
|
|
78
|
+
await expect(lobby.refresh()).rejects.toThrow();
|
|
79
|
+
expect(lobby.matches).toEqual([]);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('join a match', () => {
|
|
84
|
+
beforeEach(async () => {
|
|
85
|
+
// result of request 'join'
|
|
86
|
+
jsonResult.push(() => {
|
|
87
|
+
return { playerCredentials: 'SECRET' };
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
test('when the match exists', async () => {
|
|
91
|
+
await lobby.join('game1', 'matchID_1', '0');
|
|
92
|
+
expect(fetch).toHaveBeenCalledTimes(4);
|
|
93
|
+
expect(lobby.matches[0].players[0]).toEqual({
|
|
94
|
+
id: 0,
|
|
95
|
+
name: 'Bob',
|
|
96
|
+
});
|
|
97
|
+
expect(lobby.playerCredentials).toEqual('SECRET');
|
|
98
|
+
});
|
|
99
|
+
test('when the match does not exist', async () => {
|
|
100
|
+
await expect(lobby.join('game1', 'matchID_3', '0')).rejects.toThrow();
|
|
101
|
+
expect(lobby.matches).toEqual([match1, match2]);
|
|
102
|
+
});
|
|
103
|
+
test('when the seat is not available', async () => {
|
|
104
|
+
match1.players[0].name = 'Bob';
|
|
105
|
+
await expect(lobby.join('game1', 'matchID_3', '0')).rejects.toThrow();
|
|
106
|
+
});
|
|
107
|
+
test('when the server request fails', async () => {
|
|
108
|
+
nextStatus = 404;
|
|
109
|
+
await expect(lobby.join('game1', 'matchID_1', '0')).rejects.toThrow();
|
|
110
|
+
});
|
|
111
|
+
test('when the player has already joined another match', async () => {
|
|
112
|
+
match2.players[0].name = 'Bob';
|
|
113
|
+
await expect(lobby.join('game1', 'matchID_1', '0')).rejects.toThrow();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('leave a match', () => {
|
|
118
|
+
beforeEach(async () => {
|
|
119
|
+
// result of request 'join'
|
|
120
|
+
jsonResult.push(() => {
|
|
121
|
+
return { playerCredentials: 'SECRET' };
|
|
122
|
+
});
|
|
123
|
+
await lobby.join('game1', 'matchID_1', '0');
|
|
124
|
+
// result of request 'leave'
|
|
125
|
+
jsonResult.push(() => {
|
|
126
|
+
return {};
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
test('when the match exists', async () => {
|
|
130
|
+
await lobby.leave('game1', 'matchID_1');
|
|
131
|
+
expect(fetch).toHaveBeenCalledTimes(5);
|
|
132
|
+
expect(lobby.matches).toEqual([match1, match2]);
|
|
133
|
+
});
|
|
134
|
+
test('when the match does not exist', async () => {
|
|
135
|
+
await expect(lobby.leave('game1', 'matchID_3')).rejects.toThrow();
|
|
136
|
+
expect(fetch).toHaveBeenCalledTimes(4);
|
|
137
|
+
expect(lobby.matches).toEqual([match1, match2]);
|
|
138
|
+
});
|
|
139
|
+
test('when the player is not in the match', async () => {
|
|
140
|
+
await lobby.leave('game1', 'matchID_1');
|
|
141
|
+
expect(fetch).toHaveBeenCalledTimes(5);
|
|
142
|
+
await expect(lobby.leave('game1', 'matchID_1')).rejects.toThrow();
|
|
143
|
+
});
|
|
144
|
+
test('when the server request fails', async () => {
|
|
145
|
+
nextStatus = 404;
|
|
146
|
+
await expect(lobby.leave('game1', 'matchID_1')).rejects.toThrow();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('disconnect', () => {
|
|
151
|
+
beforeEach(async () => {});
|
|
152
|
+
test('when the player leaves the lobby', async () => {
|
|
153
|
+
await lobby.disconnect();
|
|
154
|
+
expect(lobby.matches).toEqual([]);
|
|
155
|
+
});
|
|
156
|
+
test('when the player had joined a match', async () => {
|
|
157
|
+
// result of request 'join'
|
|
158
|
+
jsonResult.push(() => {
|
|
159
|
+
return { playerCredentials: 'SECRET' };
|
|
160
|
+
});
|
|
161
|
+
await lobby.join('game1', 'matchID_1', '0');
|
|
162
|
+
// result of request 'leave'
|
|
163
|
+
jsonResult.push(() => {
|
|
164
|
+
return {};
|
|
165
|
+
});
|
|
166
|
+
await lobby.disconnect();
|
|
167
|
+
expect(lobby.matches).toEqual([]);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('create a match', () => {
|
|
172
|
+
test('when the server request succeeds', async () => {
|
|
173
|
+
jsonResult.push(() => ({ matchID: 'abc' }));
|
|
174
|
+
await lobby.create('game1', 2);
|
|
175
|
+
expect(fetch).toHaveBeenCalledTimes(4);
|
|
176
|
+
});
|
|
177
|
+
test('when the number of players is off boundaries', async () => {
|
|
178
|
+
await expect(lobby.create('game1', 1)).rejects.toThrow();
|
|
179
|
+
});
|
|
180
|
+
test('when the number of players has no boundaries', async () => {
|
|
181
|
+
jsonResult.push(() => ({ matchID: 'def' }));
|
|
182
|
+
await expect(lobby.create('game2', 1)).resolves.toBeUndefined();
|
|
183
|
+
});
|
|
184
|
+
test('when the game is unknown', async () => {
|
|
185
|
+
await expect(lobby.create('game3', 2)).rejects.toThrow();
|
|
186
|
+
});
|
|
187
|
+
test('when the server request fails', async () => {
|
|
188
|
+
nextStatus = 404;
|
|
189
|
+
await expect(lobby.create('game1', 2)).rejects.toThrow();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('handling some games', () => {
|
|
195
|
+
beforeEach(async () => {
|
|
196
|
+
lobby = LobbyConnection({
|
|
197
|
+
server: 'localhost',
|
|
198
|
+
gameComponents: [{ board: () => null, game: { name: 'game1' } }],
|
|
199
|
+
});
|
|
200
|
+
await lobby.refresh();
|
|
201
|
+
});
|
|
202
|
+
test('get list of matches for supported games', async () => {
|
|
203
|
+
expect(fetch).toHaveBeenCalledTimes(2);
|
|
204
|
+
expect(lobby.matches).toEqual([match1]);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
});
|