440 lines
12 KiB
JavaScript
440 lines
12 KiB
JavaScript
const toast = require('izitoast');
|
|
const cbor = require('borc');
|
|
|
|
const throttle = require('lodash/throttle');
|
|
const groupBy = require('lodash/groupBy');
|
|
|
|
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: 'bottomLeft',
|
|
});
|
|
}
|
|
|
|
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 sendVboxBuy(instanceId, group, index) {
|
|
send(['VboxBuy', { instance_id: instanceId, group, index }]);
|
|
events.clearInstance();
|
|
}
|
|
|
|
function sendVboxBuyEquip(instanceId, group, index, constructId) {
|
|
send(['VboxBuy', { 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 sendVboxRefill(instanceId) {
|
|
send(['VboxRefill', { instance_id: instanceId }]);
|
|
events.clearInstance();
|
|
}
|
|
|
|
function sendVboxCombine(instanceId, invIndicies, vboxIndicies) {
|
|
const formatted = {};
|
|
vboxIndicies.forEach(p => formatted[p[0]] ? formatted[p[0]].push(p[1]) : formatted[p[0]] = [p[1]]);
|
|
send(['VboxCombine', { instance_id: instanceId, inv_indices: invIndicies, vbox_indices: formatted }]);
|
|
events.clearInstance();
|
|
}
|
|
|
|
function sendVboxRefund(instanceId, index) {
|
|
send(['VboxRefund', { 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 sendGameConcede(gameId) {
|
|
send(['GameConcede', { 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);
|
|
}
|
|
|
|
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,
|
|
AccountAuthenticated: account => events.setAuthenticated(account),
|
|
AccountConstructs: onAccountConstructs,
|
|
AccountTeam: onAccountTeam,
|
|
AccountInstances: onAccountInstances,
|
|
AccountShop: onAccountShop,
|
|
SubscriptionState: onSubscriptionState,
|
|
ConstructSpawn: onConstructSpawn,
|
|
GameState: onGameState,
|
|
EmailState: onEmailState,
|
|
InstanceState: onInstanceState,
|
|
ItemInfo: onItemInfo,
|
|
Pong: onPong,
|
|
|
|
// 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...'),
|
|
|
|
// StartTutorial: () => events.startTutorial(),
|
|
PromptRegister: () => events.promptRegister(),
|
|
|
|
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: 'bottomLeft',
|
|
});
|
|
|
|
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: 'bottomLeft',
|
|
});
|
|
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,
|
|
sendGameConcede,
|
|
sendGameTarget,
|
|
|
|
sendInstanceAbandon,
|
|
// some weird shit happening in face off
|
|
sendInstanceReady: throttle(sendInstanceReady, 500),
|
|
sendInstancePractice,
|
|
sendInstanceQueue,
|
|
sendInstanceState,
|
|
sendInstanceInvite,
|
|
sendInstanceJoin,
|
|
sendInstanceChat,
|
|
sendInstanceLeave,
|
|
|
|
sendVboxBuy,
|
|
sendVboxBuyEquip,
|
|
sendVboxApply,
|
|
sendVboxRefund,
|
|
sendVboxCombine,
|
|
sendVboxRefill,
|
|
sendVboxUnequip,
|
|
sendVboxUnequipApply,
|
|
|
|
sendItemInfo,
|
|
|
|
sendEmailState,
|
|
sendSubscriptionState,
|
|
sendSubscriptionEnding,
|
|
|
|
sendMtxAccountApply,
|
|
sendMtxApply,
|
|
sendMtxBuy,
|
|
sendMtxConstructSpawn,
|
|
|
|
connect,
|
|
};
|
|
}
|
|
|
|
module.exports = createSocket;
|