Merge pull request #1536 from thelounge/xpaw/session-list
Implement session list and allow signing out other clients
This commit is contained in:
commit
3fc0f4e686
@ -252,6 +252,14 @@ kbd {
|
|||||||
color: #7f8c8d;
|
color: #7f8c8d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.session-list strong {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-list p {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
#chat .invite .from::before {
|
#chat .invite .from::before {
|
||||||
content: "\f003"; /* http://fontawesome.io/icon/envelope-o/ */
|
content: "\f003"; /* http://fontawesome.io/icon/envelope-o/ */
|
||||||
color: #2ecc40;
|
color: #2ecc40;
|
||||||
@ -668,6 +676,7 @@ kbd {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#windows p,
|
||||||
#windows label,
|
#windows label,
|
||||||
#settings .error {
|
#settings .error {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
@ -198,15 +198,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="settings" class="window">
|
<div id="settings" class="window" data-type="settings">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<button class="lt" aria-label="Toggle channel list"></button>
|
<button class="lt" aria-label="Toggle channel list"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<h1 class="title">Settings</h1>
|
<h1 class="title">Settings</h1>
|
||||||
</div>
|
|
||||||
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<h2>Messages</h2>
|
<h2>Messages</h2>
|
||||||
</div>
|
</div>
|
||||||
@ -380,6 +379,18 @@
|
|||||||
<textarea class="input" name="userStyles" id="user-specified-css-input" placeholder="You can override any style with CSS here"></textarea>
|
<textarea class="input" name="userStyles" id="user-specified-css-input" placeholder="You can override any style with CSS here"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{#unless public}}
|
||||||
|
<div class="session-list">
|
||||||
|
<h2>Sessions</h2>
|
||||||
|
|
||||||
|
<h3>Current session</h3>
|
||||||
|
<div id="session-current"></div>
|
||||||
|
|
||||||
|
<h3>Other sessions</h3>
|
||||||
|
<div id="session-list"></div>
|
||||||
|
</div>
|
||||||
|
{{/unless}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -383,8 +383,9 @@ $(function() {
|
|||||||
}
|
}
|
||||||
document.title = title;
|
document.title = title;
|
||||||
|
|
||||||
|
const type = chan.data("type");
|
||||||
var placeholder = "";
|
var placeholder = "";
|
||||||
if (chan.data("type") === "channel" || chan.data("type") === "query") {
|
if (type === "channel" || type === "query") {
|
||||||
placeholder = `Write to ${chan.data("title")}`;
|
placeholder = `Write to ${chan.data("title")}`;
|
||||||
}
|
}
|
||||||
input.attr("placeholder", placeholder);
|
input.attr("placeholder", placeholder);
|
||||||
@ -404,6 +405,11 @@ $(function() {
|
|||||||
socket.emit("names", {target: self.data("id")});
|
socket.emit("names", {target: self.data("id")});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === "settings") {
|
||||||
|
$("#session-list").html("<p>Loading…</p>");
|
||||||
|
socket.emit("sessions:get");
|
||||||
|
}
|
||||||
|
|
||||||
focus();
|
focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -17,3 +17,4 @@ require("./sync_sort");
|
|||||||
require("./topic");
|
require("./topic");
|
||||||
require("./users");
|
require("./users");
|
||||||
require("./sign_out");
|
require("./sign_out");
|
||||||
|
require("./sessions_list");
|
||||||
|
31
client/js/socket-events/sessions_list.js
Normal file
31
client/js/socket-events/sessions_list.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const socket = require("../socket");
|
||||||
|
const templates = require("../../views");
|
||||||
|
|
||||||
|
socket.on("sessions:list", function(data) {
|
||||||
|
data.sort((a, b) => b.lastUse - a.lastUse);
|
||||||
|
|
||||||
|
let html = "";
|
||||||
|
data.forEach((connection) => {
|
||||||
|
if (connection.current) {
|
||||||
|
$("#session-current").html(templates.session(connection));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += templates.session(connection);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (html.length === 0) {
|
||||||
|
html = "<p><em>You are not currently logged in to any other device.</em></p>";
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#session-list").html(html);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#settings").on("click", ".remove-session", function() {
|
||||||
|
socket.emit("sign-out", $(this).data("token"));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
@ -34,6 +34,7 @@ module.exports = {
|
|||||||
msg_unhandled: require("./msg_unhandled.tpl"),
|
msg_unhandled: require("./msg_unhandled.tpl"),
|
||||||
network: require("./network.tpl"),
|
network: require("./network.tpl"),
|
||||||
image_viewer: require("./image_viewer.tpl"),
|
image_viewer: require("./image_viewer.tpl"),
|
||||||
|
session: require("./session.tpl"),
|
||||||
unread_marker: require("./unread_marker.tpl"),
|
unread_marker: require("./unread_marker.tpl"),
|
||||||
user: require("./user.tpl"),
|
user: require("./user.tpl"),
|
||||||
user_filtered: require("./user_filtered.tpl"),
|
user_filtered: require("./user_filtered.tpl"),
|
||||||
|
17
client/views/session.tpl
Normal file
17
client/views/session.tpl
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<p>
|
||||||
|
{{#if current}}
|
||||||
|
<strong>{{agent}}</strong>
|
||||||
|
<a href="https://ipinfo.io/{{ip}}" target="_blank" rel="noopener">{{ip}}</a>
|
||||||
|
{{else}}
|
||||||
|
<button class="btn pull-right remove-session" data-token="{{token}}">Disconnect</button>
|
||||||
|
|
||||||
|
<strong>{{agent}}</strong>
|
||||||
|
<a href="https://ipinfo.io/{{ip}}" target="_blank" rel="noopener">{{ip}}</a>
|
||||||
|
<br>
|
||||||
|
{{#if active}}
|
||||||
|
<em>Currently active</em>
|
||||||
|
{{else}}
|
||||||
|
Last used on <time>{{localetime lastUse}}</time>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
</p>
|
@ -304,11 +304,15 @@ Client.prototype.updateSession = function(token, ip, request) {
|
|||||||
friendlyAgent += ` on ${agent.os.name} ${agent.os.version}`;
|
friendlyAgent += ` on ${agent.os.name} ${agent.os.version}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.config.sessions[token] = _.assign({
|
client.config.sessions[token] = _.assign(client.config.sessions[token], {
|
||||||
lastUse: Date.now(),
|
lastUse: Date.now(),
|
||||||
ip: ip,
|
ip: ip,
|
||||||
agent: friendlyAgent,
|
agent: friendlyAgent,
|
||||||
}, client.config.sessions[token]);
|
});
|
||||||
|
|
||||||
|
client.manager.updateUser(client.name, {
|
||||||
|
sessions: client.config.sessions
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.setPassword = function(hash, callback) {
|
Client.prototype.setPassword = function(hash, callback) {
|
||||||
@ -318,7 +322,6 @@ Client.prototype.setPassword = function(hash, callback) {
|
|||||||
password: hash
|
password: hash
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
log.error("Failed to update password of", client.name, err);
|
|
||||||
return callback(false);
|
return callback(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ ClientManager.prototype.updateUser = function(name, opts, callback) {
|
|||||||
|
|
||||||
fs.writeFile(Helper.getUserConfigPath(name), newUser, (err) => {
|
fs.writeFile(Helper.getUserConfigPath(name), newUser, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
log.error("Failed to update user", err);
|
log.error(`Failed to update user ${colors.green(name)} (${err})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
|
@ -237,7 +237,6 @@ function initializeClient(socket, client, token, lastMessage) {
|
|||||||
socket.on("disconnect", function() {
|
socket.on("disconnect", function() {
|
||||||
client.clientDetach(socket.id);
|
client.clientDetach(socket.id);
|
||||||
});
|
});
|
||||||
client.clientAttach(socket.id, token);
|
|
||||||
|
|
||||||
socket.on(
|
socket.on(
|
||||||
"input",
|
"input",
|
||||||
@ -378,23 +377,59 @@ function initializeClient(socket, client, token, lastMessage) {
|
|||||||
client.unregisterPushSubscription(token);
|
client.unregisterPushSubscription(token);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("sign-out", () => {
|
const sendSessionList = () => {
|
||||||
delete client.config.sessions[token];
|
const sessions = _.map(client.config.sessions, (session, sessionToken) => ({
|
||||||
|
current: sessionToken === token,
|
||||||
|
active: _.find(client.attachedClients, (u) => u.token === sessionToken) !== undefined,
|
||||||
|
lastUse: session.lastUse,
|
||||||
|
ip: session.ip,
|
||||||
|
agent: session.agent,
|
||||||
|
token: sessionToken, // TODO: Ideally don't expose actual tokens to the client
|
||||||
|
}));
|
||||||
|
|
||||||
|
socket.emit("sessions:list", sessions);
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.on("sessions:get", sendSessionList);
|
||||||
|
|
||||||
|
socket.on("sign-out", (tokenToSignOut) => {
|
||||||
|
// If no token provided, sign same client out
|
||||||
|
if (!tokenToSignOut) {
|
||||||
|
tokenToSignOut = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(tokenToSignOut in client.config.sessions)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete client.config.sessions[tokenToSignOut];
|
||||||
|
|
||||||
client.manager.updateUser(client.name, {
|
client.manager.updateUser(client.name, {
|
||||||
sessions: client.config.sessions
|
sessions: client.config.sessions
|
||||||
}, (err) => {
|
|
||||||
if (err) {
|
|
||||||
log.error("Failed to update sessions for", client.name, err);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.emit("sign-out");
|
_.map(client.attachedClients, (attachedClient, socketId) => {
|
||||||
|
if (attachedClient.token !== tokenToSignOut) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const socketToRemove = manager.sockets.of("/").connected[socketId];
|
||||||
|
|
||||||
|
socketToRemove.emit("sign-out");
|
||||||
|
socketToRemove.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Do not send updated session list if user simply logs out
|
||||||
|
if (tokenToSignOut !== token) {
|
||||||
|
sendSessionList();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.join(client.id);
|
socket.join(client.id);
|
||||||
|
|
||||||
const sendInitEvent = (tokenToSend) => {
|
const sendInitEvent = (tokenToSend) => {
|
||||||
|
client.clientAttach(socket.id, token);
|
||||||
|
|
||||||
let networks = client.networks;
|
let networks = client.networks;
|
||||||
|
|
||||||
if (lastMessage > -1) {
|
if (lastMessage > -1) {
|
||||||
@ -423,14 +458,6 @@ function initializeClient(socket, client, token, lastMessage) {
|
|||||||
|
|
||||||
client.updateSession(token, getClientIp(socket.request), socket.request);
|
client.updateSession(token, getClientIp(socket.request), socket.request);
|
||||||
|
|
||||||
client.manager.updateUser(client.name, {
|
|
||||||
sessions: client.config.sessions
|
|
||||||
}, (err) => {
|
|
||||||
if (err) {
|
|
||||||
log.error("Failed to update sessions for", client.name, err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
sendInitEvent(token);
|
sendInitEvent(token);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user