Fixes to improve chat experience. Suppress spurrious ping messages. Try to reconnect when a network connection is restored. Send messages to multiple sessions from the same user.

git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3230 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
Cory McWilliams 2016-05-07 11:07:54 +00:00
parent 1c85875db4
commit e7feab4a4a
3 changed files with 112 additions and 59 deletions

View File

@ -281,8 +281,10 @@ function setErrorMessage(message) {
while (node.firstChild) { while (node.firstChild) {
node.removeChild(node.firstChild); node.removeChild(node.firstChild);
} }
node.appendChild(document.createTextNode(message)); if (message) {
node.setAttribute("style", "display: inline; color: #dc322f"); node.appendChild(document.createTextNode(message));
node.setAttribute("style", "display: inline; color: #dc322f");
}
} }
function send(command) { function send(command) {
@ -429,11 +431,17 @@ function hashChange() {
} }
function focus() { function focus() {
send({event: "focus"}); if (gSocket && gSocket.readyState == gSocket.CLOSED) {
connectSocket();
} else {
send({event: "focus"});
}
} }
function blur() { function blur() {
send({event: "blur"}); if (gSocket && gSocket.readyState == gSocket.OPEN) {
send({event: "blur"});
}
} }
function onMessage(event) { function onMessage(event) {
@ -452,6 +460,39 @@ function submitButton() {
send({event: "submit", value: data}); send({event: "submit", value: data});
} }
function connectSocket() {
if (!gSocket || gSocket.readyState == gSocket.CLOSED) {
gSocket = new WebSocket(
(window.location.protocol == "https:" ? "wss://" : "ws://")
+ window.location.hostname
+ (window.location.port.length ? ":" + window.location.port : "")
+ "/terminal/socket");
gSocket.onopen = function() {
setErrorMessage(null);
gSocket.send(JSON.stringify({
action: "hello",
path: window.location.pathname,
terminalApi: [
['clear'],
['notify', 'title', 'options'],
['postMessageToIframe', 'name', 'message'],
['setHash', 'value'],
['setPassword', 'value'],
['setPrompt', 'value'],
['setTitle', 'value'],
['split', 'options'],
],
}));
}
gSocket.onmessage = function(event) {
receive(JSON.parse(event.data));
}
gSocket.onclose = function(event) {
setErrorMessage("Connection closed with code " + event.code);
}
}
}
window.addEventListener("load", function() { window.addEventListener("load", function() {
if (window.Notification) { if (window.Notification) {
Notification.requestPermission(); Notification.requestPermission();
@ -463,33 +504,7 @@ window.addEventListener("load", function() {
window.addEventListener("focus", focus); window.addEventListener("focus", focus);
window.addEventListener("blur", blur); window.addEventListener("blur", blur);
window.addEventListener("message", onMessage, false); window.addEventListener("message", onMessage, false);
window.addEventListener("online", connectSocket);
enableDragDrop(); enableDragDrop();
connectSocket();
gSocket = new WebSocket(
(window.location.protocol == "https:" ? "wss://" : "ws://")
+ window.location.hostname
+ (window.location.port.length ? ":" + window.location.port : "")
+ "/terminal/socket");
gSocket.onopen = function() {
gSocket.send(JSON.stringify({
action: "hello",
path: window.location.pathname,
terminalApi: [
['clear'],
['notify', 'title', 'options'],
['postMessageToIframe', 'name', 'message'],
['setHash', 'value'],
['setPassword', 'value'],
['setPrompt', 'value'],
['setTitle', 'value'],
['split', 'options'],
],
}));
}
gSocket.onmessage = function(event) {
receive(JSON.parse(event.data));
}
gSocket.onclose = function(event) {
setErrorMessage("Connection closed with code " + event.code);
}
}); });

View File

@ -105,7 +105,7 @@ function configureAccount(id, updates) {
if (schema) { if (schema) {
for (var i in schema) { for (var i in schema) {
let field = schema[i]; let field = schema[i];
terminal.print({input: field.type, name: field.name, value: account[field.name] || field.default}); terminal.print({style: "font-weight: bold", value: field.name + ": "}, {input: field.type, name: field.name, value: account[field.name] || field.default});
} }
} }
} }
@ -154,7 +154,6 @@ function connect(id) {
session.conversations = {}; session.conversations = {};
getConversation(session, {}); getConversation(session, {});
session.getConversations().then(function(conversations) { session.getConversations().then(function(conversations) {
print(conversations);
for (let j in conversations) { for (let j in conversations) {
getConversation(session, {conversation: conversations[j]}); getConversation(session, {conversation: conversations[j]});
} }
@ -207,7 +206,6 @@ function updateConversation() {
gCurrentConversation.session.getHistory(gCurrentConversation.name), gCurrentConversation.session.getHistory(gCurrentConversation.name),
gCurrentConversation.session.getParticipants(gCurrentConversation.name), gCurrentConversation.session.getParticipants(gCurrentConversation.name),
]).then(function(data) { ]).then(function(data) {
print(data);
let history = data[0]; let history = data[0];
let participants = data[1]; let participants = data[1];
gCurrentConversation.messages = history; gCurrentConversation.messages = history;
@ -232,6 +230,7 @@ function updateUsers() {
terminal.clear(); terminal.clear();
terminal.print({style: "font-size: x-large", value: "Users"}); terminal.print({style: "font-size: x-large", value: "Users"});
if (gCurrentConversation) { if (gCurrentConversation) {
gCurrentConversation.participants.sort();
for (var i in gCurrentConversation.participants) { for (var i in gCurrentConversation.participants) {
terminal.print(gCurrentConversation.participants[i]); terminal.print(gCurrentConversation.participants[i]);
} }
@ -283,20 +282,41 @@ function getConversation(session, message) {
} }
function chatCallback(event) { function chatCallback(event) {
print(event); try {
if (event.action == "message") { if (event.action == "message") {
let conversation = getConversation(this.session, event); let conversation = getConversation(this.session, event);
if (conversation == gCurrentConversation) { if (conversation == gCurrentConversation) {
printMessage(event); printMessage(event);
} }
conversation.messages.push(event); conversation.messages.push(event);
if (!gFocus) { if (!gFocus) {
gUnread++; gUnread++;
updateTitle(); updateTitle();
}
} else if (event.action == "presence") {
let conversation = event.jid.split('/', 2)[0];
if (gCurrentConversation.name == conversation) {
let index = gCurrentConversation.participants.indexOf(event.name);
if (event.type == "unavailable") {
if (index != -1) {
gCurrentConversation.participants.splice(index, 1);
updateUsers();
terminal.print(new Date().toString(), ": ", event.name + " has left the room.");
}
} else {
if (index == -1) {
gCurrentConversation.participants.push(event.name);
updateUsers();
terminal.print(new Date().toString(), ": ", event.name + " has joined the room.");
}
}
}
} else {
terminal.print("Unhandled event: ", JSON.stringify(event));
} }
} else { } catch (error) {
terminal.print("Unhandled event: ", JSON.stringify(event)); terminal.print("chatCallback: ", error);
} }
}; };

View File

@ -683,7 +683,7 @@ var gPingCount = 0;
class XmppService { class XmppService {
constructor(options) { constructor(options) {
let self = this; let self = this;
self._callback = options.callback; self._callbacks = [options.callback];
self._conversations = {}; self._conversations = {};
network.newConnection().then(function(socket) { network.newConnection().then(function(socket) {
@ -716,6 +716,21 @@ class XmppService {
return result; return result;
} }
invokeCallback(message) {
let self = this;
for (let i = self._callbacks.length - 1; i >= 0; i--) {
let callback = self._callbacks[i];
try {
callback(message);
} catch (error) {
self._callbacks.splice(i, 1);
// XXX: Send it to the other connections?
print(error);
}
}
}
_connect(options) { _connect(options) {
let self = this; let self = this;
var kTrustedCertificate = "-----BEGIN CERTIFICATE-----\n" + var kTrustedCertificate = "-----BEGIN CERTIFICATE-----\n" +
@ -741,7 +756,7 @@ class XmppService {
let server = options.server; let server = options.server;
self._socket.connect("jabber.troubleimpact.com", 5222).then(function() { self._socket.connect("jabber.troubleimpact.com", 5222).then(function() {
print("actually connected"); print("actually connected");
self._callback({action: "connected"}); self.invokeCallback({action: "connected"});
print("wtf"); print("wtf");
var parse = new XmlStanzaParser(1); var parse = new XmlStanzaParser(1);
self._socket.write("<?xml version='1.0'?>"); self._socket.write("<?xml version='1.0'?>");
@ -753,7 +768,7 @@ class XmppService {
self._socket.read(function(data) { self._socket.read(function(data) {
try { try {
if (!data) { if (!data) {
self._callback({action: "disconnected"}); self.invokeCallback({action: "disconnected"});
return; return;
} }
parse.parse(data).forEach(function(stanza) { parse.parse(data).forEach(function(stanza) {
@ -786,10 +801,10 @@ class XmppService {
self._socket.write("<presence to='chadhappyfuntime@conference.jabber.troubleimpact.com/" + userName + "'><priority>1</priority><x xmlns='http://jabber.org/protocol/muc'/></presence>"); self._socket.write("<presence to='chadhappyfuntime@conference.jabber.troubleimpact.com/" + userName + "'><priority>1</priority><x xmlns='http://jabber.org/protocol/muc'/></presence>");
self._schedulePing(); self._schedulePing();
self._conversations["chadhappyfuntime@conference.jabber.troubleimpact.com"] = {participants: [], history: []}; self._conversations["chadhappyfuntime@conference.jabber.troubleimpact.com"] = {participants: [], history: []};
} else if (stanza.attributes.id == "ping" + gPingCount) { } else if (stanza.children.length && stanza.children[0].name == "ping") {
// Ping response. // Ping response.
} else { } else {
self._callback({ self.invokeCallback({
action: "unknown", action: "unknown",
stanza: stanza, stanza: stanza,
}); });
@ -797,7 +812,7 @@ class XmppService {
} else if (stanza.name == "message") { } else if (stanza.name == "message") {
let message = self._convertMessage(stanza); let message = self._convertMessage(stanza);
self._conversations[message.conversation].history.push(message); self._conversations[message.conversation].history.push(message);
self._callback(message); self.invokeCallback(message);
} else if (stanza.name == "challenge") { } else if (stanza.name == "challenge") {
var challenge = Base64.decode(stanza.text); var challenge = Base64.decode(stanza.text);
var parts = challenge.split(','); var parts = challenge.split(',');
@ -835,21 +850,22 @@ class XmppService {
let name = stanza.attributes.from.split('/', 2)[1]; let name = stanza.attributes.from.split('/', 2)[1];
let conversation = stanza.attributes.from.split('/', 2)[0]; let conversation = stanza.attributes.from.split('/', 2)[0];
let leaving = stanza.attributes.type == "unavailable"; let leaving = stanza.attributes.type == "unavailable";
let index = self._conversations[conversation].participants.indexOf(name);
if (leaving) { if (leaving) {
self._conversations[conversation].participants.remove(name); self._conversations[conversation].participants.splice(index, 1);
} else { } else {
if (self._conversations[conversation].participants.indexOf(name) == -1) { if (index == -1) {
self._conversations[conversation].participants.push(name); self._conversations[conversation].participants.push(name);
} }
} }
self._callback({ self.invokeCallback({
action: "presence", action: "presence",
name: name, name: name,
jid: stanza.attributes.from, jid: stanza.attributes.from,
type: stanza.attributes.type, type: stanza.attributes.type,
}); });
} else { } else {
self._callback({ self.invokeCallback({
action: "unknown", action: "unknown",
stanza: stanza, stanza: stanza,
}); });
@ -869,7 +885,7 @@ class XmppService {
} }
_reportError(error) { _reportError(error) {
this._callback({ this.invokeCallback({
action: "error", action: "error",
error: error, error: error,
}).catch(function(error) { }).catch(function(error) {
@ -929,7 +945,9 @@ core.register("onMessage", function(sender, options) {
service = new XmppService(options); service = new XmppService(options);
gSessions[options.name] = service; gSessions[options.name] = service;
} else { } else {
service._callback = options.callback; if (service._callbacks.indexOf(options.callback) == -1) {
service._callbacks.push(options.callback);
}
} }
return { return {
sendMessage: service.sendMessage.bind(service), sendMessage: service.sendMessage.bind(service),