From 2243c09fea2dbff31a3803cfee2611b9faa08ea9 Mon Sep 17 00:00:00 2001 From: lilia Date: Tue, 1 Sep 2015 12:13:38 -0700 Subject: [PATCH] MessageReceiver is an event target Rather than asking for a global target, the message receiver implements the EventTarget interface itself. It does not expose the dispatchEvent method, however. This ensures that events can only be triggered from within the internal MessageReceiver class, which means we no longer need to namespace them. // FREEBIE --- js/background.js | 36 +++++---- js/libtextsecure.js | 127 +++++++++++++++++++++--------- libtextsecure/message_receiver.js | 127 +++++++++++++++++++++--------- 3 files changed, 202 insertions(+), 88 deletions(-) diff --git a/js/background.js b/js/background.js index 0b6145d1..ad330c20 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 9c53ac16..7407dd83 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; + 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(); } @@ -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 52e974a6..7557c177 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 = {