mnml/client/src/socket.jsx
2019-11-12 16:32:19 +11:00

432 lines
11 KiB
JavaScript

const toast = require('izitoast');
const cbor = require('borc');
const throttle = require('lodash/throttle');
const SOCKET_URL =
`${window.location.protocol === 'https:' ? 'wss://' : 'ws://'}${window.location.host}/api/ws`;
function errorToast(err) {
console.error(err);
return toast.error({
title: 'BEEP BOOP',
message: err,
position: 'topRight',
});
}
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[0] !== 'Ping') console.log('outgoing msg', msg);
try {
ws.send(cbor.encode(msg));
} catch (e) {
console.warn(e);
}
}
let ping;
function sendPing() {
ping = Date.now();
send(['Ping', {}]);
}
function sendAccountConstructs() {
send(['AccountConstructs', {}]);
}
function sendAccountInstances() {
send(['AccountInstances', {}]);
}
function sendAccountSetTeam(ids) {
send(['AccountSetTeam', { ids }]);
}
function sendSubscriptionEnding(ending) {
send(['SubscriptionEnding', { ending }]);
}
function sendGameState(id) {
send(['GameState', { id }]);
}
function sendGameReady(id) {
send(['GameReady', { id }]);
}
function sendInstanceState(instanceId) {
send(['InstanceState', { instance_id: instanceId }]);
}
function sendInstanceChat(instanceId, index) {
send(['InstanceChat', { instance_id: instanceId, index }]);
}
function sendVboxAccept(instanceId, group, index) {
send(['VboxAccept', { instance_id: instanceId, group, index }]);
events.clearInstance();
}
function sendVboxAcceptEquip(instanceId, group, index, constructId) {
send(['VboxAcceptEquip', { instance_id: instanceId, group, index, construct_id: constructId }]);
events.clearInstance();
}
function sendVboxApply(instanceId, constructId, index) {
send(['VboxApply', { instance_id: instanceId, construct_id: constructId, index }]);
events.clearInstance();
}
function sendVboxUnequip(instanceId, constructId, target) {
send(['VboxUnequip', { instance_id: instanceId, construct_id: constructId, target }]);
events.clearInstance();
}
function sendVboxUnequipApply(instanceId, constructId, target, targetConstructId) {
send(['VboxUnequipApply', { instance_id: instanceId, construct_id: constructId, target, target_construct_id: targetConstructId }]);
events.clearInstance();
}
function sendVboxDiscard(instanceId) {
send(['VboxDiscard', { instance_id: instanceId }]);
events.clearInstance();
}
function sendVboxCombine(instanceId, indices) {
send(['VboxCombine', { instance_id: instanceId, indices }]);
events.clearInstance();
}
function sendVboxReclaim(instanceId, index) {
send(['VboxReclaim', { instance_id: instanceId, index }]);
events.clearInstance();
}
function sendItemInfo() {
send(['ItemInfo', {}]);
}
function sendGameSkill(gameId, constructId, targetConstructId, skill) {
send(['GameSkill',
{ game_id: gameId, construct_id: constructId, target_construct_id: targetConstructId, skill },
]);
events.setActiveSkill(null);
}
function sendGameSkillClear(gameId) {
send(['GameSkillClear', { game_id: gameId }]);
events.setActiveSkill(null);
}
function sendGameOfferDraw(gameId) {
send(['GameOfferDraw', { game_id: gameId }]);
events.setActiveSkill(null);
}
function sendGameTarget(gameId, constructId, skillId) {
send(['GameTarget', { game_id: gameId, construct_id: constructId, skill_id: skillId }]);
events.setActiveSkill(null);
}
function sendInstancePractice() {
send(['InstancePractice', {}]);
}
function sendInstanceQueue() {
send(['InstanceQueue', {}]);
}
function sendInstanceLeave() {
send(['InstanceLeave', {}]);
}
function sendInstanceInvite() {
send(['InstanceInvite', {}]);
}
function sendInstanceJoin(code) {
send(['InstanceJoin', { code }]);
}
function sendInstanceReady(instanceId) {
send(['InstanceReady', { instance_id: instanceId }]);
}
function sendInstanceAbandon(instanceId) {
send(['InstanceAbandon', { instance_id: instanceId }]);
}
function sendMtxApply(constructId, mtx, name) {
send(['MtxConstructApply', { construct_id: constructId, mtx, name }]);
if (mtx === 'Rename') {
events.clearMtxActive();
events.clearConstructRename();
}
}
function sendMtxAccountApply(mtx) {
send(['MtxAccountApply', { mtx }]);
}
function sendMtxBuy(mtx) {
send(['MtxBuy', { mtx }]);
}
function sendMtxConstructSpawn() {
send(['MtxConstructSpawn', {}]);
}
function sendEmailState() {
send(['EmailState', {}]);
}
function sendSubscriptionState() {
send(['SubscriptionState', {}]);
}
// -------------
// Incoming
// -------------
function onAccount(login) {
events.setAccount(login);
}
function onAccountShop(shop) {
events.setShop(shop);
}
function onEmailState(v) {
events.setEmail(v);
}
function onAccountInstances(list) {
events.setAccountInstances(list);
}
function onAccountConstructs(constructs) {
events.setConstructList(constructs);
}
function onAccountTeam(constructs) {
events.setTeam(constructs);
}
function onSubscriptionState(sub) {
// events.subscriptionState(`Subscription cancelled. Your subscription will remain active until ${exp}. Thank you for your support.`);
events.setSubscription(sub);
}
function onConstructSpawn(construct) {
events.setNewConstruct(construct);
}
function onGameState(game) {
events.setGame(game);
}
function onInstanceState(instance) {
events.setInstance(instance);
}
function onItemInfo(info) {
events.setItemInfo(info);
}
function onDemo(v) {
events.setDemo(v);
}
let pongTimeout;
function onPong() {
events.setPing(Date.now() - ping);
pongTimeout = 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 = {
AccountState: onAccount,
AccountConstructs: onAccountConstructs,
AccountTeam: onAccountTeam,
AccountInstances: onAccountInstances,
AccountShop: onAccountShop,
SubscriptionState: onSubscriptionState,
ConstructSpawn: onConstructSpawn,
GameState: onGameState,
EmailState: onEmailState,
InstanceState: onInstanceState,
ItemInfo: onItemInfo,
Pong: onPong,
Demo: onDemo,
// QueueRequested: () => events.notify('PVP queue request received.'),
QueueRequested: () => true,
QueueJoined: () => {
events.notify('You have joined the PVP queue.');
events.setPvp(true);
},
QueueLeft: () => {
events.notify('You have left the PVP queue.');
events.setPvp(false);
},
QueueFound: () => events.notify('Your PVP game has started.'),
InviteRequested: () => events.notify('PVP invite request received.'),
Invite: code => events.setInvite(code),
InstanceChat: chat => events.setInstanceChat(chat),
ChatWheel: wheel => events.setChatWheel(wheel),
// Joining: () => events.notify('Searching for instance...'),
Processing: () => true,
Error: errHandler,
};
function logout() {
window.location.reload(true);
}
function errHandler(error) {
switch (error) {
case 'invalid token': return logout();
case 'no constructs selected': return events.errorPrompt('select_constructs');
case 'node requirements not met': return events.errorPrompt('complete_nodes');
case 'construct 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 [msgType, params] = res;
if (msgType !== 'Pong') console.log(res);
// check for error and split into response type and data
if (!handlers[msgType]) return errorToast(`${msgType} handler missing`);
return handlers[msgType](params);
}
// Connection opened
function onOpen() {
toast.info({
message: 'connected',
position: 'topRight',
});
sendPing();
sendItemInfo();
events.urlHashChange();
return true;
}
function onError(event) {
console.error('WebSocket error', event);
}
function onClose(event) {
console.error('WebSocket closed', event);
toast.warning({
message: 'disconnected',
position: 'topRight',
});
return setTimeout(connect, 5000);
}
function connect() {
if (ws) {
clearTimeout(pongTimeout);
ws.removeEventListener('open', onOpen);
ws.removeEventListener('message', onMessage);
ws.removeEventListener('error', onError);
ws.removeEventListener('close', onClose);
ws = null;
}
ws = new WebSocket(SOCKET_URL);
ws.binaryType = 'arraybuffer';
// Listen for messages
ws.addEventListener('open', onOpen);
ws.addEventListener('message', onMessage);
ws.addEventListener('error', onError);
ws.addEventListener('close', onClose);
return ws;
}
return {
sendAccountConstructs,
sendAccountInstances,
sendAccountSetTeam,
sendGameState,
sendGameReady,
sendGameSkill,
sendGameSkillClear,
sendGameOfferDraw,
sendGameTarget,
sendInstanceAbandon,
// some weird shit happening in face off
sendInstanceReady: throttle(sendInstanceReady, 500),
sendInstancePractice,
sendInstanceQueue,
sendInstanceState,
sendInstanceInvite,
sendInstanceJoin,
sendInstanceChat,
sendInstanceLeave,
sendVboxAccept,
sendVboxAcceptEquip,
sendVboxApply,
sendVboxReclaim,
sendVboxCombine,
sendVboxDiscard,
sendVboxUnequip,
sendVboxUnequipApply,
sendItemInfo,
sendEmailState,
sendSubscriptionState,
sendSubscriptionEnding,
sendMtxAccountApply,
sendMtxApply,
sendMtxBuy,
sendMtxConstructSpawn,
connect,
};
}
module.exports = createSocket;