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:
		| @@ -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); |  | ||||||
| 	} |  | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -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); | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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), | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user