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
This commit is contained in:
lilia 2015-09-01 12:13:38 -07:00
parent a925027cd2
commit 2243c09fea
3 changed files with 202 additions and 88 deletions

View file

@ -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) { if (open) {
openInbox(); openInbox();
} }
function init() { function init() {
window.removeEventListener('online', init); window.removeEventListener('online', init);
if (!textsecure.registration.isDone()) { return; } if (!textsecure.registration.isDone()) { return; }
// initialize the socket and start listening for messages if (messageReceiver) { messageReceiver.close(); }
messageReceiver = new textsecure.MessageReceiver(
'https://textsecure-service-staging.whispersystems.org', var URL = 'https://textsecure-service-staging.whispersystems.org';
textsecure.storage.get('number_id'), var USERNAME = storage.get('number_id');
textsecure.storage.get('password'), var PASSWORD = storage.get('password');
textsecure.storage.get('signaling_key'), var mySignalingKey = storage.get('signaling_key');
window
); 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) { function onContactReceived(ev) {

View file

@ -39381,9 +39381,7 @@ function generateKeys(count, progressCallback) {
'use strict'; 'use strict';
window.textsecure = window.textsecure || {}; window.textsecure = window.textsecure || {};
function MessageReceiver(url, username, password, signalingKey, eventTarget) { function MessageReceiver(url, username, password, signalingKey) {
if (eventTarget instanceof EventTarget) {
this.target = eventTarget;
this.url = url; this.url = url;
this.signalingKey = signalingKey; this.signalingKey = signalingKey;
this.username = username; this.username = username;
@ -39392,9 +39390,6 @@ function generateKeys(count, progressCallback) {
var unencoded = textsecure.utils.unencodeNumber(username); var unencoded = textsecure.utils.unencodeNumber(username);
this.number = unencoded[0]; this.number = unencoded[0];
this.deviceId = unencoded[1]; this.deviceId = unencoded[1];
} else {
throw new TypeError('MessageReceiver expected an EventTarget');
}
this.connect(); this.connect();
} }
@ -39403,7 +39398,7 @@ function generateKeys(count, progressCallback) {
connect: function() { connect: function() {
// initialize the socket and start listening for messages // initialize the socket and start listening for messages
if (this.socket && this.socket.readyState !== WebSocket.CLOSED) { if (this.socket && this.socket.readyState !== WebSocket.CLOSED) {
socket.close(); this.socket.close();
} }
this.socket = new WebSocket( this.socket = new WebSocket(
this.url.replace('https://', 'wss://').replace('http://', 'ws://') this.url.replace('https://', 'wss://').replace('http://', 'ws://')
@ -39418,15 +39413,19 @@ function generateKeys(count, progressCallback) {
keepalive: { path: '/v1/keepalive', disconnect: true } keepalive: { path: '/v1/keepalive', disconnect: true }
}); });
}, },
close: function() {
this.socket.close();
delete this.listeners;
},
onerror: function(error) { onerror: function(error) {
console.log('websocket error', error); console.log('websocket error', error);
this.socketError = error; this.socketError = error;
}, },
onclose: function(ev) { onclose: function(ev) {
var eventTarget = this.target; var eventTarget = this;
console.log('websocket closed', ev.code); console.log('websocket closed', ev.code);
// possible 403 or network issue. Make an request to confirm // 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 then(this.connect.bind(this)). // No HTTP error? Reconnect
catch(function(e) { catch(function(e) {
var ev = new Event('textsecure:error'); var ev = new Event('textsecure:error');
@ -39458,9 +39457,9 @@ function generateKeys(count, progressCallback) {
}.bind(this)).catch(function(e) { }.bind(this)).catch(function(e) {
request.respond(500, 'Bad encrypted websocket message'); request.respond(500, 'Bad encrypted websocket message');
console.log("Error handling incoming message:", e); console.log("Error handling incoming message:", e);
var ev = new Event('textsecure:error'); var ev = new Event('error');
ev.error = e; ev.error = e;
this.target.dispatchEvent(ev); this.dispatchEvent(ev);
}.bind(this)); }.bind(this));
}, },
getStatus: function() { getStatus: function() {
@ -39471,9 +39470,9 @@ function generateKeys(count, progressCallback) {
} }
}, },
onDeliveryReceipt: function (envelope) { onDeliveryReceipt: function (envelope) {
var ev = new Event('textsecure:receipt'); var ev = new Event('receipt');
ev.proto = envelope; ev.proto = envelope;
this.target.dispatchEvent(ev); this.dispatchEvent(ev);
}, },
decrypt: function(envelope, ciphertext) { decrypt: function(envelope, ciphertext) {
return textsecure.protocol_wrapper.decrypt( return textsecure.protocol_wrapper.decrypt(
@ -39482,22 +39481,22 @@ function generateKeys(count, progressCallback) {
envelope.type, envelope.type,
ciphertext ciphertext
).catch(function(error) { ).catch(function(error) {
var ev = new Event('textsecure:error'); var ev = new Event('error');
ev.error = error; ev.error = error;
ev.proto = envelope; ev.proto = envelope;
this.target.dispatchEvent(ev); this.dispatchEvent(ev);
throw error; // reject this promise throw error; // reject this promise
}.bind(this)); }.bind(this));
}, },
handleSentMessage: function(destination, timestamp, message) { handleSentMessage: function(destination, timestamp, message) {
return processDecrypted(message, this.number).then(function(message) { return processDecrypted(message, this.number).then(function(message) {
var ev = new Event('textsecure:sent'); var ev = new Event('sent');
ev.data = { ev.data = {
destination : destination, destination : destination,
timestamp : timestamp.toNumber(), timestamp : timestamp.toNumber(),
message : message message : message
}; };
this.target.dispatchEvent(ev); this.dispatchEvent(ev);
}.bind(this)); }.bind(this));
}, },
handleDataMessage: function(envelope, message, close_session) { handleDataMessage: function(envelope, message, close_session) {
@ -39506,13 +39505,13 @@ function generateKeys(count, progressCallback) {
close_session(); close_session();
} }
return processDecrypted(message, envelope.source).then(function(message) { return processDecrypted(message, envelope.source).then(function(message) {
var ev = new Event('textsecure:message'); var ev = new Event('message');
ev.data = { ev.data = {
source : envelope.source, source : envelope.source,
timestamp : envelope.timestamp.toNumber(), timestamp : envelope.timestamp.toNumber(),
message : message message : message
}; };
this.target.dispatchEvent(ev); this.dispatchEvent(ev);
}.bind(this)); }.bind(this));
}, },
handleLegacyMessage: function (envelope) { handleLegacyMessage: function (envelope) {
@ -39560,22 +39559,22 @@ function generateKeys(count, progressCallback) {
} }
}, },
handleContacts: function(contacts) { handleContacts: function(contacts) {
var eventTarget = this.target; var eventTarget = this;
var attachmentPointer = contacts.blob; var attachmentPointer = contacts.blob;
return handleAttachment(attachmentPointer).then(function() { return handleAttachment(attachmentPointer).then(function() {
var contactBuffer = new ContactBuffer(attachmentPointer.data); var contactBuffer = new ContactBuffer(attachmentPointer.data);
var contactDetails = contactBuffer.next(); var contactDetails = contactBuffer.next();
while (contactDetails !== undefined) { while (contactDetails !== undefined) {
var ev = new Event('textsecure:contact'); var ev = new Event('contact');
ev.contactDetails = contactDetails; ev.contactDetails = contactDetails;
eventTarget.dispatchEvent(ev); eventTarget.dispatchEvent(ev);
contactDetails = contactBuffer.next(); contactDetails = contactBuffer.next();
} }
eventTarget.dispatchEvent(new Event('textsecure:contactsync')); eventTarget.dispatchEvent(new Event('contactsync'));
}); });
}, },
handleGroups: function(groups) { handleGroups: function(groups) {
var eventTarget = this.target; var eventTarget = this;
var attachmentPointer = groups.blob; var attachmentPointer = groups.blob;
return handleAttachment(attachmentPointer).then(function() { return handleAttachment(attachmentPointer).then(function() {
var groupBuffer = new GroupBuffer(attachmentPointer.data); var groupBuffer = new GroupBuffer(attachmentPointer.data);
@ -39595,7 +39594,7 @@ function generateKeys(count, progressCallback) {
); );
} }
}).then(function() { }).then(function() {
var ev = new Event('textsecure:group'); var ev = new Event('group');
ev.groupDetails = groupDetails; ev.groupDetails = groupDetails;
eventTarget.dispatchEvent(ev); eventTarget.dispatchEvent(ev);
}); });
@ -39603,14 +39602,70 @@ function generateKeys(count, progressCallback) {
groupDetails = groupBuffer.next(); 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) { textsecure.MessageReceiver = function(url, username, password, signalingKey) {
var messageReceiver = new MessageReceiver(url, username, password, signalingKey, eventTarget); var messageReceiver = new MessageReceiver(url, username, password, signalingKey);
this.getStatus = function() {
return messageReceiver.getStatus(); this.addEventListener = messageReceiver.addEventListener.bind(messageReceiver);
} this.removeEventListener = messageReceiver.removeEventListener.bind(messageReceiver);
this.getStatus = messageReceiver.getStatus.bind(messageReceiver);
} }
textsecure.MessageReceiver.prototype = { textsecure.MessageReceiver.prototype = {

View file

@ -6,9 +6,7 @@
'use strict'; 'use strict';
window.textsecure = window.textsecure || {}; window.textsecure = window.textsecure || {};
function MessageReceiver(url, username, password, signalingKey, eventTarget) { function MessageReceiver(url, username, password, signalingKey) {
if (eventTarget instanceof EventTarget) {
this.target = eventTarget;
this.url = url; this.url = url;
this.signalingKey = signalingKey; this.signalingKey = signalingKey;
this.username = username; this.username = username;
@ -17,9 +15,6 @@
var unencoded = textsecure.utils.unencodeNumber(username); var unencoded = textsecure.utils.unencodeNumber(username);
this.number = unencoded[0]; this.number = unencoded[0];
this.deviceId = unencoded[1]; this.deviceId = unencoded[1];
} else {
throw new TypeError('MessageReceiver expected an EventTarget');
}
this.connect(); this.connect();
} }
@ -28,7 +23,7 @@
connect: function() { connect: function() {
// initialize the socket and start listening for messages // initialize the socket and start listening for messages
if (this.socket && this.socket.readyState !== WebSocket.CLOSED) { if (this.socket && this.socket.readyState !== WebSocket.CLOSED) {
socket.close(); this.socket.close();
} }
this.socket = new WebSocket( this.socket = new WebSocket(
this.url.replace('https://', 'wss://').replace('http://', 'ws://') this.url.replace('https://', 'wss://').replace('http://', 'ws://')
@ -43,15 +38,19 @@
keepalive: { path: '/v1/keepalive', disconnect: true } keepalive: { path: '/v1/keepalive', disconnect: true }
}); });
}, },
close: function() {
this.socket.close();
delete this.listeners;
},
onerror: function(error) { onerror: function(error) {
console.log('websocket error', error); console.log('websocket error', error);
this.socketError = error; this.socketError = error;
}, },
onclose: function(ev) { onclose: function(ev) {
var eventTarget = this.target; var eventTarget = this;
console.log('websocket closed', ev.code); console.log('websocket closed', ev.code);
// possible 403 or network issue. Make an request to confirm // 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 then(this.connect.bind(this)). // No HTTP error? Reconnect
catch(function(e) { catch(function(e) {
var ev = new Event('textsecure:error'); var ev = new Event('textsecure:error');
@ -83,9 +82,9 @@
}.bind(this)).catch(function(e) { }.bind(this)).catch(function(e) {
request.respond(500, 'Bad encrypted websocket message'); request.respond(500, 'Bad encrypted websocket message');
console.log("Error handling incoming message:", e); console.log("Error handling incoming message:", e);
var ev = new Event('textsecure:error'); var ev = new Event('error');
ev.error = e; ev.error = e;
this.target.dispatchEvent(ev); this.dispatchEvent(ev);
}.bind(this)); }.bind(this));
}, },
getStatus: function() { getStatus: function() {
@ -96,9 +95,9 @@
} }
}, },
onDeliveryReceipt: function (envelope) { onDeliveryReceipt: function (envelope) {
var ev = new Event('textsecure:receipt'); var ev = new Event('receipt');
ev.proto = envelope; ev.proto = envelope;
this.target.dispatchEvent(ev); this.dispatchEvent(ev);
}, },
decrypt: function(envelope, ciphertext) { decrypt: function(envelope, ciphertext) {
return textsecure.protocol_wrapper.decrypt( return textsecure.protocol_wrapper.decrypt(
@ -107,22 +106,22 @@
envelope.type, envelope.type,
ciphertext ciphertext
).catch(function(error) { ).catch(function(error) {
var ev = new Event('textsecure:error'); var ev = new Event('error');
ev.error = error; ev.error = error;
ev.proto = envelope; ev.proto = envelope;
this.target.dispatchEvent(ev); this.dispatchEvent(ev);
throw error; // reject this promise throw error; // reject this promise
}.bind(this)); }.bind(this));
}, },
handleSentMessage: function(destination, timestamp, message) { handleSentMessage: function(destination, timestamp, message) {
return processDecrypted(message, this.number).then(function(message) { return processDecrypted(message, this.number).then(function(message) {
var ev = new Event('textsecure:sent'); var ev = new Event('sent');
ev.data = { ev.data = {
destination : destination, destination : destination,
timestamp : timestamp.toNumber(), timestamp : timestamp.toNumber(),
message : message message : message
}; };
this.target.dispatchEvent(ev); this.dispatchEvent(ev);
}.bind(this)); }.bind(this));
}, },
handleDataMessage: function(envelope, message, close_session) { handleDataMessage: function(envelope, message, close_session) {
@ -131,13 +130,13 @@
close_session(); close_session();
} }
return processDecrypted(message, envelope.source).then(function(message) { return processDecrypted(message, envelope.source).then(function(message) {
var ev = new Event('textsecure:message'); var ev = new Event('message');
ev.data = { ev.data = {
source : envelope.source, source : envelope.source,
timestamp : envelope.timestamp.toNumber(), timestamp : envelope.timestamp.toNumber(),
message : message message : message
}; };
this.target.dispatchEvent(ev); this.dispatchEvent(ev);
}.bind(this)); }.bind(this));
}, },
handleLegacyMessage: function (envelope) { handleLegacyMessage: function (envelope) {
@ -185,22 +184,22 @@
} }
}, },
handleContacts: function(contacts) { handleContacts: function(contacts) {
var eventTarget = this.target; var eventTarget = this;
var attachmentPointer = contacts.blob; var attachmentPointer = contacts.blob;
return handleAttachment(attachmentPointer).then(function() { return handleAttachment(attachmentPointer).then(function() {
var contactBuffer = new ContactBuffer(attachmentPointer.data); var contactBuffer = new ContactBuffer(attachmentPointer.data);
var contactDetails = contactBuffer.next(); var contactDetails = contactBuffer.next();
while (contactDetails !== undefined) { while (contactDetails !== undefined) {
var ev = new Event('textsecure:contact'); var ev = new Event('contact');
ev.contactDetails = contactDetails; ev.contactDetails = contactDetails;
eventTarget.dispatchEvent(ev); eventTarget.dispatchEvent(ev);
contactDetails = contactBuffer.next(); contactDetails = contactBuffer.next();
} }
eventTarget.dispatchEvent(new Event('textsecure:contactsync')); eventTarget.dispatchEvent(new Event('contactsync'));
}); });
}, },
handleGroups: function(groups) { handleGroups: function(groups) {
var eventTarget = this.target; var eventTarget = this;
var attachmentPointer = groups.blob; var attachmentPointer = groups.blob;
return handleAttachment(attachmentPointer).then(function() { return handleAttachment(attachmentPointer).then(function() {
var groupBuffer = new GroupBuffer(attachmentPointer.data); var groupBuffer = new GroupBuffer(attachmentPointer.data);
@ -220,7 +219,7 @@
); );
} }
}).then(function() { }).then(function() {
var ev = new Event('textsecure:group'); var ev = new Event('group');
ev.groupDetails = groupDetails; ev.groupDetails = groupDetails;
eventTarget.dispatchEvent(ev); eventTarget.dispatchEvent(ev);
}); });
@ -228,14 +227,70 @@
groupDetails = groupBuffer.next(); 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) { textsecure.MessageReceiver = function(url, username, password, signalingKey) {
var messageReceiver = new MessageReceiver(url, username, password, signalingKey, eventTarget); var messageReceiver = new MessageReceiver(url, username, password, signalingKey);
this.getStatus = function() {
return messageReceiver.getStatus(); this.addEventListener = messageReceiver.addEventListener.bind(messageReceiver);
} this.removeEventListener = messageReceiver.removeEventListener.bind(messageReceiver);
this.getStatus = messageReceiver.getStatus.bind(messageReceiver);
} }
textsecure.MessageReceiver.prototype = { textsecure.MessageReceiver.prototype = {