sendMessage refactor, initial group stuff (breaks message storage)
This commit is contained in:
parent
fb2aa6144c
commit
d0fd3e94d8
13 changed files with 335 additions and 219 deletions
|
@ -38,6 +38,8 @@
|
||||||
<script type="text/javascript" src="js/models/messages.js"></script>
|
<script type="text/javascript" src="js/models/messages.js"></script>
|
||||||
<script type="text/javascript" src="js/models/threads.js"></script>
|
<script type="text/javascript" src="js/models/threads.js"></script>
|
||||||
<script type="text/javascript" src="js/api.js"></script>
|
<script type="text/javascript" src="js/api.js"></script>
|
||||||
|
<script type="text/javascript" src="js/sendmessage.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="js/chromium.js"></script>
|
<script type="text/javascript" src="js/chromium.js"></script>
|
||||||
<script type="text/javascript" src="js/background.js"></script>
|
<script type="text/javascript" src="js/background.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
34
js/api.js
34
js/api.js
|
@ -226,6 +226,40 @@ window.textsecure.api = function() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.putAttachment = function(encryptedBin) {
|
||||||
|
return doAjax({
|
||||||
|
call : 'attachment',
|
||||||
|
httpType : 'GET',
|
||||||
|
do_auth : true,
|
||||||
|
}).then(function(response) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
$.ajax(response.location, {
|
||||||
|
type : "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/octet-stream"
|
||||||
|
},
|
||||||
|
data: encryptedBin,
|
||||||
|
|
||||||
|
success : function() {
|
||||||
|
resolve(response.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
error : function(jqXHR, textStatus, errorThrown) {
|
||||||
|
var code = jqXHR.status;
|
||||||
|
if (code > 999 || code < 100)
|
||||||
|
code = -1;
|
||||||
|
|
||||||
|
var e = new Error(code);
|
||||||
|
e.name = "HTTPError";
|
||||||
|
if (jqXHR.responseJSON)
|
||||||
|
e.response = jqXHR.responseJSON;
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
self.getWebsocket = function() {
|
self.getWebsocket = function() {
|
||||||
var user = textsecure.storage.getUnencrypted("number_id");
|
var user = textsecure.storage.getUnencrypted("number_id");
|
||||||
var password = textsecure.storage.getEncrypted("password");
|
var password = textsecure.storage.getEncrypted("password");
|
||||||
|
|
26
js/crypto.js
26
js/crypto.js
|
@ -619,18 +619,38 @@ window.textsecure.crypto = function() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.encryptAttachment = function(plaintext, keys, iv) {
|
||||||
|
var aes_key = keys.slice(0, 32);
|
||||||
|
var mac_key = keys.slice(32, 64);
|
||||||
|
|
||||||
|
return window.crypto.subtle.encrypt({name: "AES-CBC", iv: iv}, aes_key, plaintext).then(function(ciphertext) {
|
||||||
|
var ivAndCiphertext = new Uint8Array(16 + ciphertext.byteLength);
|
||||||
|
ivAndCiphertext.set(iv);
|
||||||
|
ivAndCiphertext.set(ciphertext, 16);
|
||||||
|
|
||||||
|
return calculateMAC(ivAndCiphertext.buffer, mac_key).then(function(mac) {
|
||||||
|
var encryptedBin = new Uint8Array(16 + ciphertext.byteLength + 32);
|
||||||
|
encryptedBin.set(ivAndCiphertext.buffer);
|
||||||
|
encryptedBin.set(mac, 16 + ciphertext.byteLength);
|
||||||
|
return encryptedBin.buffer;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
self.handleIncomingPushMessageProto = function(proto) {
|
self.handleIncomingPushMessageProto = function(proto) {
|
||||||
switch(proto.type) {
|
switch(proto.type) {
|
||||||
case 0: //TYPE_MESSAGE_PLAINTEXT
|
case textsecure.protos.IncomingPushMessageProtobuf.Type.PLAINTEXT:
|
||||||
return Promise.resolve(textsecure.protos.decodePushMessageContentProtobuf(getString(proto.message)));
|
return Promise.resolve(textsecure.protos.decodePushMessageContentProtobuf(getString(proto.message)));
|
||||||
case 1: //TYPE_MESSAGE_CIPHERTEXT
|
case textsecure.protos.IncomingPushMessageProtobuf.Type.CIPHERTEXT:
|
||||||
var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice);
|
var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice);
|
||||||
return decryptWhisperMessage(from, getString(proto.message));
|
return decryptWhisperMessage(from, getString(proto.message));
|
||||||
case 3: //TYPE_MESSAGE_PREKEY_BUNDLE
|
case textsecure.protos.IncomingPushMessageProtobuf.Type.PREKEY_BUNDLE:
|
||||||
if (proto.message.readUint8() != (2 << 4 | 2))
|
if (proto.message.readUint8() != (2 << 4 | 2))
|
||||||
throw new Error("Bad version byte");
|
throw new Error("Bad version byte");
|
||||||
var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice);
|
var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice);
|
||||||
return handlePreKeyWhisperMessage(from, getString(proto.message));
|
return handlePreKeyWhisperMessage(from, getString(proto.message));
|
||||||
|
default:
|
||||||
|
return new Promise(function(resolve, reject) { reject(new Error("Unknown message type")); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,8 @@ textsecure.api.sendMessages = function(destination, messageArray) {
|
||||||
msg.destinationRegistrationId === undefined ||
|
msg.destinationRegistrationId === undefined ||
|
||||||
msg.body === undefined ||
|
msg.body === undefined ||
|
||||||
msg.timestamp == undefined ||
|
msg.timestamp == undefined ||
|
||||||
msg.relay !== undefined)
|
msg.relay !== undefined ||
|
||||||
|
msg.destination !== undefined)
|
||||||
throw new Error("Invalid message");
|
throw new Error("Invalid message");
|
||||||
|
|
||||||
messagesSentMap[destination + "." + messageArray[i].destinationDeviceId] = msg;
|
messagesSentMap[destination + "." + messageArray[i].destinationDeviceId] = msg;
|
||||||
|
|
166
js/helpers.js
166
js/helpers.js
|
@ -406,6 +406,21 @@ window.textsecure.storage = function() {
|
||||||
return self;
|
return self;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
|
/*********************
|
||||||
|
*** Group Storage ***
|
||||||
|
*********************/
|
||||||
|
self.groups = function() {
|
||||||
|
var self = {};
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
|
||||||
|
self.getNumbers = function(groupId) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}();
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
|
@ -567,157 +582,6 @@ window.textsecure.subscribeToPush = function() {
|
||||||
}
|
}
|
||||||
}();
|
}();
|
||||||
|
|
||||||
// sendMessage(numbers = [], message = PushMessageContentProto, callback(success/failure map))
|
|
||||||
window.textsecure.sendMessage = function() {
|
|
||||||
function getKeysForNumber(number, updateDevices) {
|
|
||||||
return textsecure.api.getKeysForNumber(number).then(function(response) {
|
|
||||||
var identityKey = getString(response[0].identityKey);
|
|
||||||
for (i in response)
|
|
||||||
if (getString(response[i].identityKey) != identityKey)
|
|
||||||
throw new Error("Identity key not consistent");
|
|
||||||
|
|
||||||
for (i in response) {
|
|
||||||
var updateDevice = (updateDevices === undefined);
|
|
||||||
if (!updateDevice)
|
|
||||||
for (j in updateDevices)
|
|
||||||
if (updateDevices[j] == response[i].deviceId)
|
|
||||||
updateDevice = true;
|
|
||||||
|
|
||||||
if (updateDevice)
|
|
||||||
textsecure.storage.devices.saveDeviceObject({
|
|
||||||
encodedNumber: number + "." + response[i].deviceId,
|
|
||||||
identityKey: response[i].identityKey,
|
|
||||||
publicKey: response[i].publicKey,
|
|
||||||
preKeyId: response[i].keyId,
|
|
||||||
registrationId: response[i].registrationId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// success_callback(server success/failure map), error_callback(error_msg)
|
|
||||||
// message == PushMessageContentProto (NOT STRING)
|
|
||||||
function sendMessageToDevices(number, deviceObjectList, message, success_callback, error_callback) {
|
|
||||||
var jsonData = [];
|
|
||||||
var relay = undefined;
|
|
||||||
var promises = [];
|
|
||||||
|
|
||||||
var addEncryptionFor = function(i) {
|
|
||||||
if (deviceObjectList[i].relay !== undefined) {
|
|
||||||
if (relay === undefined)
|
|
||||||
relay = deviceObjectList[i].relay;
|
|
||||||
else if (relay != deviceObjectList[i].relay)
|
|
||||||
return new Promise(function() { throw new Error("Mismatched relays for number " + number); });
|
|
||||||
} else {
|
|
||||||
if (relay === undefined)
|
|
||||||
relay = "";
|
|
||||||
else if (relay != "")
|
|
||||||
return new Promise(function() { throw new Error("Mismatched relays for number " + number); });
|
|
||||||
}
|
|
||||||
|
|
||||||
return textsecure.crypto.encryptMessageFor(deviceObjectList[i], message).then(function(encryptedMsg) {
|
|
||||||
jsonData[i] = {
|
|
||||||
type: encryptedMsg.type,
|
|
||||||
destination: number,
|
|
||||||
destinationDeviceId: textsecure.utils.unencodeNumber(deviceObjectList[i].encodedNumber)[1],
|
|
||||||
destinationRegistrationId: deviceObjectList[i].registrationId,
|
|
||||||
body: encryptedMsg.body,
|
|
||||||
timestamp: new Date().getTime()
|
|
||||||
};
|
|
||||||
|
|
||||||
if (deviceObjectList[i].relay !== undefined)
|
|
||||||
jsonData[i].relay = deviceObjectList[i].relay;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < deviceObjectList.length; i++)
|
|
||||||
promises[i] = addEncryptionFor(i);
|
|
||||||
return Promise.all(promises).then(function() {
|
|
||||||
return textsecure.api.sendMessages(number, jsonData);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var tryMessageAgain = function(number, encodedMessage, callback) {
|
|
||||||
//TODO: Wipe identity key!
|
|
||||||
var message = textsecure.protos.decodePushMessageContentProtobuf(encodedMessage);
|
|
||||||
textsecure.sendMessage([number], message, callback);
|
|
||||||
}
|
|
||||||
textsecure.replay.registerReplayFunction(tryMessageAgain, textsecure.replay.SEND_MESSAGE);
|
|
||||||
|
|
||||||
return function(numbers, message, callback) {
|
|
||||||
var numbersCompleted = 0;
|
|
||||||
var errors = [];
|
|
||||||
var successfulNumbers = [];
|
|
||||||
|
|
||||||
var numberCompleted = function() {
|
|
||||||
numbersCompleted++;
|
|
||||||
if (numbersCompleted >= numbers.length)
|
|
||||||
callback({success: successfulNumbers, failure: errors});
|
|
||||||
}
|
|
||||||
|
|
||||||
var registerError = function(number, message, error) {
|
|
||||||
if (error.humanError)
|
|
||||||
message = error.humanError;
|
|
||||||
errors[errors.length] = { number: number, reason: message, error: error };
|
|
||||||
numberCompleted();
|
|
||||||
}
|
|
||||||
|
|
||||||
var doSendMessage;
|
|
||||||
var reloadDevicesAndSend = function(number, recurse) {
|
|
||||||
return function() {
|
|
||||||
var devicesForNumber = textsecure.storage.devices.getDeviceObjectsForNumber(number);
|
|
||||||
if (devicesForNumber.length == 0)
|
|
||||||
registerError(number, "Go empty device list when loading device keys", null);
|
|
||||||
else
|
|
||||||
doSendMessage(number, devicesForNumber, recurse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
doSendMessage = function(number, devicesForNumber, recurse) {
|
|
||||||
return sendMessageToDevices(number, devicesForNumber, message).then(function(result) {
|
|
||||||
successfulNumbers[successfulNumbers.length] = number;
|
|
||||||
numberCompleted();
|
|
||||||
}).catch(function(error) {
|
|
||||||
if (error instanceof Error && error.name == "HTTPError" && (error.message == 410 || error.message == 409)) {
|
|
||||||
if (!recurse)
|
|
||||||
return registerError(number, "Hit retry limit attempting to reload device list", error);
|
|
||||||
|
|
||||||
if (error.message == 409)
|
|
||||||
textsecure.storage.devices.removeDeviceIdsForNumber(number, error.response.extraDevices);
|
|
||||||
|
|
||||||
var resetDevices = ((error.message == 410) ? error.response.staleDevices : error.response.missingDevices);
|
|
||||||
getKeysForNumber(number, resetDevices)
|
|
||||||
.then(reloadDevicesAndSend(number, false))
|
|
||||||
.catch(function(error) {
|
|
||||||
if (error.message !== "Identity key changed")
|
|
||||||
registerError(number, "Failed to reload device keys", error);
|
|
||||||
else {
|
|
||||||
error = textsecure.replay.createReplayableError("The destination's identity key has changed", "The identity of the destination has changed. This may be malicious, or the destination may have simply reinstalled TextSecure.",
|
|
||||||
textsecure.replay.SEND_MESSAGE, [number, getString(message.encode())]);
|
|
||||||
registerError(number, "Identity key changed", error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else
|
|
||||||
registerError(number, "Failed to create or send message", error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < numbers.length; i++) {
|
|
||||||
var number = numbers[i];
|
|
||||||
var devicesForNumber = textsecure.storage.devices.getDeviceObjectsForNumber(number);
|
|
||||||
|
|
||||||
if (devicesForNumber.length == 0) {
|
|
||||||
getKeysForNumber(number)
|
|
||||||
.then(reloadDevicesAndSend(number, true))
|
|
||||||
.catch(function(error) {
|
|
||||||
registerError(number, "Failed to retreive new device keys for number " + number, error);
|
|
||||||
});
|
|
||||||
} else
|
|
||||||
doSendMessage(number, devicesForNumber, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
|
|
||||||
window.textsecure.register = function() {
|
window.textsecure.register = function() {
|
||||||
return function(number, verificationCode, singleDevice, stepDone) {
|
return function(number, verificationCode, singleDevice, stepDone) {
|
||||||
var signalingKey = textsecure.crypto.getRandomBytes(32 + 20);
|
var signalingKey = textsecure.crypto.getRandomBytes(32 + 20);
|
||||||
|
|
|
@ -10,10 +10,6 @@ var Whisper = Whisper || {};
|
||||||
if (missing.length) { return "Message must have " + missing; }
|
if (missing.length) { return "Message must have " + missing; }
|
||||||
},
|
},
|
||||||
|
|
||||||
toProto: function() {
|
|
||||||
return new textsecure.protos.PushMessageContentProtobuf({body: this.get('body')});
|
|
||||||
},
|
|
||||||
|
|
||||||
thread: function() {
|
thread: function() {
|
||||||
return Whisper.Threads.get(this.get('threadId'));
|
return Whisper.Threads.get(this.get('threadId'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,27 +13,26 @@ var Whisper = Whisper || {};
|
||||||
},
|
},
|
||||||
|
|
||||||
validate: function(attributes, options) {
|
validate: function(attributes, options) {
|
||||||
var required = ['id', 'type', 'recipients', 'timestamp', 'image', 'name'];
|
var required = ['id', 'type', 'timestamp', 'image', 'name'];
|
||||||
var missing = _.filter(required, function(attr) { return !attributes[attr]; });
|
var missing = _.filter(required, function(attr) { return !attributes[attr]; });
|
||||||
if (missing.length) { return "Thread must have " + missing; }
|
if (missing.length) { return "Thread must have " + missing; }
|
||||||
if (attributes.recipients.length === 0) {
|
|
||||||
return "No recipients for thread " + this.id;
|
|
||||||
}
|
|
||||||
for (var person in attributes.recipients) {
|
|
||||||
if (!person) return "Invalid recipient";
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
sendMessage: function(message) {
|
sendMessage: function(message) {
|
||||||
return new Promise(function(resolve) {
|
var m = Whisper.Messages.addOutgoingMessage(message, this);
|
||||||
var m = Whisper.Messages.addOutgoingMessage(message, this);
|
if (this.get('type') == 'private')
|
||||||
textsecure.sendMessage(this.get('recipients'), m.toProto(),
|
var promise = textsecure.messaging.sendMessageToNumber(this.get('id'), message, []);
|
||||||
function(result) {
|
else
|
||||||
console.log(result);
|
var promise = textsecure.messaging.sendMessageToGroup(this.get('id'), message, []);
|
||||||
resolve();
|
promise.then(
|
||||||
}
|
function(result) {
|
||||||
);
|
console.log(result);
|
||||||
}.bind(this));
|
}
|
||||||
|
).catch(
|
||||||
|
function(error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
messages: function() {
|
messages: function() {
|
||||||
|
@ -51,23 +50,13 @@ var Whisper = Whisper || {};
|
||||||
return thread;
|
return thread;
|
||||||
},
|
},
|
||||||
|
|
||||||
findOrCreateForRecipients: function(recipients) {
|
findOrCreateForRecipient: function(recipient) {
|
||||||
var attributes = {};
|
var attributes = {};
|
||||||
if (recipients.length > 1) {
|
attributes = {
|
||||||
attributes = {
|
id : recipient,
|
||||||
//TODO group id formatting?
|
name : recipient,
|
||||||
name : recipients,
|
type : 'private',
|
||||||
recipients : recipients,
|
};
|
||||||
type : 'group',
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
attributes = {
|
|
||||||
id : recipients[0],
|
|
||||||
name : recipients[0],
|
|
||||||
recipients : recipients,
|
|
||||||
type : 'private',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return this.findOrCreate(attributes);
|
return this.findOrCreate(attributes);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -77,14 +66,12 @@ var Whisper = Whisper || {};
|
||||||
attributes = {
|
attributes = {
|
||||||
id : decrypted.message.group.id,
|
id : decrypted.message.group.id,
|
||||||
name : decrypted.message.group.name,
|
name : decrypted.message.group.name,
|
||||||
recipients : decrypted.message.group.members,
|
|
||||||
type : 'group',
|
type : 'group',
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
attributes = {
|
attributes = {
|
||||||
id : decrypted.pushMessage.source,
|
id : decrypted.pushMessage.source,
|
||||||
name : decrypted.pushMessage.source,
|
name : decrypted.pushMessage.source,
|
||||||
recipients : [decrypted.pushMessage.source],
|
|
||||||
type : 'private'
|
type : 'private'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
216
js/sendmessage.js
Normal file
216
js/sendmessage.js
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
// sendMessage(numbers = [], message = PushMessageContentProto, callback(success/failure map))
|
||||||
|
window.textsecure.messaging = function() {
|
||||||
|
var self = {};
|
||||||
|
|
||||||
|
function getKeysForNumber(number, updateDevices) {
|
||||||
|
return textsecure.api.getKeysForNumber(number).then(function(response) {
|
||||||
|
var identityKey = getString(response[0].identityKey);
|
||||||
|
for (i in response)
|
||||||
|
if (getString(response[i].identityKey) != identityKey)
|
||||||
|
throw new Error("Identity key not consistent");
|
||||||
|
|
||||||
|
for (i in response) {
|
||||||
|
var updateDevice = (updateDevices === undefined);
|
||||||
|
if (!updateDevice)
|
||||||
|
for (j in updateDevices)
|
||||||
|
if (updateDevices[j] == response[i].deviceId)
|
||||||
|
updateDevice = true;
|
||||||
|
|
||||||
|
if (updateDevice)
|
||||||
|
textsecure.storage.devices.saveDeviceObject({
|
||||||
|
encodedNumber: number + "." + response[i].deviceId,
|
||||||
|
identityKey: response[i].identityKey,
|
||||||
|
publicKey: response[i].publicKey,
|
||||||
|
preKeyId: response[i].keyId,
|
||||||
|
registrationId: response[i].registrationId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// success_callback(server success/failure map), error_callback(error_msg)
|
||||||
|
// message == PushMessageContentProto (NOT STRING)
|
||||||
|
function sendMessageToDevices(number, deviceObjectList, message, success_callback, error_callback) {
|
||||||
|
var jsonData = [];
|
||||||
|
var relay = undefined;
|
||||||
|
var promises = [];
|
||||||
|
|
||||||
|
var addEncryptionFor = function(i) {
|
||||||
|
if (deviceObjectList[i].relay !== undefined) {
|
||||||
|
if (relay === undefined)
|
||||||
|
relay = deviceObjectList[i].relay;
|
||||||
|
else if (relay != deviceObjectList[i].relay)
|
||||||
|
return new Promise(function() { throw new Error("Mismatched relays for number " + number); });
|
||||||
|
} else {
|
||||||
|
if (relay === undefined)
|
||||||
|
relay = "";
|
||||||
|
else if (relay != "")
|
||||||
|
return new Promise(function() { throw new Error("Mismatched relays for number " + number); });
|
||||||
|
}
|
||||||
|
|
||||||
|
return textsecure.crypto.encryptMessageFor(deviceObjectList[i], message).then(function(encryptedMsg) {
|
||||||
|
jsonData[i] = {
|
||||||
|
type: encryptedMsg.type,
|
||||||
|
destinationDeviceId: textsecure.utils.unencodeNumber(deviceObjectList[i].encodedNumber)[1],
|
||||||
|
destinationRegistrationId: deviceObjectList[i].registrationId,
|
||||||
|
body: encryptedMsg.body,
|
||||||
|
timestamp: new Date().getTime()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (deviceObjectList[i].relay !== undefined)
|
||||||
|
jsonData[i].relay = deviceObjectList[i].relay;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < deviceObjectList.length; i++)
|
||||||
|
promises[i] = addEncryptionFor(i);
|
||||||
|
return Promise.all(promises).then(function() {
|
||||||
|
return textsecure.api.sendMessages(number, jsonData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var tryMessageAgain = function(number, encodedMessage, callback) {
|
||||||
|
//TODO: Wipe identity key!
|
||||||
|
var message = textsecure.protos.decodePushMessageContentProtobuf(encodedMessage);
|
||||||
|
textsecure.sendMessage([number], message, callback);
|
||||||
|
}
|
||||||
|
textsecure.replay.registerReplayFunction(tryMessageAgain, textsecure.replay.SEND_MESSAGE);
|
||||||
|
|
||||||
|
var sendMessageProto = function(numbers, message, callback) {
|
||||||
|
var numbersCompleted = 0;
|
||||||
|
var errors = [];
|
||||||
|
var successfulNumbers = [];
|
||||||
|
|
||||||
|
var numberCompleted = function() {
|
||||||
|
numbersCompleted++;
|
||||||
|
if (numbersCompleted >= numbers.length)
|
||||||
|
callback({success: successfulNumbers, failure: errors});
|
||||||
|
}
|
||||||
|
|
||||||
|
var registerError = function(number, message, error) {
|
||||||
|
if (error.humanError)
|
||||||
|
message = error.humanError;
|
||||||
|
errors[errors.length] = { number: number, reason: message, error: error };
|
||||||
|
numberCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
var doSendMessage;
|
||||||
|
var reloadDevicesAndSend = function(number, recurse) {
|
||||||
|
return function() {
|
||||||
|
var devicesForNumber = textsecure.storage.devices.getDeviceObjectsForNumber(number);
|
||||||
|
if (devicesForNumber.length == 0)
|
||||||
|
registerError(number, "Go empty device list when loading device keys", null);
|
||||||
|
else
|
||||||
|
doSendMessage(number, devicesForNumber, recurse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doSendMessage = function(number, devicesForNumber, recurse) {
|
||||||
|
return sendMessageToDevices(number, devicesForNumber, message).then(function(result) {
|
||||||
|
successfulNumbers[successfulNumbers.length] = number;
|
||||||
|
numberCompleted();
|
||||||
|
}).catch(function(error) {
|
||||||
|
if (error instanceof Error && error.name == "HTTPError" && (error.message == 410 || error.message == 409)) {
|
||||||
|
if (!recurse)
|
||||||
|
return registerError(number, "Hit retry limit attempting to reload device list", error);
|
||||||
|
|
||||||
|
if (error.message == 409)
|
||||||
|
textsecure.storage.devices.removeDeviceIdsForNumber(number, error.response.extraDevices);
|
||||||
|
|
||||||
|
var resetDevices = ((error.message == 410) ? error.response.staleDevices : error.response.missingDevices);
|
||||||
|
getKeysForNumber(number, resetDevices)
|
||||||
|
.then(reloadDevicesAndSend(number, false))
|
||||||
|
.catch(function(error) {
|
||||||
|
if (error.message !== "Identity key changed")
|
||||||
|
registerError(number, "Failed to reload device keys", error);
|
||||||
|
else {
|
||||||
|
error = textsecure.replay.createReplayableError("The destination's identity key has changed", "The identity of the destination has changed. This may be malicious, or the destination may have simply reinstalled TextSecure.",
|
||||||
|
textsecure.replay.SEND_MESSAGE, [number, getString(message.encode())]);
|
||||||
|
registerError(number, "Identity key changed", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else
|
||||||
|
registerError(number, "Failed to create or send message", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < numbers.length; i++) {
|
||||||
|
var number = numbers[i];
|
||||||
|
var devicesForNumber = textsecure.storage.devices.getDeviceObjectsForNumber(number);
|
||||||
|
|
||||||
|
if (devicesForNumber.length == 0) {
|
||||||
|
getKeysForNumber(number)
|
||||||
|
.then(reloadDevicesAndSend(number, true))
|
||||||
|
.catch(function(error) {
|
||||||
|
registerError(number, "Failed to retreive new device keys for number " + number, error);
|
||||||
|
});
|
||||||
|
} else
|
||||||
|
doSendMessage(number, devicesForNumber, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var makeAttachmentPointer = function(attachment) {
|
||||||
|
var proto = new textsecure.protos.PushMessageContentProtobuf.AttachmentPointer();
|
||||||
|
proto.key = textsecure.crypto.getRandomBytes(64);
|
||||||
|
|
||||||
|
var iv = textsecure.crypto.getRandomBytes(16);
|
||||||
|
return textsecure.crypto.encryptAttachment(attachment.data, proto.key, iv).then(function(encryptedBin) {
|
||||||
|
return textsecure.api.putAttachment(encryptedBin).then(function(id) {
|
||||||
|
proto.id = id;
|
||||||
|
proto.contentType = attachment.contentType;
|
||||||
|
return proto;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sendMessageToNumber = function(number, messageText, attachments) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var proto = new textsecure.protos.PushMessageContentProtobuf();
|
||||||
|
proto.body = messageText;
|
||||||
|
|
||||||
|
var promises = [];
|
||||||
|
for (i in attachments)
|
||||||
|
promises.push(makeAttachmentPointer(attachments[i]));
|
||||||
|
Promise.all(promises).then(function(attachmentsArray) {
|
||||||
|
proto.attachments = attachmentsArray;
|
||||||
|
sendMessageProto([number], proto, function(res) {
|
||||||
|
if (res.failure.length > 0)
|
||||||
|
reject(res.failure[0].error);
|
||||||
|
else
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sendMessageToGroup = function(groupId, messageText, attachments) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var proto = new textsecure.protos.PushMessageContentProtobuf();
|
||||||
|
proto.body = messageText;
|
||||||
|
proto.group = new textsecure.protos.PushMessageContentProtobuf.GroupContext();
|
||||||
|
proto.group.id = groupId;
|
||||||
|
proto.group.type = textsecure.protos.PushMessageContentProtobuf.GroupContext.DELIVER;
|
||||||
|
|
||||||
|
var numbers = textsecure.storage.groups.getNumbers(groupId);
|
||||||
|
|
||||||
|
var promises = [];
|
||||||
|
for (i in attachments)
|
||||||
|
promises.push(makeAttachmentPointer(attachments[i]));
|
||||||
|
Promise.all(promises).then(function(attachmentsArray) {
|
||||||
|
proto.attachments = attachmentsArray;
|
||||||
|
sendMessageProto(numbers, proto, function(res) {
|
||||||
|
if (res.failure.length > 0) {
|
||||||
|
reject(res.failure);
|
||||||
|
} else
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.closeSession = function(number) {
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}();
|
38
js/test.js
38
js/test.js
|
@ -114,7 +114,7 @@ textsecure.registerOnLoadFunction(function() {
|
||||||
|
|
||||||
var text_message = new PushMessageProto();
|
var text_message = new PushMessageProto();
|
||||||
text_message.body = "Hi Mom";
|
text_message.body = "Hi Mom";
|
||||||
var server_message = {type: 0, // unencrypted
|
var server_message = {type: 4, // unencrypted
|
||||||
source: "+19999999999", timestamp: 42, message: text_message.encode() };
|
source: "+19999999999", timestamp: 42, message: text_message.encode() };
|
||||||
|
|
||||||
return textsecure.crypto.handleIncomingPushMessageProto(server_message).then(function(message) {
|
return textsecure.crypto.handleIncomingPushMessageProto(server_message).then(function(message) {
|
||||||
|
@ -367,28 +367,20 @@ textsecure.registerOnLoadFunction(function() {
|
||||||
if (data.getKeys !== undefined)
|
if (data.getKeys !== undefined)
|
||||||
getKeysForNumberMap[remoteNumber] = data.getKeys;
|
getKeysForNumberMap[remoteNumber] = data.getKeys;
|
||||||
|
|
||||||
var message = new textsecure.protos.PushMessageContentProtobuf();
|
return textsecure.messaging.sendMessageToNumber(remoteNumber, data.smsText, []).then(function() {
|
||||||
message.body = data.smsText;
|
var msg = messagesSentMap[remoteNumber + "." + 0];
|
||||||
|
delete messagesSentMap[remoteNumber + "." + 0];
|
||||||
return new Promise(function(resolve) {
|
//XXX: This should be all we do: stepDone(getString(data.expectedCiphertext) == getString(res.body));
|
||||||
textsecure.sendMessage([remoteNumber], message, function(res) {
|
if (msg.type == 1) {
|
||||||
if (res.failure.length != 0 || res.success.length != 1 || res.success[0] != remoteNumber)
|
var expectedString = getString(data.expectedCiphertext);
|
||||||
return resolve(false);
|
var decoded = textsecure.protos.decodeWhisperMessageProtobuf(expectedString.substring(1, expectedString.length - 8));
|
||||||
|
var result = getString(msg.body);
|
||||||
var msg = messagesSentMap[remoteNumber + "." + 0];
|
return getString(decoded.encode()) == result.substring(1, result.length - 8);
|
||||||
delete messagesSentMap[remoteNumber + "." + 0];
|
} else {
|
||||||
//XXX: This should be all we do: stepDone(getString(data.expectedCiphertext) == getString(res.body));
|
var decoded = textsecure.protos.decodePreKeyWhisperMessageProtobuf(getString(data.expectedCiphertext).substr(1));
|
||||||
if (msg.type == 1) {
|
var result = getString(msg.body).substring(1);
|
||||||
var expectedString = getString(data.expectedCiphertext);
|
return getString(decoded.encode()) == result;
|
||||||
var decoded = textsecure.protos.decodeWhisperMessageProtobuf(expectedString.substring(1, expectedString.length - 8));
|
}
|
||||||
var result = getString(msg.body);
|
|
||||||
resolve(getString(decoded.encode()) == result.substring(1, result.length - 8));
|
|
||||||
} else {
|
|
||||||
var decoded = textsecure.protos.decodePreKeyWhisperMessageProtobuf(getString(data.expectedCiphertext).substr(1));
|
|
||||||
var result = getString(msg.body).substring(1);
|
|
||||||
resolve(getString(decoded.encode()) == result);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ var Whisper = Whisper || {};
|
||||||
numbers = _.filter(numbers, _.identity); // rm undefined, null, "", etc...
|
numbers = _.filter(numbers, _.identity); // rm undefined, null, "", etc...
|
||||||
if (numbers.length) {
|
if (numbers.length) {
|
||||||
$('#send').hide();
|
$('#send').hide();
|
||||||
Whisper.Threads.findOrCreateForRecipients(numbers).trigger('select');
|
Whisper.Threads.findOrCreateForRecipient(numbers).trigger('select');
|
||||||
} else {
|
} else {
|
||||||
Whisper.notify('recipient missing or invalid');
|
Whisper.notify('recipient missing or invalid');
|
||||||
$('#send input[type=text]').focus();
|
$('#send input[type=text]').focus();
|
||||||
|
|
|
@ -66,6 +66,8 @@
|
||||||
<script type="text/javascript" src="js/models/messages.js"></script>
|
<script type="text/javascript" src="js/models/messages.js"></script>
|
||||||
<script type="text/javascript" src="js/models/threads.js"></script>
|
<script type="text/javascript" src="js/models/threads.js"></script>
|
||||||
<script type="text/javascript" src="js/api.js"></script>
|
<script type="text/javascript" src="js/api.js"></script>
|
||||||
|
<script type="text/javascript" src="js/sendmessage.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="js/chromium.js"></script>
|
<script type="text/javascript" src="js/chromium.js"></script>
|
||||||
<script type="text/javascript" src="js/options.js"></script>
|
<script type="text/javascript" src="js/options.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -68,8 +68,9 @@
|
||||||
<script type="text/javascript" src="js/models/messages.js"></script>
|
<script type="text/javascript" src="js/models/messages.js"></script>
|
||||||
<script type="text/javascript" src="js/models/threads.js"></script>
|
<script type="text/javascript" src="js/models/threads.js"></script>
|
||||||
<script type="text/javascript" src="js/api.js"></script>
|
<script type="text/javascript" src="js/api.js"></script>
|
||||||
<script type="text/javascript" src="js/chromium.js"></script>
|
<script type="text/javascript" src="js/sendmessage.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="js/chromium.js"></script>
|
||||||
<script type="text/javascript" src="js/views/notifications.js"></script>
|
<script type="text/javascript" src="js/views/notifications.js"></script>
|
||||||
<script type="text/javascript" src="js/views/message.js"></script>
|
<script type="text/javascript" src="js/views/message.js"></script>
|
||||||
<script type="text/javascript" src="js/views/conversation.js"></script>
|
<script type="text/javascript" src="js/views/conversation.js"></script>
|
||||||
|
|
|
@ -48,8 +48,9 @@
|
||||||
<script type="text/javascript" src="js/models/messages.js"></script>
|
<script type="text/javascript" src="js/models/messages.js"></script>
|
||||||
<script type="text/javascript" src="js/models/threads.js"></script>
|
<script type="text/javascript" src="js/models/threads.js"></script>
|
||||||
<script type="text/javascript" src="js/api.js"></script>
|
<script type="text/javascript" src="js/api.js"></script>
|
||||||
<script type="text/javascript" src="js/chromium.js"></script>
|
<script type="text/javascript" src="js/sendmessage.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="js/chromium.js"></script>
|
||||||
<script type="text/javascript" src="js/fake_api.js"></script>
|
<script type="text/javascript" src="js/fake_api.js"></script>
|
||||||
<script type="text/javascript" src="js/test.js"></script>
|
<script type="text/javascript" src="js/test.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
Loading…
Reference in a new issue