diff --git a/js/background.js b/js/background.js index 0b6145d15..ad330c208 100644 --- a/js/background.js +++ b/js/background.js @@ -49,29 +49,33 @@ } }; - window.addEventListener('textsecure:message', onMessageReceived); - window.addEventListener('textsecure:receipt', onDeliveryReceipt); - window.addEventListener('textsecure:contact', onContactReceived); - window.addEventListener('textsecure:group', onGroupReceived); - window.addEventListener('textsecure:sent', onSentMessage); - window.addEventListener('textsecure:error', onError); - if (open) { openInbox(); } - function init() { window.removeEventListener('online', init); if (!textsecure.registration.isDone()) { return; } - // initialize the socket and start listening for messages - messageReceiver = new textsecure.MessageReceiver( - 'https://textsecure-service-staging.whispersystems.org', - textsecure.storage.get('number_id'), - textsecure.storage.get('password'), - textsecure.storage.get('signaling_key'), - window - ); + if (messageReceiver) { messageReceiver.close(); } + + var URL = 'https://textsecure-service-staging.whispersystems.org'; + var USERNAME = storage.get('number_id'); + var PASSWORD = storage.get('password'); + var mySignalingKey = storage.get('signaling_key'); + + messageReceiver = new textsecure.MessageReceiver(URL, USERNAME, PASSWORD, mySignalingKey); + messageReceiver.addEventListener('message', onMessageReceived); + messageReceiver.addEventListener('receipt', onDeliveryReceipt); + messageReceiver.addEventListener('contact', onContactReceived); + messageReceiver.addEventListener('group', onGroupReceived); + messageReceiver.addEventListener('sent', onSentMessage); + messageReceiver.addEventListener('error', onError); + + messageReceiver.addEventListener('contactsync', onContactSyncComplete); + } + + function onContactSyncComplete() { + window.dispatchEvent(new Event('textsecure:contactsync')); } function onContactReceived(ev) { diff --git a/js/libtextsecure.js b/js/libtextsecure.js index 9c53ac163..7407dd832 100644 --- a/js/libtextsecure.js +++ b/js/libtextsecure.js @@ -39381,20 +39381,15 @@ function generateKeys(count, progressCallback) { 'use strict'; window.textsecure = window.textsecure || {}; - function MessageReceiver(url, username, password, signalingKey, eventTarget) { - if (eventTarget instanceof EventTarget) { - this.target = eventTarget; - this.url = url; - this.signalingKey = signalingKey; - this.username = username; - this.password = password; - - var unencoded = textsecure.utils.unencodeNumber(username); - this.number = unencoded[0]; - this.deviceId = unencoded[1]; - } else { - throw new TypeError('MessageReceiver expected an EventTarget'); - } + function MessageReceiver(url, username, password, signalingKey) { + this.url = url; + this.signalingKey = signalingKey; + this.username = username; + this.password = password; + + var unencoded = textsecure.utils.unencodeNumber(username); + this.number = unencoded[0]; + this.deviceId = unencoded[1]; this.connect(); } @@ -39403,7 +39398,7 @@ function generateKeys(count, progressCallback) { connect: function() { // initialize the socket and start listening for messages if (this.socket && this.socket.readyState !== WebSocket.CLOSED) { - socket.close(); + this.socket.close(); } this.socket = new WebSocket( this.url.replace('https://', 'wss://').replace('http://', 'ws://') @@ -39418,15 +39413,19 @@ function generateKeys(count, progressCallback) { keepalive: { path: '/v1/keepalive', disconnect: true } }); }, + close: function() { + this.socket.close(); + delete this.listeners; + }, onerror: function(error) { console.log('websocket error', error); this.socketError = error; }, onclose: function(ev) { - var eventTarget = this.target; + var eventTarget = this; console.log('websocket closed', ev.code); // possible 403 or network issue. Make an request to confirm - TextSecureServer(this.url).getDevices(this.number). + TextSecureServer.getDevices(this.number). then(this.connect.bind(this)). // No HTTP error? Reconnect catch(function(e) { var ev = new Event('textsecure:error'); @@ -39458,9 +39457,9 @@ function generateKeys(count, progressCallback) { }.bind(this)).catch(function(e) { request.respond(500, 'Bad encrypted websocket message'); console.log("Error handling incoming message:", e); - var ev = new Event('textsecure:error'); + var ev = new Event('error'); ev.error = e; - this.target.dispatchEvent(ev); + this.dispatchEvent(ev); }.bind(this)); }, getStatus: function() { @@ -39471,9 +39470,9 @@ function generateKeys(count, progressCallback) { } }, onDeliveryReceipt: function (envelope) { - var ev = new Event('textsecure:receipt'); + var ev = new Event('receipt'); ev.proto = envelope; - this.target.dispatchEvent(ev); + this.dispatchEvent(ev); }, decrypt: function(envelope, ciphertext) { return textsecure.protocol_wrapper.decrypt( @@ -39482,22 +39481,22 @@ function generateKeys(count, progressCallback) { envelope.type, ciphertext ).catch(function(error) { - var ev = new Event('textsecure:error'); + var ev = new Event('error'); ev.error = error; ev.proto = envelope; - this.target.dispatchEvent(ev); + this.dispatchEvent(ev); throw error; // reject this promise }.bind(this)); }, handleSentMessage: function(destination, timestamp, message) { return processDecrypted(message, this.number).then(function(message) { - var ev = new Event('textsecure:sent'); + var ev = new Event('sent'); ev.data = { destination : destination, timestamp : timestamp.toNumber(), message : message }; - this.target.dispatchEvent(ev); + this.dispatchEvent(ev); }.bind(this)); }, handleDataMessage: function(envelope, message, close_session) { @@ -39506,13 +39505,13 @@ function generateKeys(count, progressCallback) { close_session(); } return processDecrypted(message, envelope.source).then(function(message) { - var ev = new Event('textsecure:message'); + var ev = new Event('message'); ev.data = { source : envelope.source, timestamp : envelope.timestamp.toNumber(), message : message }; - this.target.dispatchEvent(ev); + this.dispatchEvent(ev); }.bind(this)); }, handleLegacyMessage: function (envelope) { @@ -39560,22 +39559,22 @@ function generateKeys(count, progressCallback) { } }, handleContacts: function(contacts) { - var eventTarget = this.target; + var eventTarget = this; var attachmentPointer = contacts.blob; return handleAttachment(attachmentPointer).then(function() { var contactBuffer = new ContactBuffer(attachmentPointer.data); var contactDetails = contactBuffer.next(); while (contactDetails !== undefined) { - var ev = new Event('textsecure:contact'); + var ev = new Event('contact'); ev.contactDetails = contactDetails; eventTarget.dispatchEvent(ev); contactDetails = contactBuffer.next(); } - eventTarget.dispatchEvent(new Event('textsecure:contactsync')); + eventTarget.dispatchEvent(new Event('contactsync')); }); }, handleGroups: function(groups) { - var eventTarget = this.target; + var eventTarget = this; var attachmentPointer = groups.blob; return handleAttachment(attachmentPointer).then(function() { var groupBuffer = new GroupBuffer(attachmentPointer.data); @@ -39595,7 +39594,7 @@ function generateKeys(count, progressCallback) { ); } }).then(function() { - var ev = new Event('textsecure:group'); + var ev = new Event('group'); ev.groupDetails = groupDetails; eventTarget.dispatchEvent(ev); }); @@ -39603,14 +39602,70 @@ function generateKeys(count, progressCallback) { groupDetails = groupBuffer.next(); } }); + }, + + /* Implements EventTarget */ + dispatchEvent: function(ev) { + if (!(ev instanceof Event)) { + throw new Error('Expects an event'); + } + if (this.listeners === null || typeof this.listeners !== 'object') { + this.listeners = {}; + } + var listeners = this.listeners[ev.type]; + if (typeof listeners === 'object') { + for (var i=0; i < listeners.length; ++i) { + if (typeof listeners[i] === 'function') { + listeners[i].call(null, ev); + } + } + } + }, + addEventListener: function(eventName, callback) { + if (typeof eventName !== 'string') { + throw new Error('First argument expects a string'); + } + if (typeof callback !== 'function') { + throw new Error('Second argument expects a function'); + } + if (this.listeners === null || typeof this.listeners !== 'object') { + this.listeners = {}; + } + var listeners = this.listeners[eventName]; + if (typeof listeners !== 'object') { + listeners = []; + } + listeners.push(callback); + this.listeners[eventName] = listeners; + }, + removeEventListener: function(eventName, callback) { + if (typeof eventName !== 'string') { + throw new Error('First argument expects a string'); + } + if (typeof callback !== 'function') { + throw new Error('Second argument expects a function'); + } + if (this.listeners === null || typeof this.listeners !== 'object') { + this.listeners = {}; + } + var listeners = this.listeners[eventName]; + for (var i=0; i < listeners.length; ++ i) { + if (listeners[i] === callback) { + listeners.splice(i, 1); + return; + } + } + this.listeners[eventName] = listeners; } + }; - textsecure.MessageReceiver = function(url, username, password, signalingKey, eventTarget) { - var messageReceiver = new MessageReceiver(url, username, password, signalingKey, eventTarget); - this.getStatus = function() { - return messageReceiver.getStatus(); - } + textsecure.MessageReceiver = function(url, username, password, signalingKey) { + var messageReceiver = new MessageReceiver(url, username, password, signalingKey); + + this.addEventListener = messageReceiver.addEventListener.bind(messageReceiver); + this.removeEventListener = messageReceiver.removeEventListener.bind(messageReceiver); + this.getStatus = messageReceiver.getStatus.bind(messageReceiver); } textsecure.MessageReceiver.prototype = { diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 52e974a68..7557c177c 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -6,20 +6,15 @@ 'use strict'; window.textsecure = window.textsecure || {}; - function MessageReceiver(url, username, password, signalingKey, eventTarget) { - if (eventTarget instanceof EventTarget) { - this.target = eventTarget; - this.url = url; - this.signalingKey = signalingKey; - this.username = username; - this.password = password; + function MessageReceiver(url, username, password, signalingKey) { + this.url = url; + this.signalingKey = signalingKey; + this.username = username; + this.password = password; - var unencoded = textsecure.utils.unencodeNumber(username); - this.number = unencoded[0]; - this.deviceId = unencoded[1]; - } else { - throw new TypeError('MessageReceiver expected an EventTarget'); - } + var unencoded = textsecure.utils.unencodeNumber(username); + this.number = unencoded[0]; + this.deviceId = unencoded[1]; this.connect(); } @@ -28,7 +23,7 @@ connect: function() { // initialize the socket and start listening for messages if (this.socket && this.socket.readyState !== WebSocket.CLOSED) { - socket.close(); + this.socket.close(); } this.socket = new WebSocket( this.url.replace('https://', 'wss://').replace('http://', 'ws://') @@ -43,15 +38,19 @@ keepalive: { path: '/v1/keepalive', disconnect: true } }); }, + close: function() { + this.socket.close(); + delete this.listeners; + }, onerror: function(error) { console.log('websocket error', error); this.socketError = error; }, onclose: function(ev) { - var eventTarget = this.target; + var eventTarget = this; console.log('websocket closed', ev.code); // possible 403 or network issue. Make an request to confirm - TextSecureServer(this.url).getDevices(this.number). + TextSecureServer.getDevices(this.number). then(this.connect.bind(this)). // No HTTP error? Reconnect catch(function(e) { var ev = new Event('textsecure:error'); @@ -83,9 +82,9 @@ }.bind(this)).catch(function(e) { request.respond(500, 'Bad encrypted websocket message'); console.log("Error handling incoming message:", e); - var ev = new Event('textsecure:error'); + var ev = new Event('error'); ev.error = e; - this.target.dispatchEvent(ev); + this.dispatchEvent(ev); }.bind(this)); }, getStatus: function() { @@ -96,9 +95,9 @@ } }, onDeliveryReceipt: function (envelope) { - var ev = new Event('textsecure:receipt'); + var ev = new Event('receipt'); ev.proto = envelope; - this.target.dispatchEvent(ev); + this.dispatchEvent(ev); }, decrypt: function(envelope, ciphertext) { return textsecure.protocol_wrapper.decrypt( @@ -107,22 +106,22 @@ envelope.type, ciphertext ).catch(function(error) { - var ev = new Event('textsecure:error'); + var ev = new Event('error'); ev.error = error; ev.proto = envelope; - this.target.dispatchEvent(ev); + this.dispatchEvent(ev); throw error; // reject this promise }.bind(this)); }, handleSentMessage: function(destination, timestamp, message) { return processDecrypted(message, this.number).then(function(message) { - var ev = new Event('textsecure:sent'); + var ev = new Event('sent'); ev.data = { destination : destination, timestamp : timestamp.toNumber(), message : message }; - this.target.dispatchEvent(ev); + this.dispatchEvent(ev); }.bind(this)); }, handleDataMessage: function(envelope, message, close_session) { @@ -131,13 +130,13 @@ close_session(); } return processDecrypted(message, envelope.source).then(function(message) { - var ev = new Event('textsecure:message'); + var ev = new Event('message'); ev.data = { source : envelope.source, timestamp : envelope.timestamp.toNumber(), message : message }; - this.target.dispatchEvent(ev); + this.dispatchEvent(ev); }.bind(this)); }, handleLegacyMessage: function (envelope) { @@ -185,22 +184,22 @@ } }, handleContacts: function(contacts) { - var eventTarget = this.target; + var eventTarget = this; var attachmentPointer = contacts.blob; return handleAttachment(attachmentPointer).then(function() { var contactBuffer = new ContactBuffer(attachmentPointer.data); var contactDetails = contactBuffer.next(); while (contactDetails !== undefined) { - var ev = new Event('textsecure:contact'); + var ev = new Event('contact'); ev.contactDetails = contactDetails; eventTarget.dispatchEvent(ev); contactDetails = contactBuffer.next(); } - eventTarget.dispatchEvent(new Event('textsecure:contactsync')); + eventTarget.dispatchEvent(new Event('contactsync')); }); }, handleGroups: function(groups) { - var eventTarget = this.target; + var eventTarget = this; var attachmentPointer = groups.blob; return handleAttachment(attachmentPointer).then(function() { var groupBuffer = new GroupBuffer(attachmentPointer.data); @@ -220,7 +219,7 @@ ); } }).then(function() { - var ev = new Event('textsecure:group'); + var ev = new Event('group'); ev.groupDetails = groupDetails; eventTarget.dispatchEvent(ev); }); @@ -228,14 +227,70 @@ groupDetails = groupBuffer.next(); } }); + }, + + /* Implements EventTarget */ + dispatchEvent: function(ev) { + if (!(ev instanceof Event)) { + throw new Error('Expects an event'); + } + if (this.listeners === null || typeof this.listeners !== 'object') { + this.listeners = {}; + } + var listeners = this.listeners[ev.type]; + if (typeof listeners === 'object') { + for (var i=0; i < listeners.length; ++i) { + if (typeof listeners[i] === 'function') { + listeners[i].call(null, ev); + } + } + } + }, + addEventListener: function(eventName, callback) { + if (typeof eventName !== 'string') { + throw new Error('First argument expects a string'); + } + if (typeof callback !== 'function') { + throw new Error('Second argument expects a function'); + } + if (this.listeners === null || typeof this.listeners !== 'object') { + this.listeners = {}; + } + var listeners = this.listeners[eventName]; + if (typeof listeners !== 'object') { + listeners = []; + } + listeners.push(callback); + this.listeners[eventName] = listeners; + }, + removeEventListener: function(eventName, callback) { + if (typeof eventName !== 'string') { + throw new Error('First argument expects a string'); + } + if (typeof callback !== 'function') { + throw new Error('Second argument expects a function'); + } + if (this.listeners === null || typeof this.listeners !== 'object') { + this.listeners = {}; + } + var listeners = this.listeners[eventName]; + for (var i=0; i < listeners.length; ++ i) { + if (listeners[i] === callback) { + listeners.splice(i, 1); + return; + } + } + this.listeners[eventName] = listeners; } + }; - textsecure.MessageReceiver = function(url, username, password, signalingKey, eventTarget) { - var messageReceiver = new MessageReceiver(url, username, password, signalingKey, eventTarget); - this.getStatus = function() { - return messageReceiver.getStatus(); - } + textsecure.MessageReceiver = function(url, username, password, signalingKey) { + var messageReceiver = new MessageReceiver(url, username, password, signalingKey); + + this.addEventListener = messageReceiver.addEventListener.bind(messageReceiver); + this.removeEventListener = messageReceiver.removeEventListener.bind(messageReceiver); + this.getStatus = messageReceiver.getStatus.bind(messageReceiver); } textsecure.MessageReceiver.prototype = {