@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.
Files changed (296) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +102 -0
  3. package/ai/package.json +7 -0
  4. package/client/package.json +7 -0
  5. package/core/package.json +7 -0
  6. package/debug/package.json +7 -0
  7. package/dist/boardgameio.es.js +14238 -0
  8. package/dist/boardgameio.js +14277 -0
  9. package/dist/boardgameio.min.js +16 -0
  10. package/dist/cjs/Debug-9d141c06.js +9586 -0
  11. package/dist/cjs/ai-e0e8a768.js +377 -0
  12. package/dist/cjs/ai.js +20 -0
  13. package/dist/cjs/client-76dec77b.js +258 -0
  14. package/dist/cjs/client-a22d7500.js +524 -0
  15. package/dist/cjs/client.js +26 -0
  16. package/dist/cjs/core.js +52 -0
  17. package/dist/cjs/debug.js +18 -0
  18. package/dist/cjs/filter-player-view-bb02e2f6.js +89 -0
  19. package/dist/cjs/initialize-267fcd69.js +61 -0
  20. package/dist/cjs/internal.js +25 -0
  21. package/dist/cjs/master-2904879d.js +320 -0
  22. package/dist/cjs/master.js +18 -0
  23. package/dist/cjs/multiplayer.js +23 -0
  24. package/dist/cjs/plugin-random-7425844d.js +229 -0
  25. package/dist/cjs/plugins.js +59 -0
  26. package/dist/cjs/react-native.js +182 -0
  27. package/dist/cjs/react.js +727 -0
  28. package/dist/cjs/reducer-16eec232.js +1203 -0
  29. package/dist/cjs/server.js +4087 -0
  30. package/dist/cjs/socketio-7a0837eb.js +478 -0
  31. package/dist/cjs/testing.js +30 -0
  32. package/dist/cjs/transport-b1874dfa.js +37 -0
  33. package/dist/cjs/turn-order-b2ff8740.js +1136 -0
  34. package/dist/cjs/util-fcfd8fb8.js +140 -0
  35. package/dist/esm/Debug-0141fe2d.js +9577 -0
  36. package/dist/esm/ai-5c06e761.js +371 -0
  37. package/dist/esm/ai.js +8 -0
  38. package/dist/esm/client-2e653027.js +522 -0
  39. package/dist/esm/client-5f57c3f2.js +255 -0
  40. package/dist/esm/client.js +16 -0
  41. package/dist/esm/core.js +40 -0
  42. package/dist/esm/debug.js +10 -0
  43. package/dist/esm/filter-player-view-2c6cc96f.js +87 -0
  44. package/dist/esm/initialize-11d626ca.js +59 -0
  45. package/dist/esm/internal.js +10 -0
  46. package/dist/esm/master-fa8f2e43.js +318 -0
  47. package/dist/esm/master.js +10 -0
  48. package/dist/esm/multiplayer.js +14 -0
  49. package/dist/esm/plugin-random-087f861e.js +226 -0
  50. package/dist/esm/plugins.js +55 -0
  51. package/dist/esm/react-native.js +173 -0
  52. package/dist/esm/react.js +716 -0
  53. package/dist/esm/reducer-c46da7e5.js +1198 -0
  54. package/dist/esm/socketio-c22ffa65.js +455 -0
  55. package/dist/esm/testing.js +26 -0
  56. package/dist/esm/transport-ce07b771.js +35 -0
  57. package/dist/esm/turn-order-376d315e.js +1091 -0
  58. package/dist/esm/util-b6147cef.js +135 -0
  59. package/dist/types/packages/ai.d.ts +5 -0
  60. package/dist/types/packages/client.d.ts +3 -0
  61. package/dist/types/packages/core.d.ts +5 -0
  62. package/dist/types/packages/debug.d.ts +2 -0
  63. package/dist/types/packages/internal.d.ts +8 -0
  64. package/dist/types/packages/master.d.ts +2 -0
  65. package/dist/types/packages/multiplayer.d.ts +3 -0
  66. package/dist/types/packages/plugins.d.ts +3 -0
  67. package/dist/types/packages/react-native.d.ts +2 -0
  68. package/dist/types/packages/react.d.ts +3 -0
  69. package/dist/types/packages/server.d.ts +6 -0
  70. package/dist/types/packages/testing.d.ts +1 -0
  71. package/dist/types/src/ai/ai.d.ts +53 -0
  72. package/dist/types/src/ai/ai.test.d.ts +1 -0
  73. package/dist/types/src/ai/bot.d.ts +40 -0
  74. package/dist/types/src/ai/mcts-bot.d.ts +60 -0
  75. package/dist/types/src/ai/random-bot.d.ts +27 -0
  76. package/dist/types/src/client/client.d.ts +104 -0
  77. package/dist/types/src/client/client.test.d.ts +1 -0
  78. package/dist/types/src/client/debug/tests/debug.test.d.ts +1 -0
  79. package/dist/types/src/client/manager.d.ts +61 -0
  80. package/dist/types/src/client/react.d.ts +75 -0
  81. package/dist/types/src/client/react.ssr.test.d.ts +4 -0
  82. package/dist/types/src/client/react.test.d.ts +1 -0
  83. package/dist/types/src/client/transport/dummy.d.ts +18 -0
  84. package/dist/types/src/client/transport/local.d.ts +59 -0
  85. package/dist/types/src/client/transport/local.test.d.ts +1 -0
  86. package/dist/types/src/client/transport/socketio.d.ts +45 -0
  87. package/dist/types/src/client/transport/socketio.test.d.ts +1 -0
  88. package/dist/types/src/client/transport/transport.d.ts +50 -0
  89. package/dist/types/src/client/transport/transport.test.d.ts +1 -0
  90. package/dist/types/src/core/action-creators.d.ts +144 -0
  91. package/dist/types/src/core/action-types.d.ts +10 -0
  92. package/dist/types/src/core/backwards-compatibility.d.ts +12 -0
  93. package/dist/types/src/core/constants.d.ts +6 -0
  94. package/dist/types/src/core/errors.d.ts +15 -0
  95. package/dist/types/src/core/flow.d.ts +28 -0
  96. package/dist/types/src/core/flow.test.d.ts +1 -0
  97. package/dist/types/src/core/game-methods.d.ts +9 -0
  98. package/dist/types/src/core/game.d.ts +26 -0
  99. package/dist/types/src/core/game.test.d.ts +1 -0
  100. package/dist/types/src/core/initialize.d.ts +9 -0
  101. package/dist/types/src/core/logger.d.ts +2 -0
  102. package/dist/types/src/core/player-view.d.ts +7 -0
  103. package/dist/types/src/core/player-view.test.d.ts +1 -0
  104. package/dist/types/src/core/reducer.d.ts +155 -0
  105. package/dist/types/src/core/reducer.test.d.ts +1 -0
  106. package/dist/types/src/core/turn-order.d.ts +179 -0
  107. package/dist/types/src/core/turn-order.test.d.ts +8 -0
  108. package/dist/types/src/lobby/client.d.ts +194 -0
  109. package/dist/types/src/lobby/client.test.d.ts +1 -0
  110. package/dist/types/src/lobby/connection.d.ts +44 -0
  111. package/dist/types/src/lobby/connection.test.d.ts +1 -0
  112. package/dist/types/src/lobby/create-match-form.d.ts +26 -0
  113. package/dist/types/src/lobby/login-form.d.ts +23 -0
  114. package/dist/types/src/lobby/match-instance.d.ts +31 -0
  115. package/dist/types/src/lobby/react.d.ts +113 -0
  116. package/dist/types/src/lobby/react.ssr.test.d.ts +4 -0
  117. package/dist/types/src/lobby/react.test.d.ts +1 -0
  118. package/dist/types/src/master/filter-player-view.d.ts +96 -0
  119. package/dist/types/src/master/filter-player-view.test.d.ts +1 -0
  120. package/dist/types/src/master/master.d.ts +94 -0
  121. package/dist/types/src/master/master.test.d.ts +1 -0
  122. package/dist/types/src/plugins/events/events.d.ts +54 -0
  123. package/dist/types/src/plugins/events/events.test.d.ts +1 -0
  124. package/dist/types/src/plugins/main.d.ts +75 -0
  125. package/dist/types/src/plugins/main.test.d.ts +1 -0
  126. package/dist/types/src/plugins/plugin-events.d.ts +5 -0
  127. package/dist/types/src/plugins/plugin-immer.d.ts +7 -0
  128. package/dist/types/src/plugins/plugin-immer.test.d.ts +1 -0
  129. package/dist/types/src/plugins/plugin-log.d.ts +14 -0
  130. package/dist/types/src/plugins/plugin-log.test.d.ts +1 -0
  131. package/dist/types/src/plugins/plugin-player.d.ts +29 -0
  132. package/dist/types/src/plugins/plugin-player.test.d.ts +1 -0
  133. package/dist/types/src/plugins/plugin-random.d.ts +4 -0
  134. package/dist/types/src/plugins/plugin-serializable.d.ts +7 -0
  135. package/dist/types/src/plugins/plugin-serializable.test.d.ts +1 -0
  136. package/dist/types/src/plugins/random/random.alea.d.ts +19 -0
  137. package/dist/types/src/plugins/random/random.d.ts +54 -0
  138. package/dist/types/src/plugins/random/random.test.d.ts +1 -0
  139. package/dist/types/src/server/api.d.ts +13 -0
  140. package/dist/types/src/server/api.test.d.ts +1 -0
  141. package/dist/types/src/server/auth.d.ts +38 -0
  142. package/dist/types/src/server/auth.test.d.ts +1 -0
  143. package/dist/types/src/server/cors.d.ts +4 -0
  144. package/dist/types/src/server/cors.test.d.ts +1 -0
  145. package/dist/types/src/server/db/base.d.ts +192 -0
  146. package/dist/types/src/server/db/flatfile.d.ts +44 -0
  147. package/dist/types/src/server/db/flatfile.test.d.ts +1 -0
  148. package/dist/types/src/server/db/index.d.ts +4 -0
  149. package/dist/types/src/server/db/index.test.d.ts +1 -0
  150. package/dist/types/src/server/db/inmemory.d.ts +43 -0
  151. package/dist/types/src/server/db/inmemory.test.d.ts +1 -0
  152. package/dist/types/src/server/db/localstorage.d.ts +7 -0
  153. package/dist/types/src/server/db/localstorage.test.d.ts +1 -0
  154. package/dist/types/src/server/index.d.ts +68 -0
  155. package/dist/types/src/server/index.test.d.ts +1 -0
  156. package/dist/types/src/server/transport/pubsub/generic-pub-sub.d.ts +6 -0
  157. package/dist/types/src/server/transport/pubsub/in-memory-pub-sub.d.ts +7 -0
  158. package/dist/types/src/server/transport/pubsub/in-memory-pub-sub.test.d.ts +1 -0
  159. package/dist/types/src/server/transport/socketio-simultaneous.test.d.ts +1 -0
  160. package/dist/types/src/server/transport/socketio.d.ts +65 -0
  161. package/dist/types/src/server/transport/socketio.test.d.ts +1 -0
  162. package/dist/types/src/server/util.d.ts +35 -0
  163. package/dist/types/src/testing/mock-random.d.ts +15 -0
  164. package/dist/types/src/testing/mock-random.test.d.ts +1 -0
  165. package/dist/types/src/types.d.ts +387 -0
  166. package/internal/package.json +7 -0
  167. package/master/package.json +7 -0
  168. package/multiplayer/package.json +7 -0
  169. package/package.json +211 -0
  170. package/plugins/package.json +7 -0
  171. package/react/package.json +7 -0
  172. package/react-native/package.json +7 -0
  173. package/server/package.json +6 -0
  174. package/src/ai/ai.test.ts +433 -0
  175. package/src/ai/ai.ts +84 -0
  176. package/src/ai/bot.ts +122 -0
  177. package/src/ai/mcts-bot.ts +331 -0
  178. package/src/ai/random-bot.ts +20 -0
  179. package/src/client/client.test.ts +993 -0
  180. package/src/client/client.ts +588 -0
  181. package/src/client/debug/Debug.svelte +239 -0
  182. package/src/client/debug/Menu.svelte +65 -0
  183. package/src/client/debug/ai/AI.svelte +215 -0
  184. package/src/client/debug/ai/Options.svelte +48 -0
  185. package/src/client/debug/info/Info.svelte +22 -0
  186. package/src/client/debug/info/Item.svelte +24 -0
  187. package/src/client/debug/log/Log.svelte +157 -0
  188. package/src/client/debug/log/LogEvent.svelte +149 -0
  189. package/src/client/debug/log/LogMetadata.svelte +7 -0
  190. package/src/client/debug/log/PhaseMarker.svelte +27 -0
  191. package/src/client/debug/log/TurnMarker.svelte +23 -0
  192. package/src/client/debug/main/ClientSwitcher.svelte +59 -0
  193. package/src/client/debug/main/Controls.svelte +58 -0
  194. package/src/client/debug/main/Hotkey.svelte +84 -0
  195. package/src/client/debug/main/InteractiveFunction.svelte +85 -0
  196. package/src/client/debug/main/Main.svelte +121 -0
  197. package/src/client/debug/main/Move.svelte +68 -0
  198. package/src/client/debug/main/PlayerInfo.svelte +70 -0
  199. package/src/client/debug/mcts/Action.svelte +22 -0
  200. package/src/client/debug/mcts/MCTS.svelte +78 -0
  201. package/src/client/debug/mcts/Table.svelte +98 -0
  202. package/src/client/debug/tests/JSONTree.mock.svelte +3 -0
  203. package/src/client/debug/tests/debug.test.ts +183 -0
  204. package/src/client/debug/utils/shortcuts.js +50 -0
  205. package/src/client/debug/utils/shortcuts.test.js +49 -0
  206. package/src/client/manager.ts +177 -0
  207. package/src/client/react-native.js +136 -0
  208. package/src/client/react-native.test.js +229 -0
  209. package/src/client/react.ssr.test.tsx +24 -0
  210. package/src/client/react.test.tsx +213 -0
  211. package/src/client/react.tsx +192 -0
  212. package/src/client/transport/dummy.ts +19 -0
  213. package/src/client/transport/local.test.ts +353 -0
  214. package/src/client/transport/local.ts +230 -0
  215. package/src/client/transport/socketio.test.ts +328 -0
  216. package/src/client/transport/socketio.ts +210 -0
  217. package/src/client/transport/transport.test.ts +27 -0
  218. package/src/client/transport/transport.ts +95 -0
  219. package/src/core/action-creators.ts +159 -0
  220. package/src/core/action-types.ts +18 -0
  221. package/src/core/backwards-compatibility.ts +23 -0
  222. package/src/core/constants.ts +6 -0
  223. package/src/core/errors.ts +35 -0
  224. package/src/core/flow.test.ts +2433 -0
  225. package/src/core/flow.ts +897 -0
  226. package/src/core/game-methods.ts +9 -0
  227. package/src/core/game.test.ts +286 -0
  228. package/src/core/game.ts +114 -0
  229. package/src/core/initialize.ts +77 -0
  230. package/src/core/logger.test.js +90 -0
  231. package/src/core/logger.ts +18 -0
  232. package/src/core/player-view.test.ts +50 -0
  233. package/src/core/player-view.ts +39 -0
  234. package/src/core/reducer.test.ts +991 -0
  235. package/src/core/reducer.ts +532 -0
  236. package/src/core/turn-order.test.ts +1123 -0
  237. package/src/core/turn-order.ts +473 -0
  238. package/src/lobby/client.test.ts +385 -0
  239. package/src/lobby/client.ts +358 -0
  240. package/src/lobby/connection.test.ts +207 -0
  241. package/src/lobby/connection.ts +162 -0
  242. package/src/lobby/create-match-form.tsx +122 -0
  243. package/src/lobby/login-form.tsx +75 -0
  244. package/src/lobby/match-instance.tsx +135 -0
  245. package/src/lobby/react.ssr.test.tsx +22 -0
  246. package/src/lobby/react.test.tsx +594 -0
  247. package/src/lobby/react.tsx +402 -0
  248. package/src/master/filter-player-view.test.ts +381 -0
  249. package/src/master/filter-player-view.ts +102 -0
  250. package/src/master/master.test.ts +1068 -0
  251. package/src/master/master.ts +492 -0
  252. package/src/plugins/events/events.test.ts +108 -0
  253. package/src/plugins/events/events.ts +209 -0
  254. package/src/plugins/main.test.ts +411 -0
  255. package/src/plugins/main.ts +314 -0
  256. package/src/plugins/plugin-events.ts +40 -0
  257. package/src/plugins/plugin-immer.test.ts +86 -0
  258. package/src/plugins/plugin-immer.ts +37 -0
  259. package/src/plugins/plugin-log.test.ts +37 -0
  260. package/src/plugins/plugin-log.ts +40 -0
  261. package/src/plugins/plugin-player.test.ts +172 -0
  262. package/src/plugins/plugin-player.ts +100 -0
  263. package/src/plugins/plugin-random.ts +40 -0
  264. package/src/plugins/plugin-serializable.test.ts +40 -0
  265. package/src/plugins/plugin-serializable.ts +55 -0
  266. package/src/plugins/random/random.alea.ts +109 -0
  267. package/src/plugins/random/random.test.ts +167 -0
  268. package/src/plugins/random/random.ts +198 -0
  269. package/src/server/api.test.ts +1699 -0
  270. package/src/server/api.ts +527 -0
  271. package/src/server/auth.test.ts +275 -0
  272. package/src/server/auth.ts +89 -0
  273. package/src/server/cors.test.ts +121 -0
  274. package/src/server/cors.ts +7 -0
  275. package/src/server/db/base.ts +296 -0
  276. package/src/server/db/flatfile.test.ts +221 -0
  277. package/src/server/db/flatfile.ts +228 -0
  278. package/src/server/db/index.test.ts +8 -0
  279. package/src/server/db/index.ts +12 -0
  280. package/src/server/db/inmemory.test.ts +143 -0
  281. package/src/server/db/inmemory.ts +143 -0
  282. package/src/server/db/localstorage.test.ts +73 -0
  283. package/src/server/db/localstorage.ts +44 -0
  284. package/src/server/index.test.ts +265 -0
  285. package/src/server/index.ts +175 -0
  286. package/src/server/transport/pubsub/generic-pub-sub.ts +11 -0
  287. package/src/server/transport/pubsub/in-memory-pub-sub.test.ts +47 -0
  288. package/src/server/transport/pubsub/in-memory-pub-sub.ts +28 -0
  289. package/src/server/transport/socketio-simultaneous.test.ts +603 -0
  290. package/src/server/transport/socketio.test.ts +303 -0
  291. package/src/server/transport/socketio.ts +279 -0
  292. package/src/server/util.ts +85 -0
  293. package/src/testing/mock-random.test.ts +45 -0
  294. package/src/testing/mock-random.ts +27 -0
  295. package/src/types.ts +511 -0
  296. package/testing/package.json +7 -0
