span {{ Object.keys(people).length + " " + st.tr["participant(s):"] }}
span(
v-for="p in Object.values(people)"
- v-if="p.name"
+ v-if="p.focus && !!p.name"
)
| {{ p.name }}
- span.anonymous(v-if="Object.values(people).some(p => !p.name && p.id === 0)")
+ span.anonymous(
+ v-if="Object.values(people).some(p => p.focus && !p.name)"
+ )
| + @nonymous
Chat(
ref="chatcomp"
this.atCreation();
},
mounted: function() {
+ document.addEventListener('visibilitychange', this.visibilityChange);
document
.getElementById("chatWrap")
.addEventListener("click", processModalClick);
}
},
beforeDestroy: function() {
+ document.removeEventListener('visibilitychange', this.visibilityChange);
this.cleanBeforeDestroy();
},
methods: {
+ visibilityChange: function() {
+ // TODO: Use document.hidden? https://webplatform.news/issues/2019-03-27
+ this.send(
+ document.visibilityState == "visible"
+ ? "getfocus"
+ : "losefocus"
+ );
+ },
atCreation: function() {
// 0] (Re)Set variables
this.gameRef.id = this.$route.params["id"];
this.nextIds = JSON.parse(this.$route.query["next"] || "[]");
// Always add myself to players' list
const my = this.st.user;
- this.$set(this.people, my.sid, { id: my.id, name: my.name });
+ this.$set(
+ this.people,
+ my.sid,
+ {
+ id: my.id,
+ name: my.name,
+ focus: true
+ }
+ );
this.game = {
players: [{ name: "" }, { name: "" }],
chats: [],
},
isConnected: function(index) {
const player = this.game.players[index];
- // Is it me ?
+ // Is it me ? In this case no need to bother with focus
if (this.st.user.sid == player.sid || this.st.user.id == player.uid)
// Still have to check for name (because of potential multi-accounts
// on same browser, although this should be rare...)
// Try to find a match in people:
return (
(
- player.sid &&
- Object.keys(this.people).some(sid => sid == player.sid)
+ !!player.sid &&
+ Object.keys(this.people).some(sid =>
+ sid == player.sid && this.people[sid].focus)
)
||
(
player.uid &&
- Object.values(this.people).some(p => p.id == player.uid)
+ Object.values(this.people).some(p =>
+ p.id == player.uid && p.focus)
)
);
},
switch (data.code) {
case "pollclients":
data.sockIds.forEach(sid => {
- if (sid != this.st.user.sid)
+ if (sid != this.st.user.sid) {
+ this.people[sid] = { focus: true };
this.send("askidentity", { target: sid });
+ }
});
break;
case "connect":
if (!this.people[data.from]) {
+ this.people[data.from] = { focus: true };
this.newConnect[data.from] = true; //for self multi-connects tests
this.send("askidentity", { target: data.from });
}
case "mdisconnect":
ArrayFun.remove(this.onMygames, sid => sid == data.from);
break;
+ case "getfocus": {
+ let player = this.people[data.from];
+ if (!!player) {
+ player.focus = true;
+ this.$forceUpdate(); //TODO: shouldn't be required
+ }
+ break;
+ }
+ case "losefocus": {
+ let player = this.people[data.from];
+ if (!!player) {
+ player.focus = false;
+ this.$forceUpdate(); //TODO: shouldn't be required
+ }
+ break;
+ }
case "killed":
// I logged in elsewhere:
this.conn = null;
}
case "identity": {
const user = data.data;
- this.$set(this.people, user.sid, { name: user.name, id: user.id });
+ let player = this.people[user.sid];
+ // player.focus is already set
+ player.name = user.name;
+ player.id = user.id;
+ this.$forceUpdate(); //TODO: shouldn't be required
// If I multi-connect, kill current connexion if no mark (I'm older)
if (this.newConnect[user.sid]) {
if (
)
| {{ st.tr["Observe"] }}
button.player-action(
- v-else-if="st.user.id > 0 && sid != st.user.sid"
+ v-else-if="isFocusedOnHall(sid)"
@click="challenge(sid)"
)
| {{ st.tr["Challenge"] }}
if (this.st.variants.length > 0 && this.newchallenge.vid > 0)
this.loadNewchallVariant();
const my = this.st.user;
- this.$set(this.people, my.sid, { id: my.id, name: my.name, pages: ["/"] });
+ this.$set(
+ this.people,
+ my.sid,
+ {
+ id: my.id,
+ name: my.name,
+ pages: [{ path: "/", focus: true }]
+ }
+ );
// Ask server for current corr games (all but mines)
ajax(
"/games",
this.conn.onclose = this.socketCloseListener;
},
mounted: function() {
+ document.addEventListener('visibilitychange', this.visibilityChange);
["peopleWrap", "infoDiv", "newgameDiv"].forEach(eltName => {
let elt = document.getElementById(eltName);
elt.addEventListener("click", processModalClick);
this.setDisplay("g", showGtype);
},
beforeDestroy: function() {
+ document.removeEventListener('visibilitychange', this.visibilityChange);
this.send("disconnect");
},
methods: {
+ visibilityChange: function() {
+ // TODO: Use document.hidden? https://webplatform.news/issues/2019-03-27
+ this.send(
+ document.visibilityState == "visible"
+ ? "getfocus"
+ : "losefocus"
+ );
+ },
// Helpers:
cadenceFocusIfOpened: function() {
if (event.target.checked)
else elt.nextElementSibling.classList.remove("active");
},
isGamer: function(sid) {
- return this.people[sid].pages.some(p => p.indexOf("/game/") >= 0);
+ return this.people[sid].pages
+ .some(p => p.focus && p.path.indexOf("/game/") >= 0);
+ },
+ isFocusedOnHall: function(sid) {
+ return (
+ // This is meant to challenge people, thus the next 2 conditions:
+ this.st.user.id > 0 &&
+ sid != this.st.user.sid &&
+ this.people[sid].pages.some(p => p.path == "/" && p.focus)
+ );
},
challenge: function(sid) {
// Available, in Hall (only)
// In some game, maybe playing maybe not: show a random one
let gids = [];
this.people[sid].pages.forEach(p => {
- const matchGid = p.match(/[a-zA-Z0-9]+$/);
- if (!!matchGid) gids.push(matchGid[0]);
+ if (p.focus) {
+ const matchGid = p.path.match(/[a-zA-Z0-9]+$/);
+ if (!!matchGid) gids.push(matchGid[0]);
+ }
});
const gid = gids[Math.floor(Math.random() * gids.length)];
const game = this.games.find(g => g.id == gid);
this.send("askidentity", { target: s.sid, page: page });
identityAsked[s.sid] = true;
}
- if (!this.people[s.sid])
+ if (!this.people[s.sid]) {
// Do not set name or id: identity unknown yet
- this.$set(this.people, s.sid, { pages: [page] });
- else if (this.people[s.sid].pages.indexOf(page) < 0)
- this.people[s.sid].pages.push(page);
+ this.people[s.sid] = { pages: [{path: page, focus: true}] };
+ }
+ else if (!(this.people[s.sid].pages.find(p => p.path == page)))
+ this.people[s.sid].pages.push({ path: page, focus: true });
if (!s.page)
// Peer is in Hall
this.send("askchallenge", { target: s.sid });
case "connect":
case "gconnect": {
const page = data.page || "/";
- // NOTE: player could have been polled earlier, but might have logged in then
- // So it's a good idea to ask identity if he was anonymous.
- // But only ask game / challenge if currently disconnected.
+ // Only ask game / challenge if first connexion:
if (!this.people[data.from]) {
- this.$set(this.people, data.from, { pages: [page] });
+ this.people[data.from] = { pages: [{ path: page, focus: true }] };
if (data.code == "connect")
this.send("askchallenge", { target: data.from });
else this.send("askgame", { target: data.from, page: page });
} else {
// Append page if not already in list
- if (this.people[data.from].pages.indexOf(page) < 0)
- this.people[data.from].pages.push(page);
+ if (!(this.people[data.from].pages.find(p => p.path == page)))
+ this.people[data.from].pages.push({ path: page, focus: true });
}
if (!this.people[data.from].name && this.people[data.from].id !== 0) {
// Identity not known yet
}
}
const page = data.page || "/";
- ArrayFun.remove(this.people[data.from].pages, p => p == page);
+ ArrayFun.remove(this.people[data.from].pages, p => p.path == page);
if (this.people[data.from].pages.length == 0)
this.$delete(this.people, data.from);
break;
}
+ case "getfocus":
+ // If user reload a page, focus may arrive earlier than connect
+ if (!!this.people[data.from]) {
+ this.people[data.from].pages
+ .find(p => p.path == data.page).focus = true;
+ this.$forceUpdate(); //TODO: shouldn't be required
+ }
+ break;
+ case "losefocus":
+ if (!!this.people[data.from]) {
+ this.people[data.from].pages
+ .find(p => p.path == data.page).focus = false;
+ this.$forceUpdate(); //TODO: shouldn't be required
+ }
+ break;
case "killed":
// I logged in elsewhere:
this.conn = null;
}
case "identity": {
const user = data.data;
- this.$set(this.people, user.sid, {
- id: user.id,
- name: user.name,
- pages: this.people[user.sid].pages
- });
+ let player = this.people[user.sid];
+ // player.pages is already set
+ player.id = user.id;
+ player.name = user.name;
+ // TODO: this.$set(people, ...) fails. So forceUpdate.
+ // But this shouldn't be like that!
+ this.$forceUpdate();
// If I multi-connect, kill current connexion if no mark (I'm older)
if (this.newConnect[user.sid]) {
if (
module.exports = function(wss) {
// Associative array page --> sid --> tmpId --> socket
// "page" is either "/" for hall or "/game/some_gid" for Game,
+ // or "/mygames" for Mygames page (simpler: no 'people' array).
// tmpId is required if a same user (browser) has different tabs
let clients = {};
wss.on("connection", (socket, req) => {
Object.keys(clients[page][k]).forEach(x => {
if (k == sid && x == tmpId) return;
send(
- clients[page][k][x],
+ clients[page][k][x].socket,
+ Object.assign({ code: code, from: sid }, obj)
+ );
+ });
+ });
+ };
+ // For focus events: no need to target self
+ const notifyAllButMe = (page,code,obj={}) => {
+ if (!clients[page]) return;
+ Object.keys(clients[page]).forEach(k => {
+ if (k == sid) return;
+ Object.keys(clients[page][k]).forEach(x => {
+ send(
+ clients[page][k][x].socket,
Object.assign({ code: code, from: sid }, obj)
);
});
// Self multi-connect: manual removal + disconnect
const doKill = (pg) => {
Object.keys(clients[pg][obj.sid]).forEach(x => {
- send(clients[pg][obj.sid][x], {code: "killed"});
+ send(clients[pg][obj.sid][x].socket, { code: "killed" });
});
delete clients[pg][obj.sid];
};
if (k != obj.sid) {
Object.keys(clients[pg][k]).forEach(x => {
send(
- clients[pg][k][x],
+ clients[pg][k][x].socket,
Object.assign({ code: code, from: obj.sid }, o)
);
});
}
const tmpId_idx = Math.floor(Math.random() * tmpIds.length);
send(
- clients[pg][obj.target][tmpIds[tmpId_idx]],
+ clients[pg][obj.target][tmpIds[tmpId_idx]].socket,
{ code: obj.code, from: [sid,tmpId,page] }
);
}
Object.keys(clients[page][obj.target]).forEach(x => {
if (obj.target != sid || x != tmpId)
send(
- clients[page][obj.target][x],
+ clients[page][obj.target][x].socket,
{ code: obj.code, data: obj.data }
);
});
if (!!obj.target && !!clients[page][obj.target]) {
Object.keys(clients[page][obj.target]).forEach(x => {
send(
- clients[page][obj.target][x],
+ clients[page][obj.target][x].socket,
Object.assign({ code: "newmove" }, dataWithFrom)
);
});
!!clients[page][obj.target[0]][obj.target[1]]
) {
send(
- clients[page][obj.target[0]][obj.target[1]],
+ clients[page][obj.target[0]][obj.target[1]].socket,
{ code: "gotmove" }
);
}
Object.keys(clients[pg]).forEach(s => {
Object.keys(clients[pg][s]).forEach(x => {
send(
- clients[pg][s][x],
+ clients[pg][s][x].socket,
{ code: "mconnect", from: sid }
);
});
if (!!clients[gamePg] && !!clients[gamePg][obj.target]) {
Object.keys(clients[gamePg][obj.target]).forEach(x => {
send(
- clients[gamePg][obj.target][x],
+ clients[gamePg][obj.target][x].socket,
{ code: "abort" }
);
});
break;
}
+ case "getfocus":
+ case "losefocus":
+ if (page == "/") notifyAllButMe("/", obj.code, { page: "/" });
+ else {
+ // Notify game room + Hall:
+ notifyAllButMe(page, obj.code);
+ notifyAllButMe("/", obj.code, { page: page });
+ }
+ break;
+
// Passing, relaying something: from isn't needed,
// but target is fully identified (sid + tmpId)
case "challenge":
// but leaving Hall, clients[pg] or clients[pg][target] could be undefined
if (!!clients[pg] && !!clients[pg][obj.target[0]]) {
send(
- clients[pg][obj.target[0]][obj.target[1]],
+ clients[pg][obj.target[0]][obj.target[1]].socket,
{ code:obj.code, data:obj.data }
);
}
doDisconnect();
};
// Update clients object: add new connexion
+ const newElt = { socket: socket, focus: true };
if (!clients[page])
- clients[page] = { [sid]: {[tmpId]: socket } };
+ clients[page] = { [sid]: {[tmpId]: newElt } };
else if (!clients[page][sid])
- clients[page][sid] = { [tmpId]: socket };
+ clients[page][sid] = { [tmpId]: newElt };
else
- clients[page][sid][tmpId] = socket;
+ clients[page][sid][tmpId] = newElt;
socket.on("message", messageListener);
socket.on("close", closeListener);
});