UNPKG

3.08 kBJavaScriptView Raw
1import { REMOTE_ACTION, hashState, Keys } from "@cardcore/util";
2
3export default function createGameMiddleware(gameActions, clientActions) {
4 const { clientFetch, clientPoll, clientNext } = clientActions;
5
6 return function gameMiddleware(store) {
7 return next => {
8 return async action => {
9 // Hacky, but we need this in the store before anything.
10
11 // First thing first... implement thunk.
12 if (typeof action === "function") {
13 return action(store.dispatch, store.getState);
14 }
15
16 // Next... if it's not a game action, pass through as a promise.
17 if (!gameActions[action.type]) {
18 return Promise.resolve(next(action));
19 }
20
21 // Okay, it's a game action. Great! Let's handle some special cases first.
22
23 // Special case: we're loading the state from the server. Pass through.
24
25 // Resolve the action.
26 const prevState = store.getState();
27 const me = prevState.client.keys.id;
28 let prevHash = null;
29 // Special case until we have an actual blockchain to build on
30 if (action.type !== gameActions.CREATE_GAME) {
31 prevHash = hashState(prevState);
32 }
33 if (!action[REMOTE_ACTION]) {
34 action = {
35 ...action,
36 agent: me,
37 prev: prevHash
38 };
39 } else {
40 if (action.prev !== prevHash) {
41 throw new Error(`hash mismatch, ${action.next} !== ${nextHash}`);
42 }
43 }
44 next(action);
45 const nextState = store.getState();
46 const nextHash = hashState(nextState);
47
48 if (action[REMOTE_ACTION]) {
49 if (action.next !== nextHash) {
50 throw new Error(`hash mismatch, ${action.next} !== ${nextHash}`);
51 }
52 } else {
53 action = {
54 ...action,
55 next: nextHash
56 };
57 }
58
59 // Resolved. Are we the user making this action? Neato! Let's tell the server about it.
60 if (!action[REMOTE_ACTION]) {
61 const signedAction = Keys.signAction(prevState, action);
62 let res;
63 try {
64 res = await store.dispatch(
65 clientFetch(`/${encodeURIComponent(nextHash)}`, {
66 method: "POST",
67 body: signedAction,
68 headers: {
69 "content-type": "application/json"
70 }
71 })
72 );
73 } catch (e) {
74 throw new Error(
75 "Failed to create an action. We should probably handle this error."
76 );
77 }
78 if (!res.ok) {
79 throw new Error(await res.text());
80 }
81 }
82
83 // Are we _not_ the user making this action? Okay, let's verify it.
84 else {
85 if (!Keys.verifyAction(prevState, action)) {
86 throw new Error(
87 `Remote action failed to verify: ${JSON.stringify(action)}`
88 );
89 }
90 }
91
92 await store.dispatch(clientNext());
93 store.dispatch(clientPoll());
94 };
95 };
96 };
97}