On update + clocks thinking
[vchess.git] / client / src / utils / storage.js
1 import { extractTime } from "@/utils/timeControl";
2
3 // TODO: show game structure
4 //const newItem = [
5 // { gameId: "", players: [], timeControl: "", clocks: [] }
6 //];
7
8 function dbOperation(callback)
9 {
10 let db = null;
11 let DBOpenRequest = window.indexedDB.open("vchess", 4);
12
13 DBOpenRequest.onerror = function(event) {
14 alert("Database error: " + event.target.errorCode);
15 };
16
17 DBOpenRequest.onsuccess = function(event) {
18 db = DBOpenRequest.result;
19 callback(db);
20 db.close();
21 };
22
23 DBOpenRequest.onupgradeneeded = function(event) {
24 let db = event.target.result;
25 db.onerror = function(event) {
26 alert("Error while loading database: " + event.target.errorCode);
27 };
28 // Create objectStore for vchess->games
29 db.createObjectStore("games", { keyPath: "gameId" });
30 }
31 }
32
33 // Optional callback to get error status
34 function addGame(game, callback)
35 {
36 dbOperation((db) => {
37 let transaction = db.transaction(["games"], "readwrite");
38 if (callback)
39 {
40 transaction.oncomplete = function() {
41 callback({}); //everything's fine
42 }
43 transaction.onerror = function() {
44 callback({errmsg: "addGame failed: " + transaction.error});
45 };
46 }
47 let objectStore = transaction.objectStore("games");
48 objectStore.add(game);
49 });
50 }
51
52 // Clear current live game from localStorage
53 function clear() {
54 localStorage.deleteItem("gameInfo");
55 localStorage.deleteItem("gameState");
56 }
57
58 // Current live game:
59 function getCurrent()
60 {
61 return Object.assign({},
62 JSON.parse(localStorage.getItem("gameInfo")),
63 JSON.parse(localStorage.getItem("gameState")));
64 }
65
66 // Only called internally after a score update
67 function transferToDb()
68 {
69 addGame(getCurrent(), (err) => {
70 if (!!err.errmsg)
71 return err;
72 clear();
73 });
74 }
75
76 export const GameStorage =
77 {
78 // localStorage:
79 init: function(o)
80 {
81 // Extract times (in [milli]seconds), set clocks, store in localStorage
82 const tc = extractTime(o.timeControl);
83
84 // game infos: constant
85 const gameInfo =
86 {
87 gameId: o.gameId,
88 vname: o.vname,
89 fenStart: o.fenStart,
90 players: o.players,
91 timeControl: o.timeControl,
92 increment: tc.increment,
93 mode: "live", //function for live games only
94 };
95
96 // game state: will be updated
97 const gameState =
98 {
99 fen: o.fenStart,
100 moves: [],
101 clocks: [...Array(o.players.length)].fill(tc.mainTime),
102 started: [...Array(o.players.length)].fill(false),
103 score: "*",
104 };
105
106 localStorage.setItem("gameInfo", JSON.stringify(gameInfo));
107 localStorage.setItem("gameState", JSON.stringify(gameState));
108 },
109
110 // localStorage:
111 // TODO: also option to takeback a move ?
112 // NOTE: for live games only (all on server for corr)
113 update: function(o) //move, clock, initime, score, colorIdx
114 {
115 // TODO: finish this --> colorIdx must be computed before entering the function
116 let gameState = JSON.parse(localStorage.getItem("gameState"));
117 if (!!o.move)
118 {
119 // https://stackoverflow.com/a/38750895
120 const allowed = ['appear', 'vanish', 'start', 'end'];
121 const filtered_move = Object.keys(o.move)
122 .filter(key => allowed.includes(key))
123 .reduce((obj, key) => {
124 obj[key] = raw[key];
125 return obj;
126 }, {});
127 gameState.moves.push(filtered_move);
128 gameState.fen = o.move.fen;
129 const colorIdx = ["w","b","g","r"][o.move.color];
130 gameState.clocks[colorIdx] = o.move.clock;
131 }
132 if (!!o.initime) //just a flag (true)
133 gameState.initime = Date.now();
134 if (!!o.score)
135 gameState.score = o.score;
136 localStorage.setItem("gameState", JSON.stringify(gameState));
137 if (!!o.score && o.score != "*")
138 transferToDb(); //game is over
139 },
140
141 // indexedDB:
142 // Since DB requests are asynchronous, require a callback using the result
143 // TODO: option for remote retrieval (third arg, or just "gameRef")
144 getLocal: function(gameId, callback)
145 {
146 let games = [];
147 dbOperation((db) => {
148 // TODO: if gameId is provided, limit search to gameId (just .get(gameId). ...)
149 let objectStore = db.transaction('games').objectStore('games');
150 objectStore.openCursor().onsuccess = function(event) {
151 var cursor = event.target.result;
152 // if there is still another cursor to go, keep runing this code
153 if (cursor)
154 {
155 games.push(cursor.value);
156 cursor.continue();
157 }
158 else
159 callback(games);
160 }
161 });
162 },
163
164 // Delete a game in indexedDB
165 remove: function(gameId, callback)
166 {
167 dbOperation((db) => {
168 let transaction = db.transaction(["games"], "readwrite");
169 if (callback)
170 {
171 transaction.oncomplete = function() {
172 callback({}); //everything's fine
173 }
174 transaction.onerror = function() {
175 callback({errmsg: "deleteGame failed: " + transaction.error});
176 };
177 }
178 transaction.objectStore("games").delete(gameId);
179 });
180 },
181
182 // Retrieve any live game from its identifiers (remote or not, running or not)
183 // NOTE: need callback because result might be obtained asynchronously
184 get: function(gameRef, callback)
185 {
186 const gid = gameRef.id;
187 const rid = gameRef.rid; //may be blank
188 if (!!rid)
189 {
190 // TODO: send request to server which forward to user sid == rid,
191 // need to listen to "remote game" event in main hall ?
192 return callback({}); //means "the game will arrive later" (TODO...)
193 }
194
195 const gameInfoStr = localStorage.getItem("gameInfo");
196 if (gameInfoStr)
197 {
198 const gameInfo = JSON.parse(gameInfoStr);
199 if (gameInfo.gameId == gid)
200 {
201 const gameState = JSON.parse(localStorage.getItem("gameState"));
202 return callback(Object.assign({}, gameInfo, gameState));
203 }
204 }
205
206 // Game is local and not running
207 GameStorage.getLocal(gid, callback);
208 },
209 };