const toast = require('izitoast'); const cbor = require('borc'); const testGame = require('./test.game'); const SOCKET_URL = process.env.NODE_ENV === 'production' ? 'wss://cryps.gg/ws' : 'ws://localhost:40000'; function errorToast(err) { console.error(err); return toast.error({ title: 'BEEP BOOP', message: err, position: 'bottomCenter', }); } function createSocket(events) { let ws; // handle account auth within the socket itself // https://www.christian-schneider.net/CrossSiteWebSocketHijacking.html let account; try { account = JSON.parse(localStorage.getItem('account')); } catch (e) { localStorage.removeItem('account'); } // ------------- // Outgoing // ------------- function send(msg) { if (msg.method !== 'ping') console.log('outgoing msg', msg); msg.token = account && account.token && account.token; ws.send(cbor.encode(msg)); } let ping; function sendPing() { ping = Date.now(); send({ method: 'ping', params: {} }); } function sendAccountLogin(name, password) { send({ method: 'account_login', params: { name, password } }); } function sendAccountCreate(name, password) { send({ method: 'account_create', params: { name, password } }); } function sendAccountDemo() { send({ method: 'account_demo', params: {} }); } function sendAccountCryps() { send({ method: 'account_cryps', params: {} }); } function sendAccountInstances() { send({ method: 'account_instances', params: {} }); } function sendAccountZone() { send({ method: 'account_zone', params: {} }); } function sendCrypSpawn(name) { send({ method: 'cryp_spawn', params: { name } }); } function sendGameState(id) { send({ method: 'game_state', params: { id } }); } function sendGameReady(id) { send({ method: 'game_ready', params: { id } }); } function sendSpecForget(id, spec) { send({ method: 'cryp_unspec', params: { id, spec } }); } function sendPlayerMmCrypsSet(crypIds) { send({ method: 'player_mm_cryps_set', params: { cryp_ids: crypIds } }); } function sendInstanceState(instanceId) { send({ method: 'instance_state', params: { instance_id: instanceId } }); } function sendVboxAccept(instanceId, group, index) { send({ method: 'player_vbox_accept', params: { instance_id: instanceId, group, index } }); } function sendVboxApply(instanceId, crypId, index) { send({ method: 'player_vbox_apply', params: { instance_id: instanceId, cryp_id: crypId, index } }); events.setActiveVar(null); } function sendVboxUnequip(instanceId, crypId, target) { send({ method: 'player_vbox_unequip', params: { instance_id: instanceId, cryp_id: crypId, target } }); events.clearInfo(); } function sendVboxDiscard(instanceId) { send({ method: 'player_vbox_discard', params: { instance_id: instanceId } }); } function sendVboxCombine(instanceId, indices) { send({ method: 'player_vbox_combine', params: { instance_id: instanceId, indices } }); events.clearCombiner(); } function sendVboxReclaim(instanceId, index) { send({ method: 'player_vbox_reclaim', params: { instance_id: instanceId, index } }); } function sendVboxInfo() { send({ method: 'vbox_info', params: {} }); } function sendGameSkill(gameId, crypId, targetCrypId, skill) { send({ method: 'game_skill', params: { game_id: gameId, cryp_id: crypId, target_cryp_id: targetCrypId, skill, }, }); events.setActiveSkill(null); } function sendGameTarget(gameId, crypId, skillId) { send({ method: 'game_target', params: { game_id: gameId, cryp_id: crypId, skill_id: skillId } }); events.setActiveSkill(null); } function sendZoneCreate() { send({ method: 'zone_create', params: {} }); } function sendZoneJoin(zoneId, nodeId, crypIds) { send({ method: 'zone_join', params: { zone_id: zoneId, node_id: nodeId, cryp_ids: crypIds } }); } function sendZoneClose(zoneId) { send({ method: 'zone_close', params: { zone_id: zoneId } }); } function sendInstanceJoin(instanceId, cryps) { send({ method: 'instance_join', params: { instance_id: instanceId, cryp_ids: cryps } }); } function sendInstanceNew(cryps, name, players) { send({ method: 'instance_new', params: { cryp_ids: cryps, name, players } }); } function sendInstanceReady(instanceId) { send({ method: 'instance_ready', params: { instance_id: instanceId } }); } function sendInstanceScores(instanceId) { send({ method: 'instance_scores', params: { instance_id: instanceId } }); } // ------------- // Incoming // ------------- function accountLogin(res) { const [struct, login] = res; account = login; localStorage.setItem('account', JSON.stringify(login)); events.setAccount(login); sendAccountCryps(); sendAccountInstances(); } function accountInstanceList(res) { const [struct, playerList] = res; sendVboxInfo(); events.setInstanceList(playerList); } function accountCryps(response) { const [structName, cryps] = response; events.setCrypList(cryps); } function gameState(response) { const [structName, game] = response; events.setGame(game); } let gameStateTimeout; function startGameStateTimeout(id) { clearTimeout(gameStateTimeout); gameStateTimeout = setTimeout(() => sendGameState(id), 1000); return true; } function clearGameStateTimeout() { clearTimeout(gameStateTimeout); } function crypSpawn(response) { const [structName, cryp] = response; } function zoneState(response) { const [structName, zone] = response; events.setZone(zone); } let instanceStateTimeout; function startInstanceStateTimeout(id) { clearTimeout(instanceStateTimeout); instanceStateTimeout = setTimeout(() => sendInstanceState(id), 1000); return true; } function instanceState(response) { const [structName, i] = response; events.setInstance(i); return true; } function clearInstanceStateTimeout() { clearTimeout(instanceStateTimeout); } function vboxInfo(response) { const [structName, info] = response; events.setVboxInfo(info); } function pong() { events.setPing(Date.now() - ping); setTimeout(sendPing, 1000); } // ------------- // Setup // ------------- // when the server sends a reply it will have one of these message types // this object wraps the reply types to a function const handlers = { cryp_spawn: crypSpawn, game_state: gameState, account_login: accountLogin, account_create: accountLogin, account_cryps: accountCryps, account_instances: accountInstanceList, zone_create: res => console.log(res), zone_state: zoneState, zone_close: res => console.log(res), instance_state: instanceState, vbox_info: vboxInfo, pong, }; function logout() { localStorage.removeItem('account'); account = null; event.setAccount(null); } function errHandler(error) { switch (error) { case 'invalid token': return logout(); case 'no active zone': return sendZoneCreate(); case 'no cryps selected': return events.errorPrompt('select_cryps'); case 'node requirements not met': return events.errorPrompt('complete_nodes'); case 'cryp at max skills (4)': return events.errorPrompt('max_skills'); default: return errorToast(error); } } // decodes the cbor and // calls the handlers defined above based on message type function onMessage(event) { // decode binary msg from server const blob = new Uint8Array(event.data); const res = cbor.decode(blob); const { method, params } = res; if (method !== 'pong' ) console.log(res); // check for error and split into response type and data if (res.err) return errHandler(res.err); if (!handlers[method]) return errorToast(`${method} handler missing`); return handlers[method](params); } function connect() { ws = new WebSocket(SOCKET_URL); ws.binaryType = 'arraybuffer'; // Connection opened ws.addEventListener('open', () => { toast.info({ message: 'connected', position: 'bottomCenter', }); if (account) { events.setAccount(account); sendAccountInstances(); sendAccountCryps(); // events.setGame(testGame(account.id)); // clearGameStateTimeout(); } sendPing(); return true; }); // Listen for messages ws.addEventListener('message', onMessage); ws.addEventListener('error', (event) => { console.error('WebSocket error', event); // account = null; // return setTimeout(connect, 5000); }); ws.addEventListener('close', (event) => { console.error('WebSocket closed', event); toast.warning({ message: 'disconnected', position: 'bottomCenter', }); return setTimeout(connect, 5000); }); return ws; } return { clearGameStateTimeout, clearInstanceStateTimeout, sendAccountLogin, sendAccountCreate, sendAccountDemo, sendAccountCryps, sendAccountInstances, sendAccountZone, sendGameState, sendGameReady, sendGameSkill, sendGameTarget, sendCrypSpawn, sendSpecForget, sendZoneCreate, sendZoneJoin, sendZoneClose, sendInstanceJoin, sendInstanceReady, sendInstanceNew, sendInstanceScores, sendPlayerMmCrypsSet, sendInstanceState, sendVboxAccept, sendVboxApply, sendVboxReclaim, sendVboxCombine, sendVboxDiscard, sendVboxUnequip, sendVboxInfo, startInstanceStateTimeout, startGameStateTimeout, connect, }; } module.exports = createSocket;