Finish abstracting native client
Firstly, don't initialize textsecure.nativclient unless the browser
supports it. The mimetype-check trick is hewn from nacl-common.js.
Secondly, nativeclient crypto functions will all automatically wait for
the module to load before sending messages, so we needn't register any
onload callbacks outside nativeclient.js. (Previously, if you wanted to
do crypto with native client, you would have to register a call back and
wait for the module to load.) Now that the native client crypto is
encapsulated behind a nice interface, it can handle all that
onload-callback jazz internally: if the module isn't loaded when you
call a nativeclient function, return a promise that waits for the load
callback, and eventually resolves with the result of the requested
command. This removes the need for textsecure.registerOnLoadCallback.
Finally, although native client has its quirks, it's significantly
faster than the alternative (emscripten compiled js), so this commit
also lets the crypto backend use native client opportunistically, if
it's available, falling back to js if not, which should make us
compatible with older versions of chrome and chromium.
2014-11-09 02:26:20 +01:00
|
|
|
/* vim: ts=4:sw=4:expandtab
|
2014-05-14 20:58:12 +02:00
|
|
|
*
|
2014-05-04 08:34:13 +02:00
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
Finish abstracting native client
Firstly, don't initialize textsecure.nativclient unless the browser
supports it. The mimetype-check trick is hewn from nacl-common.js.
Secondly, nativeclient crypto functions will all automatically wait for
the module to load before sending messages, so we needn't register any
onload callbacks outside nativeclient.js. (Previously, if you wanted to
do crypto with native client, you would have to register a call back and
wait for the module to load.) Now that the native client crypto is
encapsulated behind a nice interface, it can handle all that
onload-callback jazz internally: if the module isn't loaded when you
call a nativeclient function, return a promise that waits for the load
callback, and eventually resolves with the result of the requested
command. This removes the need for textsecure.registerOnLoadCallback.
Finally, although native client has its quirks, it's significantly
faster than the alternative (emscripten compiled js), so this commit
also lets the crypto backend use native client opportunistically, if
it's available, falling back to js if not, which should make us
compatible with older versions of chrome and chromium.
2014-11-09 02:26:20 +01:00
|
|
|
;(function() {
|
|
|
|
'use strict';
|
2014-12-20 01:49:18 +01:00
|
|
|
var conversations = new Whisper.ConversationCollection();
|
|
|
|
var messages = new Whisper.MessageCollection();
|
|
|
|
|
|
|
|
if (!localStorage.getItem('first_install_ran')) {
|
|
|
|
localStorage.setItem('first_install_ran', 1);
|
|
|
|
extension.navigator.tabs.create("options.html");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (textsecure.registration.isDone()) {
|
|
|
|
init();
|
|
|
|
}
|
2014-12-29 16:19:30 +01:00
|
|
|
extension.on('registration_done', init);
|
Finish abstracting native client
Firstly, don't initialize textsecure.nativclient unless the browser
supports it. The mimetype-check trick is hewn from nacl-common.js.
Secondly, nativeclient crypto functions will all automatically wait for
the module to load before sending messages, so we needn't register any
onload callbacks outside nativeclient.js. (Previously, if you wanted to
do crypto with native client, you would have to register a call back and
wait for the module to load.) Now that the native client crypto is
encapsulated behind a nice interface, it can handle all that
onload-callback jazz internally: if the module isn't loaded when you
call a nativeclient function, return a promise that waits for the load
callback, and eventually resolves with the result of the requested
command. This removes the need for textsecure.registerOnLoadCallback.
Finally, although native client has its quirks, it's significantly
faster than the alternative (emscripten compiled js), so this commit
also lets the crypto backend use native client opportunistically, if
it's available, falling back to js if not, which should make us
compatible with older versions of chrome and chromium.
2014-11-09 02:26:20 +01:00
|
|
|
|
2014-11-10 21:42:51 +01:00
|
|
|
function init() {
|
2014-12-20 01:49:18 +01:00
|
|
|
if (!textsecure.registration.isDone()) { return; }
|
|
|
|
|
|
|
|
// initialize the socket and start listening for messages
|
|
|
|
var socket = textsecure.api.getMessageWebsocket();
|
|
|
|
new WebSocketResource(socket, function(request) {
|
|
|
|
// TODO: handle different types of requests. for now we only expect
|
|
|
|
// PUT /messages <encrypted IncomingPushMessageSignal>
|
|
|
|
textsecure.protocol.decryptWebsocketMessage(request.body).then(function(plaintext) {
|
|
|
|
var proto = textsecure.protobuf.IncomingPushMessageSignal.decode(plaintext);
|
|
|
|
// After this point, decoding errors are not the server's
|
|
|
|
// fault, and we should handle them gracefully and tell the
|
|
|
|
// user they received an invalid message
|
|
|
|
request.respond(200, 'OK');
|
|
|
|
|
|
|
|
if (proto.type === textsecure.protobuf.IncomingPushMessageSignal.Type.RECEIPT) {
|
|
|
|
onDeliveryReceipt(proto);
|
|
|
|
} else {
|
|
|
|
onMessageReceived(proto);
|
|
|
|
}
|
|
|
|
|
|
|
|
}).catch(function(e) {
|
|
|
|
console.log("Error handling incoming message:", e);
|
|
|
|
extension.trigger('error', e);
|
|
|
|
request.respond(500, 'Bad encrypted websocket message');
|
|
|
|
});
|
|
|
|
});
|
2015-01-16 05:41:44 +01:00
|
|
|
var opened = false;
|
|
|
|
var panel = 0;
|
|
|
|
|
2015-01-22 09:21:37 +01:00
|
|
|
extension.browserAction(function () {
|
2015-01-16 05:41:44 +01:00
|
|
|
if (opened === false) {
|
|
|
|
opened = true;
|
2015-01-22 09:21:37 +01:00
|
|
|
extension.windows.open({
|
2015-01-22 09:33:32 +01:00
|
|
|
url: 'index.html',
|
2015-01-16 05:41:44 +01:00
|
|
|
type: 'panel',
|
|
|
|
focused: true,
|
|
|
|
width: 260, // 280 for chat
|
|
|
|
height: 440 // 420 for chat
|
|
|
|
}, function (window) {
|
|
|
|
var isPanelEnabled = window.alwaysOnTop;
|
|
|
|
panel = window.id;
|
|
|
|
});
|
|
|
|
} else if (opened === true) {
|
2015-01-22 09:21:37 +01:00
|
|
|
extension.windows.focus(panel);
|
2015-01-16 05:41:44 +01:00
|
|
|
}
|
2015-01-22 09:21:37 +01:00
|
|
|
extension.windows.onClosed(function (windowId) {
|
2015-01-16 05:41:44 +01:00
|
|
|
if (windowId === panel) {
|
|
|
|
panel = 0;
|
|
|
|
opened = false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2014-12-20 01:49:18 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
function onMessageReceived(pushMessage) {
|
|
|
|
var now = new Date().getTime();
|
|
|
|
var timestamp = pushMessage.timestamp.toNumber();
|
|
|
|
|
|
|
|
var conversation = conversations.add({
|
|
|
|
id : pushMessage.source,
|
|
|
|
type : 'private'
|
|
|
|
}, { merge : true } );
|
|
|
|
|
|
|
|
var message = messages.add({
|
|
|
|
source : pushMessage.source,
|
|
|
|
sourceDevice : pushMessage.sourceDevice,
|
|
|
|
relay : pushMessage.relay,
|
|
|
|
sent_at : timestamp,
|
|
|
|
received_at : now,
|
|
|
|
conversationId : pushMessage.source,
|
|
|
|
type : 'incoming'
|
|
|
|
});
|
|
|
|
|
|
|
|
var newUnreadCount = textsecure.storage.getUnencrypted("unreadCount", 0) + 1;
|
|
|
|
textsecure.storage.putUnencrypted("unreadCount", newUnreadCount);
|
|
|
|
extension.navigator.setBadgeText(newUnreadCount);
|
|
|
|
|
|
|
|
conversation.save().then(function() {
|
|
|
|
message.save().then(function() {
|
|
|
|
return new Promise(function(resolve) {
|
|
|
|
resolve(textsecure.protocol.handleIncomingPushMessageProto(pushMessage).then(
|
|
|
|
function(pushMessageContent) {
|
|
|
|
handlePushMessageContent(pushMessageContent, message);
|
|
|
|
}
|
|
|
|
));
|
|
|
|
}).catch(function(e) {
|
|
|
|
if (e.name === 'IncomingIdentityKeyError') {
|
|
|
|
e.args.push(message.id);
|
|
|
|
message.save({ errors : [e] }).then(function() {
|
|
|
|
extension.trigger('message', message); // notify frontend listeners
|
|
|
|
});
|
2014-12-24 00:33:35 +01:00
|
|
|
} else if (e.message === 'Bad MAC') {
|
|
|
|
message.save({ errors : [ _.pick(e, ['name', 'message'])]}).then(function() {
|
|
|
|
extension.trigger('message', message); // notify frontend listeners
|
|
|
|
});
|
2014-12-20 01:49:18 +01:00
|
|
|
} else {
|
2014-12-24 00:33:35 +01:00
|
|
|
console.log(e);
|
2014-12-20 01:49:18 +01:00
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
extension.on('message:decrypted', function(options) {
|
|
|
|
var message = messages.add({id: options.message_id});
|
|
|
|
message.fetch().then(function() {
|
|
|
|
var pushMessageContent = handlePushMessageContent(
|
|
|
|
new textsecure.protobuf.PushMessageContent(options.data),
|
|
|
|
message
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
function handlePushMessageContent(pushMessageContent, message) {
|
|
|
|
// This function can be called from the background script on an
|
|
|
|
// incoming message or from the frontend after the user accepts an
|
|
|
|
// identity key change.
|
2014-12-22 22:24:02 +01:00
|
|
|
var source = message.get('source');
|
|
|
|
return textsecure.processDecrypted(pushMessageContent, source).then(function(pushMessageContent) {
|
2014-12-20 01:49:18 +01:00
|
|
|
var now = new Date().getTime();
|
|
|
|
var conversationId = pushMessageContent.group ? pushMessageContent.group.id : source;
|
2014-12-25 00:03:03 +01:00
|
|
|
var conversation = new Whisper.Conversation({id: conversationId});
|
2014-12-22 22:24:12 +01:00
|
|
|
var attributes = {};
|
2014-12-25 00:03:03 +01:00
|
|
|
conversation.fetch().always(function() {
|
2014-12-22 22:24:12 +01:00
|
|
|
if (pushMessageContent.group &&
|
|
|
|
pushMessageContent.group.type === textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE) {
|
|
|
|
attributes = {
|
2014-12-25 00:03:03 +01:00
|
|
|
type : 'group',
|
2014-12-22 22:24:12 +01:00
|
|
|
groupId : pushMessageContent.group.id,
|
|
|
|
name : pushMessageContent.group.name,
|
|
|
|
avatar : pushMessageContent.group.avatar,
|
|
|
|
members : pushMessageContent.group.members,
|
|
|
|
};
|
2015-01-09 23:53:39 +01:00
|
|
|
var group_update = conversation.changedAttributes(_.pick(pushMessageContent.group, 'name', 'avatar'));
|
|
|
|
var difference = _.difference(pushMessageContent.group.members, conversation.get('members'));
|
|
|
|
if (difference.length > 0) {
|
|
|
|
group_update.joined = difference;
|
|
|
|
}
|
|
|
|
if (_.keys(group_update).length > 0) {
|
|
|
|
message.set({group_update: group_update});
|
|
|
|
}
|
2014-12-22 22:24:12 +01:00
|
|
|
}
|
|
|
|
attributes.active_at = now;
|
2014-12-20 01:49:18 +01:00
|
|
|
conversation.set(attributes);
|
|
|
|
|
|
|
|
message.set({
|
|
|
|
body : pushMessageContent.body,
|
|
|
|
conversationId : conversation.id,
|
|
|
|
attachments : pushMessageContent.attachments,
|
|
|
|
decrypted_at : now,
|
|
|
|
errors : []
|
|
|
|
});
|
|
|
|
|
2014-12-23 08:05:51 +01:00
|
|
|
if (message.get('sent_at') > conversation.get('timestamp')) {
|
|
|
|
conversation.set({ timestamp: message.get('sent_at'), lastMessage: message.get('body') });
|
|
|
|
}
|
|
|
|
|
2014-12-20 01:49:18 +01:00
|
|
|
conversation.save().then(function() {
|
|
|
|
message.save().then(function() {
|
|
|
|
extension.trigger('message', message); // notify frontend listeners
|
2014-11-16 22:19:51 +01:00
|
|
|
});
|
Finish abstracting native client
Firstly, don't initialize textsecure.nativclient unless the browser
supports it. The mimetype-check trick is hewn from nacl-common.js.
Secondly, nativeclient crypto functions will all automatically wait for
the module to load before sending messages, so we needn't register any
onload callbacks outside nativeclient.js. (Previously, if you wanted to
do crypto with native client, you would have to register a call back and
wait for the module to load.) Now that the native client crypto is
encapsulated behind a nice interface, it can handle all that
onload-callback jazz internally: if the module isn't loaded when you
call a nativeclient function, return a promise that waits for the load
callback, and eventually resolves with the result of the requested
command. This removes the need for textsecure.registerOnLoadCallback.
Finally, although native client has its quirks, it's significantly
faster than the alternative (emscripten compiled js), so this commit
also lets the crypto backend use native client opportunistically, if
it's available, falling back to js if not, which should make us
compatible with older versions of chrome and chromium.
2014-11-09 02:26:20 +01:00
|
|
|
});
|
2014-12-20 01:49:18 +01:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function onDeliveryReceipt(pushMessage) {
|
2014-12-20 02:15:57 +01:00
|
|
|
var timestamp = pushMessage.timestamp.toNumber();
|
|
|
|
var messages = new Whisper.MessageCollection();
|
|
|
|
var groups = new Whisper.ConversationCollection();
|
2014-12-22 06:01:21 +01:00
|
|
|
console.log('delivery receipt', pushMessage.source, timestamp);
|
2014-12-20 02:15:57 +01:00
|
|
|
messages.fetchSentAt(timestamp).then(function() {
|
|
|
|
groups.fetchGroups(pushMessage.source).then(function() {
|
2014-12-22 06:01:21 +01:00
|
|
|
for (var i in messages.where({type: 'outgoing'})) {
|
|
|
|
var message = messages.at(i);
|
2014-12-20 02:15:57 +01:00
|
|
|
var deliveries = message.get('delivered') || 0;
|
|
|
|
var conversationId = message.get('conversationId');
|
|
|
|
if (conversationId === pushMessage.source || groups.get(conversationId)) {
|
2014-12-22 06:01:21 +01:00
|
|
|
message.save({delivered: deliveries + 1}).then(function() {
|
|
|
|
extension.trigger('message', message); // notify frontend listeners
|
|
|
|
});
|
2014-12-20 02:15:57 +01:00
|
|
|
return;
|
|
|
|
// TODO: consider keeping a list of numbers we've
|
|
|
|
// successfully delivered to?
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}).fail(function() {
|
|
|
|
console.log('got delivery receipt for unknown message', pushMessage.source, timestamp);
|
|
|
|
});
|
2014-11-10 21:42:51 +01:00
|
|
|
};
|
2015-01-16 22:40:17 +01:00
|
|
|
|
2015-01-22 05:27:42 +01:00
|
|
|
var windowMap = Whisper.windowMap = new Whisper.Bimap('windowId', 'modelId');
|
2015-01-16 22:40:17 +01:00
|
|
|
|
2015-01-22 05:27:42 +01:00
|
|
|
// make sure panels are cleaned up on close
|
2015-01-22 09:21:37 +01:00
|
|
|
extension.windows.onClosed(function (windowId) {
|
2015-01-22 05:27:42 +01:00
|
|
|
if (windowMap.windowId[windowId]) {
|
|
|
|
closeConversation(windowId);
|
2015-01-19 02:43:25 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
Finish abstracting native client
Firstly, don't initialize textsecure.nativclient unless the browser
supports it. The mimetype-check trick is hewn from nacl-common.js.
Secondly, nativeclient crypto functions will all automatically wait for
the module to load before sending messages, so we needn't register any
onload callbacks outside nativeclient.js. (Previously, if you wanted to
do crypto with native client, you would have to register a call back and
wait for the module to load.) Now that the native client crypto is
encapsulated behind a nice interface, it can handle all that
onload-callback jazz internally: if the module isn't loaded when you
call a nativeclient function, return a promise that waits for the load
callback, and eventually resolves with the result of the requested
command. This removes the need for textsecure.registerOnLoadCallback.
Finally, although native client has its quirks, it's significantly
faster than the alternative (emscripten compiled js), so this commit
also lets the crypto backend use native client opportunistically, if
it's available, falling back to js if not, which should make us
compatible with older versions of chrome and chromium.
2014-11-09 02:26:20 +01:00
|
|
|
})();
|