@@ -0,0 +1,603 @@
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 type { SocketOpts } from './socketio';
10
+ import { SocketIO } from './socketio';
11
+ import { Auth } from '../auth';
12
+ import { InMemory } from '../db';
13
+ import { Async } from '../db/base';
14
+ import { createMetadata } from '../util';
15
+ import { ProcessGameConfig } from '../../core/game';
16
+ import * as ActionCreators from '../../core/action-creators';
17
+ import { InitializeGame } from '../../core/initialize';
18
+ import { PlayerView } from '../../core/player-view';
19
+ import type { Master } from '../../master/master';
20
+ import type { Game, LogEntry, Server, State, StorageAPI } from '../../types';
21
+
22
+ type SyncArgs = Parameters<Master['onSync']>;
23
+ type UpdateArgs = Parameters<Master['onUpdate']>;
24
+
25
+ type SocketIOTestAdapterOpts = SocketOpts & {
26
+ clientInfo?: Map<any, any>;
27
+ roomInfo?: Map<any, any>;
28
+ };
29
+
30
+ class InMemoryAsync extends Async {
31
+ db: InMemory;
32
+ delays: number[];
33
+
34
+ constructor() {
35
+ super();
36
+ this.db = new InMemory();
37
+ this.delays = [];
38
+ }
39
+
40
+ async connect() {
41
+ await this.sleep();
42
+ }
43
+
44
+ private sleep(): Promise<void> {
45
+ const interval =
46
+ this.delays.length > 0
47
+ ? this.delays.shift()
48
+ : Math.round(Math.random() * 50 + 50);
49
+ return new Promise((resolve) => void setTimeout(resolve, interval));
50
+ }
51
+
52
+ async createMatch(id: string, opts: StorageAPI.CreateMatchOpts) {
53
+ await this.sleep();
54
+ this.db.createMatch(id, opts);
55
+ }
56
+
57
+ async setMetadata(matchID: string, metadata: Server.MatchData) {
58
+ await this.sleep();
59
+ this.db.setMetadata(matchID, metadata);
60
+ }
61
+
62
+ async setState(matchID: string, state: State, deltalog?: LogEntry[]) {
63
+ await this.sleep();
64
+ this.db.setState(matchID, state, deltalog);
65
+ }
66
+
67
+ async fetch<O extends StorageAPI.FetchOpts>(
68
+ matchID: string,
69
+ opts: O
70
+ ): Promise<StorageAPI.FetchResult<O>> {
71
+ await this.sleep();
72
+ return this.db.fetch(matchID, opts);
73
+ }
74
+
75
+ async wipe(matchID: string) {
76
+ await this.sleep();
77
+ this.db.wipe(matchID);
78
+ }
79
+
80
+ async listMatches(opts?: StorageAPI.ListMatchesOpts): Promise<string[]> {
81
+ await this.sleep();
82
+ return this.db.listMatches(opts);
83
+ }
84
+ }
85
+
86
+ class SocketIOTestAdapter extends SocketIO {
87
+ constructor({
88
+ clientInfo = new Map(),
89
+ roomInfo = new Map(),
90
+ ...args
91
+ }: SocketIOTestAdapterOpts = {}) {
92
+ super(Object.keys(args).length > 0 ? args : undefined);
93
+ this.clientInfo = clientInfo;
94
+ this.roomInfo = roomInfo;
95
+ }
96
+
97
+ public get getPerMatchQueue() {
98
+ return this.perMatchQueue;
99
+ }
100
+ }
101
+
102
+ jest.mock('koa-socket-2', () => {
103
+ class MockSocket {
104
+ id: string;
105
+ callbacks: Record<string, (...args: any[]) => Promise<void>>;
106
+ emit: jest.Mock<any, any>;
107
+ broadcast: { emit: jest.Mock<any, any> };
108
+
109
+ constructor({ id }: { id: string }) {
110
+ this.id = id;
111
+ this.callbacks = {};
112
+ this.emit = jest.fn();
113
+ this.broadcast = { emit: jest.fn() };
114
+ }
115
+
116
+ async receive(type, ...args) {
117
+ await this.callbacks[type](...args);
118
+ }
119
+
120
+ on(type, callback) {
121
+ this.callbacks[type] = callback;
122
+ }
123
+
124
+ to() {
125
+ return {
126
+ broadcast: this.broadcast,
127
+ emit: this.emit,
128
+ };
129
+ }
130
+
131
+ join() {}
132
+ }
133
+
134
+ class MockIO {
135
+ sockets: Map<string, MockSocket>;
136
+ socketAdapter: any;
137
+
138
+ constructor() {
139
+ this.sockets = new Map(
140
+ ['0', '1'].map((id) => [id, new MockSocket({ id })])
141
+ );
142
+ }
143
+
144
+ adapter(socketAdapter) {
145
+ this.socketAdapter = socketAdapter;
146
+ }
147
+
148
+ attach(app) {
149
+ app.io = app._io = this;
150
+ }
151
+
152
+ of() {
153
+ return this;
154
+ }
155
+
156
+ on(_event, callback) {
157
+ this.sockets.forEach((socket) => void callback(socket));
158
+ }
159
+ }
160
+
161
+ return MockIO;
162
+ });
163
+
164
+ describe('simultaneous moves on server game', () => {
165
+ const game: Game = {
166
+ name: 'test',
167
+ setup: () => ({
168
+ players: {
169
+ '0': {
170
+ cards: ['card3'],
171
+ },
172
+ '1': {
173
+ cards: [],
174
+ },
175
+ },
176
+ cards: ['card0', 'card1', 'card2'],
177
+ discardedCards: [],
178
+ }),
179
+ playerView: PlayerView.STRIP_SECRETS,
180
+ turn: {
181
+ activePlayers: { currentPlayer: { stage: 'A' } },
182
+ stages: {
183
+ A: {
184
+ moves: {
185
+ A: {
186
+ client: false,
187
+ move: ({ G, playerID }) => {
188
+ const card = G.players[playerID].cards.shift();
189
+ G.discardedCards.push(card);
190
+ },
191
+ },
192
+ B: {
193
+ client: false,
194
+ ignoreStaleStateID: true,
195
+ move: ({ G, playerID }) => {
196
+ const card = G.cards.pop();
197
+ G.players[playerID].cards.push(card);
198
+ },
199
+ },
200
+ },
201
+ },
202
+ },
203
+ },
204
+ };
205
+
206
+ let app;
207
+ let transport: SocketIOTestAdapter;
208
+ let clientInfo;
209
+ let roomInfo;
210
+ let io;
211
+
212
+ beforeEach(async () => {
213
+ clientInfo = new Map();
214
+ roomInfo = new Map();
215
+ });
216
+
217
+ test('two clients playing using sync storage', async () => {
218
+ const db = new InMemory();
219
+ const auth = new Auth();
220
+ app = { context: { db, auth } };
221
+ transport = new SocketIOTestAdapter({
222
+ clientInfo,
223
+ roomInfo,
224
+ });
225
+ transport.init(app, [ProcessGameConfig(game)]);
226
+ io = app.context.io;
227
+ const socket0 = io.sockets.get('0');
228
+ const socket1 = io.sockets.get('1');
229
+
230
+ const spyGetMatchQueue = jest.spyOn(
231
+ SocketIOTestAdapter.prototype,
232
+ 'getMatchQueue'
233
+ );
234
+ const spyDeleteMatchQueue = jest.spyOn(
235
+ SocketIOTestAdapter.prototype,
236
+ 'deleteMatchQueue'
237
+ );
238
+
239
+ db.createMatch('matchID', {
240
+ initialState: InitializeGame({ game, numPlayers: 2 }),
241
+ metadata: createMetadata({
242
+ game: game,
243
+ unlisted: false,
244
+ numPlayers: 2,
245
+ }),
246
+ });
247
+
248
+ // Call sync for both players
249
+ await Promise.all([
250
+ (async () => {
251
+ const args0: SyncArgs = ['matchID', '0', undefined, 2];
252
+ await socket0.receive('sync', ...args0);
253
+ })(),
254
+ (async () => {
255
+ const args1: SyncArgs = ['matchID', '1', undefined, 2];
256
+ await socket1.receive('sync', ...args1);
257
+ })(),
258
+ ]);
259
+
260
+ const moveAArgs: UpdateArgs = [
261
+ ActionCreators.makeMove('A', null, '0'),
262
+ 0,
263
+ 'matchID',
264
+ '0',
265
+ ];
266
+
267
+ // Call normal move
268
+ await socket0.receive('update', ...moveAArgs);
269
+
270
+ // Assertions for match queue creation
271
+ expect(spyGetMatchQueue).toHaveBeenCalledWith('matchID');
272
+
273
+ const activePlayersArgs: UpdateArgs = [
274
+ ActionCreators.gameEvent('setActivePlayers', [{ all: 'A' }], '0'),
275
+ 1,
276
+ 'matchID',
277
+ '0',
278
+ ];
279
+
280
+ // Set all players active
281
+ await socket0.receive('update', ...activePlayersArgs);
282
+
283
+ // Call actions simultaeously
284
+ await Promise.all([
285
+ (async () => {
286
+ const moveBArgs: UpdateArgs = [
287
+ ActionCreators.makeMove('B', null, '1'),
288
+ 2,
289
+ 'matchID',
290
+ '1',
291
+ ];
292
+ await socket1.receive('update', ...moveBArgs);
293
+ })(),
294
+ (async () => {
295
+ const moveBArgs: UpdateArgs = [
296
+ ActionCreators.makeMove('B', null, '0'),
297
+ 2,
298
+ 'matchID',
299
+ '0',
300
+ ];
301
+ await socket0.receive('update', ...moveBArgs);
302
+ })(),
303
+ ]);
304
+
305
+ const fetchResult = db.fetch('matchID', {
306
+ state: true,
307
+ metadata: true,
308
+ log: true,
309
+ });
310
+
311
+ expect(fetchResult.state.G).toMatchObject({
312
+ players: {
313
+ '0': {
314
+ cards: ['card1'],
315
+ },
316
+ '1': {
317
+ cards: ['card2'],
318
+ },
319
+ },
320
+ cards: ['card0'],
321
+ discardedCards: ['card3'],
322
+ });
323
+
324
+ // Call disconnect for both players
325
+ await Promise.all([
326
+ (async () => {
327
+ await socket0.receive('disconnect');
328
+ })(),
329
+ (async () => {
330
+ await socket1.receive('disconnect');
331
+ })(),
332
+ ]);
333
+
334
+ expect(spyDeleteMatchQueue).toHaveBeenCalledWith('matchID');
335
+
336
+ db.wipe('matchID');
337
+ });
338
+
339
+ test('two clients playing using async storage', async () => {
340
+ const db = new InMemoryAsync();
341
+ await db.connect();
342
+ const auth = new Auth();
343
+
344
+ app = { context: { db, auth } };
345
+ transport = new SocketIOTestAdapter({
346
+ clientInfo,
347
+ roomInfo,
348
+ });
349
+ transport.init(app, [ProcessGameConfig(game)]);
350
+ io = app.context.io;
351
+ const socket0 = io.sockets.get('0');
352
+ const socket1 = io.sockets.get('1');
353
+
354
+ const spyGetMatchQueue = jest.spyOn(
355
+ SocketIOTestAdapter.prototype,
356
+ 'getMatchQueue'
357
+ );
358
+ const spyDeleteMatchQueue = jest.spyOn(
359
+ SocketIOTestAdapter.prototype,
360
+ 'deleteMatchQueue'
361
+ );
362
+
363
+ await db.createMatch('matchID', {
364
+ initialState: InitializeGame({ game, numPlayers: 2 }),
365
+ metadata: createMetadata({
366
+ game: game,
367
+ unlisted: false,
368
+ numPlayers: 2,
369
+ }),
370
+ });
371
+
372
+ // Call sync for both players
373
+ await Promise.all([
374
+ (async () => {
375
+ await socket0.receive('sync', 'matchID', '0', 2);
376
+ })(),
377
+ (async () => {
378
+ await socket1.receive('sync', 'matchID', '1', 2);
379
+ })(),
380
+ ]);
381
+
382
+ const moveAArgs: UpdateArgs = [
383
+ ActionCreators.makeMove('A', null, '0'),
384
+ 0,
385
+ 'matchID',
386
+ '0',
387
+ ];
388
+
389
+ // Call normal move
390
+ await socket0.receive('update', ...moveAArgs);
391
+
392
+ // Assertions for match queue creation
393
+ expect(spyGetMatchQueue).toHaveBeenCalledWith('matchID');
394
+
395
+ const activePlayersArgs: UpdateArgs = [
396
+ ActionCreators.gameEvent('setActivePlayers', [{ all: 'A' }], '0'),
397
+ 1,
398
+ 'matchID',
399
+ '0',
400
+ ];
401
+
402
+ // Set all players active
403
+ await socket0.receive('update', ...activePlayersArgs);
404
+
405
+ // Call actions simultaeously
406
+ await Promise.all([
407
+ (async () => {
408
+ const moveBArgs: UpdateArgs = [
409
+ ActionCreators.makeMove('B', null, '1'),
410
+ 2,
411
+ 'matchID',
412
+ '1',
413
+ ];
414
+ await socket1.receive('update', ...moveBArgs);
415
+ })(),
416
+ (async () => {
417
+ const moveBArgs: UpdateArgs = [
418
+ ActionCreators.makeMove('B', null, '0'),
419
+ 2,
420
+ 'matchID',
421
+ '0',
422
+ ];
423
+ await socket0.receive('update', ...moveBArgs);
424
+ })(),
425
+ ]);
426
+
427
+ const fetchResult = await db.fetch('matchID', {
428
+ state: true,
429
+ metadata: true,
430
+ log: true,
431
+ });
432
+
433
+ expect(fetchResult.state.G).toMatchObject({
434
+ players: {
435
+ '0': {
436
+ cards: ['card1'],
437
+ },
438
+ '1': {
439
+ cards: ['card2'],
440
+ },
441
+ },
442
+ cards: ['card0'],
443
+ discardedCards: ['card3'],
444
+ });
445
+
446
+ // Call disconnect for both players
447
+ await Promise.all([
448
+ (async () => {
449
+ await socket0.receive('disconnect');
450
+ })(),
451
+ (async () => {
452
+ await socket1.receive('disconnect');
453
+ })(),
454
+ ]);
455
+
456
+ expect(spyDeleteMatchQueue).toHaveBeenCalledWith('matchID');
457
+
458
+ await db.wipe('matchID');
459
+ });
460
+ });
461
+
462
+ describe('inauthentic clients', () => {
463
+ const game: Game = {
464
+ setup: () => ({
465
+ 0: 'foo',
466
+ 1: 'bar',
467
+ }),
468
+ playerView: ({ G, playerID }) => ({ [playerID]: G[playerID] }),
469
+ };
470
+
471
+ let app;
472
+ let db: InMemoryAsync;
473
+ let transport: SocketIOTestAdapter;
474
+ let clientInfo: Map<string, Record<string, any>>;
475
+ let roomInfo: Map<string, Set<string>>;
476
+ let io;
477
+ const matchID = 'matchID';
478
+ const cred0 = 'password-0';
479
+ const cred1 = 'password-1';
480
+
481
+ beforeEach(async () => {
482
+ clientInfo = new Map();
483
+ roomInfo = new Map();
484
+
485
+ db = new InMemoryAsync();
486
+ await db.connect();
487
+
488
+ app = { context: { db, auth: new Auth() } };
489
+ transport = new SocketIOTestAdapter({ clientInfo, roomInfo });
490
+ transport.init(app, [ProcessGameConfig(game)]);
491
+ io = app.context.io;
492
+
493
+ // Create credentialed match.
494
+ const metadata = createMetadata({ game, unlisted: false, numPlayers: 2 });
495
+ metadata.players[0].credentials = cred0;
496
+ metadata.players[1].credentials = cred1;
497
+ const initialState = InitializeGame({ game, numPlayers: 2 });
498
+ await db.createMatch(matchID, { initialState, metadata });
499
+ });
500
+
501
+ afterEach(async () => {
502
+ await db.wipe(matchID);
503
+ });
504
+
505
+ test('inauthentic client is not added to clientInfo', async () => {
506
+ const inauthenticID = '0';
507
+ const socket = io.sockets.get(inauthenticID);
508
+
509
+ const args: Parameters<Master['onSync']> = ['matchID', '0', undefined];
510
+ const doneReceiving = socket.receive('sync', ...args);
511
+ // Is not added before awaiting the game master.
512
+ expect(clientInfo.get(inauthenticID)).toBeUndefined();
513
+ await doneReceiving;
514
+ // Is not added after awaiting the game master.
515
+ expect(clientInfo.get(inauthenticID)).toBeUndefined();
516
+ });
517
+
518
+ test('connected inauthentic client doesn’t receive authentic client’s sync', async () => {
519
+ const inauthenticID = '0';
520
+ const authenticID = '1';
521
+ const inauthenticSocket = io.sockets.get(inauthenticID);
522
+ const authenticSocket = io.sockets.get(authenticID);
523
+
524
+ // Call sync for both players
525
+ {
526
+ const args: Parameters<Master['onSync']> = ['matchID', '0', undefined];
527
+ await inauthenticSocket.receive('sync', ...args);
528
+ }
529
+ {
530
+ const args: Parameters<Master['onSync']> = ['matchID', '0', cred0];
531
+ await authenticSocket.receive('sync', ...args);
532
+ }
533
+
534
+ expect(clientInfo.get(inauthenticID)).toBeUndefined();
535
+
536
+ expect(clientInfo.get(authenticID)).toEqual({
537
+ matchID,
538
+ playerID: '0',
539
+ credentials: cred0,
540
+ socket: authenticSocket,
541
+ });
542
+
543
+ expect(inauthenticSocket.emit).not.toHaveBeenCalled();
544
+
545
+ expect(authenticSocket.emit).toHaveBeenCalledWith(
546
+ 'sync',
547
+ matchID,
548
+ expect.objectContaining({
549
+ state: expect.objectContaining({
550
+ G: {
551
+ 0: 'foo',
552
+ },
553
+ }),
554
+ })
555
+ );
556
+
557
+ const syncEmits = authenticSocket.emit.mock.calls.filter(
558
+ ([type]) => type === 'sync'
559
+ );
560
+ expect(syncEmits).toHaveLength(1);
561
+ });
562
+
563
+ test('inauthentic client doesn’t receive authentic client’s sync while still syncing', async () => {
564
+ const inauthenticID = '0';
565
+ const authenticID = '1';
566
+ const inauthenticSocket = io.sockets.get(inauthenticID);
567
+ const authenticSocket = io.sockets.get(authenticID);
568
+
569
+ // Make db#fetch in Master#onSync for first sync resolve after second sync.
570
+ db.delays = [200, 0];
571
+
572
+ // Call sync for both players
573
+ await Promise.all([
574
+ (async () => {
575
+ const args: Parameters<Master['onSync']> = ['matchID', '0', undefined];
576
+ await inauthenticSocket.receive('sync', ...args);
577
+ })(),
578
+ (async () => {
579
+ const args: Parameters<Master['onSync']> = ['matchID', '0', cred0];
580
+ await authenticSocket.receive('sync', ...args);
581
+ })(),
582
+ ]);
583
+
584
+ expect(clientInfo.get(inauthenticID)).toBeUndefined();
585
+
586
+ expect(authenticSocket.emit).toHaveBeenCalledWith(
587
+ 'sync',
588
+ matchID,
589
+ expect.objectContaining({
590
+ state: expect.objectContaining({
591
+ G: {
592
+ 0: 'foo',
593
+ },
594
+ }),
595
+ })
596
+ );
597
+
598
+ const syncEmits = authenticSocket.emit.mock.calls.filter(
599
+ ([type]) => type === 'sync'
600
+ );
601
+ expect(syncEmits).toHaveLength(1);
602
+ });
603
+ });