townsquare/src/store/session.js

276 lines
6.8 KiB
JavaScript

class LiveSession {
constructor(store) {
this.wss = "wss://connect.websocket.in/v3/";
this.key = "zXzDomOphNQ94tWXrHfT8E8gkxjUMSXOQt0ypZetKoFsIUiEBegqWNAlExyd";
this.socket = null;
this.isSpectator = true;
this.gamestate = [];
this.store = store;
}
/**
* Open a new session for the passed channel.
* @param channel
* @private
*/
_open(channel) {
this.disconnect();
this.socket = new WebSocket(this.wss + channel + "?apiKey=" + this.key);
this.socket.addEventListener("message", this._handleMessage.bind(this));
this.socket.onopen = this._onOpen.bind(this);
this.socket.onclose = () => {
this.socket = null;
this.store.commit("setSessionId", "");
};
}
/**
* Send a message through the socket.
* @param command
* @param params
* @private
*/
_send(command, params) {
if (this.socket) {
this.socket.send(JSON.stringify([command, params]));
}
}
/**
* Open event handler for socket.
* @private
*/
_onOpen() {
if (this.isSpectator) {
this._send("req", "gs");
} else {
this.sendGamestate();
}
}
/**
* Handle an incoming socket message.
* @param data
* @private
*/
_handleMessage({ data }) {
let command, params;
try {
[command, params] = JSON.parse(data);
} catch (err) {
console.log("unsupported socket message", data);
}
switch (command) {
case "req":
if (params === "gs") {
this.sendGamestate();
}
break;
case "gs":
this._updateGamestate(params);
break;
case "player":
this._updatePlayer(params);
}
}
/**
* Connect to a new live session, either as host or spectator.
* @param channel
*/
connect(channel) {
this.isSpectator = this.store.state.session.isSpectator;
this._open(channel);
}
/**
* Close the current session, if any.
*/
disconnect() {
if (this.socket) {
this.socket.close();
this.socket = null;
}
}
/**
* Publish the current gamestate.
*/
sendGamestate() {
if (this.isSpectator) return;
this.gamestate = this.store.state.players.players.map(player => ({
name: player.name,
isDead: player.isDead,
isVoteless: player.isVoteless,
...(player.role && player.role.team === "traveler"
? {
role: {
id: player.role.id,
team: "traveler",
name: player.role.name
}
}
: {})
}));
this._send("gs", {
gamestate: this.gamestate,
edition: this.store.state.edition
});
}
/**
* Update the gamestate based on incoming data.
* @param gamestate
* @param edition
* @private
*/
_updateGamestate({ gamestate, edition }) {
this.store.commit("setEdition", edition);
const players = this.store.state.players.players;
// adjust number of players
if (players.length < gamestate.length) {
for (let x = players.length; x < gamestate.length; x++) {
this.store.commit("players/add", gamestate[x].name);
}
} else if (players.length > gamestate.length) {
for (let x = players.length; x > gamestate.length; x--) {
this.store.commit("players/remove", x - 1);
}
}
// update status for each player
gamestate.forEach((state, x) => {
const player = players[x];
const { name, isDead, isVoteless, role } = state;
if (player.name !== name) {
this.store.commit("players/update", {
player,
property: "name",
value: name
});
}
if (player.isDead !== isDead) {
this.store.commit("players/update", {
player,
property: "isDead",
value: isDead
});
}
if (player.isVoteless !== isVoteless) {
this.store.commit("players/update", {
player,
property: "isVoteless",
value: isVoteless
});
}
if (role && player.role.id !== role.id) {
this.store.commit("players/update", {
player,
property: "role",
value: role
});
} else if (!role && player.role.team === "traveler") {
this.store.commit("players/update", {
player,
property: "role",
value: {}
});
}
});
}
/**
* Publish a player update.
* @param player
* @param property
* @param value
*/
sendPlayer({ player, property, value }) {
if (this.isSpectator || property === "reminders") return;
const index = this.store.state.players.players.indexOf(player);
if (property === "role") {
if (value.team && value.team === "traveler") {
// update local gamestate to remember this player as a traveler
this.gamestate[index].role = {
id: player.role.id,
team: "traveler",
name: player.role.name
};
this._send("player", {
index,
property,
value: this.gamestate[index].role
});
} else if (this.gamestate[index].role) {
delete this.gamestate[index].role;
this._send("player", { index, property, value: {} });
}
} else {
this._send("player", { index, property, value });
}
}
/**
* Update a player based on incoming data.
* @param index
* @param property
* @param value
* @private
*/
_updatePlayer({ index, property, value }) {
const player = this.store.state.players.players[index];
if (!player) return;
// special case where a player stops being a traveler
if (
property === "role" &&
value.team !== "traveler" &&
player.role.team === "traveler"
) {
// reset to an unknown role
this.store.commit("players/update", {
player,
property: "role",
value: {}
});
} else {
// just update the player otherwise
this.store.commit("players/update", { player, property, value });
}
}
}
module.exports = store => {
// setup
const session = new LiveSession(store);
// listen to mutations
store.subscribe(({ type, payload }) => {
switch (type) {
case "setSessionId":
if (payload) {
session.connect(payload);
} else {
window.location.hash = "";
session.disconnect();
}
break;
case "players/set":
case "players/clear":
case "players/remove":
case "players/add":
case "setEdition":
session.sendGamestate();
break;
case "players/update":
session.sendPlayer(payload);
break;
}
});
// check for session Id in hash
const [command, param] = window.location.hash.substr(1).split("/");
if (command === "play") {
store.commit("setSpectator", true);
store.commit("setSessionId", param);
}
};