Now we've got everything prettified!
This commit is contained in:
parent
754d65ae2e
commit
a0ed993b42
30 changed files with 3562 additions and 2873 deletions
|
@ -18,6 +18,8 @@ ts/**/*.js
|
||||||
components/*
|
components/*
|
||||||
dist/*
|
dist/*
|
||||||
libtextsecure/libsignal-protocol.js
|
libtextsecure/libsignal-protocol.js
|
||||||
|
test/fixtures.js
|
||||||
|
test/blanket_mocha.js
|
||||||
|
|
||||||
/**/*.json
|
/**/*.json
|
||||||
/**/*.css
|
/**/*.css
|
||||||
|
|
|
@ -8,7 +8,7 @@ ProvisioningCipher.prototype = {
|
||||||
var masterEphemeral = provisionEnvelope.publicKey.toArrayBuffer();
|
var masterEphemeral = provisionEnvelope.publicKey.toArrayBuffer();
|
||||||
var message = provisionEnvelope.body.toArrayBuffer();
|
var message = provisionEnvelope.body.toArrayBuffer();
|
||||||
if (new Uint8Array(message)[0] != 1) {
|
if (new Uint8Array(message)[0] != 1) {
|
||||||
throw new Error("Bad version number on ProvisioningMessage");
|
throw new Error('Bad version number on ProvisioningMessage');
|
||||||
}
|
}
|
||||||
|
|
||||||
var iv = message.slice(1, 16 + 1);
|
var iv = message.slice(1, 16 + 1);
|
||||||
|
@ -16,27 +16,37 @@ ProvisioningCipher.prototype = {
|
||||||
var ivAndCiphertext = message.slice(0, message.byteLength - 32);
|
var ivAndCiphertext = message.slice(0, message.byteLength - 32);
|
||||||
var ciphertext = message.slice(16 + 1, message.byteLength - 32);
|
var ciphertext = message.slice(16 + 1, message.byteLength - 32);
|
||||||
|
|
||||||
return libsignal.Curve.async.calculateAgreement(
|
return libsignal.Curve.async
|
||||||
masterEphemeral, this.keyPair.privKey
|
.calculateAgreement(masterEphemeral, this.keyPair.privKey)
|
||||||
).then(function(ecRes) {
|
.then(function(ecRes) {
|
||||||
return libsignal.HKDF.deriveSecrets(
|
return libsignal.HKDF.deriveSecrets(
|
||||||
ecRes, new ArrayBuffer(32), "TextSecure Provisioning Message"
|
ecRes,
|
||||||
|
new ArrayBuffer(32),
|
||||||
|
'TextSecure Provisioning Message'
|
||||||
);
|
);
|
||||||
}).then(function(keys) {
|
})
|
||||||
return libsignal.crypto.verifyMAC(ivAndCiphertext, keys[1], mac, 32).then(function() {
|
.then(function(keys) {
|
||||||
|
return libsignal.crypto
|
||||||
|
.verifyMAC(ivAndCiphertext, keys[1], mac, 32)
|
||||||
|
.then(function() {
|
||||||
return libsignal.crypto.decrypt(keys[0], ciphertext, iv);
|
return libsignal.crypto.decrypt(keys[0], ciphertext, iv);
|
||||||
});
|
});
|
||||||
}).then(function(plaintext) {
|
})
|
||||||
var provisionMessage = textsecure.protobuf.ProvisionMessage.decode(plaintext);
|
.then(function(plaintext) {
|
||||||
|
var provisionMessage = textsecure.protobuf.ProvisionMessage.decode(
|
||||||
|
plaintext
|
||||||
|
);
|
||||||
var privKey = provisionMessage.identityKeyPrivate.toArrayBuffer();
|
var privKey = provisionMessage.identityKeyPrivate.toArrayBuffer();
|
||||||
|
|
||||||
return libsignal.Curve.async.createKeyPair(privKey).then(function(keyPair) {
|
return libsignal.Curve.async
|
||||||
|
.createKeyPair(privKey)
|
||||||
|
.then(function(keyPair) {
|
||||||
var ret = {
|
var ret = {
|
||||||
identityKeyPair: keyPair,
|
identityKeyPair: keyPair,
|
||||||
number: provisionMessage.number,
|
number: provisionMessage.number,
|
||||||
provisioningCode: provisionMessage.provisioningCode,
|
provisioningCode: provisionMessage.provisioningCode,
|
||||||
userAgent: provisionMessage.userAgent,
|
userAgent: provisionMessage.userAgent,
|
||||||
readReceipts : provisionMessage.readReceipts
|
readReceipts: provisionMessage.readReceipts,
|
||||||
};
|
};
|
||||||
if (provisionMessage.profileKey) {
|
if (provisionMessage.profileKey) {
|
||||||
ret.profileKey = provisionMessage.profileKey.toArrayBuffer();
|
ret.profileKey = provisionMessage.profileKey.toArrayBuffer();
|
||||||
|
@ -46,16 +56,24 @@ ProvisioningCipher.prototype = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getPublicKey: function() {
|
getPublicKey: function() {
|
||||||
return Promise.resolve().then(function() {
|
return Promise.resolve()
|
||||||
|
.then(
|
||||||
|
function() {
|
||||||
if (!this.keyPair) {
|
if (!this.keyPair) {
|
||||||
return libsignal.Curve.async.generateKeyPair().then(function(keyPair) {
|
return libsignal.Curve.async.generateKeyPair().then(
|
||||||
|
function(keyPair) {
|
||||||
this.keyPair = keyPair;
|
this.keyPair = keyPair;
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}.bind(this)).then(function() {
|
}.bind(this)
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
function() {
|
||||||
return this.keyPair.pubKey;
|
return this.keyPair.pubKey;
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
}
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
libsignal.ProvisioningCipher = function() {
|
libsignal.ProvisioningCipher = function() {
|
||||||
|
@ -64,5 +82,4 @@ libsignal.ProvisioningCipher = function() {
|
||||||
this.decrypt = cipher.decrypt.bind(cipher);
|
this.decrypt = cipher.decrypt.bind(cipher);
|
||||||
this.getPublicKey = cipher.getPublicKey.bind(cipher);
|
this.getPublicKey = cipher.getPublicKey.bind(cipher);
|
||||||
};
|
};
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -122,9 +122,10 @@ MessageReceiver.prototype.extend({
|
||||||
this.onEmpty();
|
this.onEmpty();
|
||||||
}
|
}
|
||||||
// possible 403 or network issue. Make an request to confirm
|
// possible 403 or network issue. Make an request to confirm
|
||||||
return this.server.getDevices(this.number)
|
return this.server
|
||||||
|
.getDevices(this.number)
|
||||||
.then(this.connect.bind(this)) // No HTTP error? Reconnect
|
.then(this.connect.bind(this)) // No HTTP error? Reconnect
|
||||||
.catch((e) => {
|
.catch(e => {
|
||||||
const event = new Event('error');
|
const event = new Event('error');
|
||||||
event.error = e;
|
event.error = e;
|
||||||
return this.dispatchAndWait(event);
|
return this.dispatchAndWait(event);
|
||||||
|
@ -146,10 +147,9 @@ MessageReceiver.prototype.extend({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const promise = textsecure.crypto.decryptWebsocketMessage(
|
const promise = textsecure.crypto
|
||||||
request.body,
|
.decryptWebsocketMessage(request.body, this.signalingKey)
|
||||||
this.signalingKey
|
.then(plaintext => {
|
||||||
).then((plaintext) => {
|
|
||||||
const envelope = textsecure.protobuf.Envelope.decode(plaintext);
|
const envelope = textsecure.protobuf.Envelope.decode(plaintext);
|
||||||
// After this point, decoding errors are not the server's
|
// After this point, decoding errors are not the server's
|
||||||
// fault, and we should handle them gracefully and tell the
|
// fault, and we should handle them gracefully and tell the
|
||||||
|
@ -159,18 +159,25 @@ MessageReceiver.prototype.extend({
|
||||||
return request.respond(200, 'OK');
|
return request.respond(200, 'OK');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.addToCache(envelope, plaintext).then(() => {
|
return this.addToCache(envelope, plaintext).then(
|
||||||
|
() => {
|
||||||
request.respond(200, 'OK');
|
request.respond(200, 'OK');
|
||||||
this.queueEnvelope(envelope);
|
this.queueEnvelope(envelope);
|
||||||
}, (error) => {
|
},
|
||||||
|
error => {
|
||||||
console.log(
|
console.log(
|
||||||
'handleRequest error trying to add message to cache:',
|
'handleRequest error trying to add message to cache:',
|
||||||
error && error.stack ? error.stack : error
|
error && error.stack ? error.stack : error
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
}).catch((e) => {
|
);
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
request.respond(500, 'Bad encrypted websocket message');
|
request.respond(500, 'Bad encrypted websocket message');
|
||||||
console.log('Error handling incoming message:', e && e.stack ? e.stack : e);
|
console.log(
|
||||||
|
'Error handling incoming message:',
|
||||||
|
e && e.stack ? e.stack : e
|
||||||
|
);
|
||||||
const ev = new Event('error');
|
const ev = new Event('error');
|
||||||
ev.error = e;
|
ev.error = e;
|
||||||
return this.dispatchAndWait(ev);
|
return this.dispatchAndWait(ev);
|
||||||
|
@ -203,7 +210,7 @@ MessageReceiver.prototype.extend({
|
||||||
this.incoming = [];
|
this.incoming = [];
|
||||||
|
|
||||||
const dispatchEmpty = () => {
|
const dispatchEmpty = () => {
|
||||||
console.log('MessageReceiver: emitting \'empty\' event');
|
console.log("MessageReceiver: emitting 'empty' event");
|
||||||
const ev = new Event('empty');
|
const ev = new Event('empty');
|
||||||
return this.dispatchAndWait(ev);
|
return this.dispatchAndWait(ev);
|
||||||
};
|
};
|
||||||
|
@ -224,7 +231,8 @@ MessageReceiver.prototype.extend({
|
||||||
const { incoming } = this;
|
const { incoming } = this;
|
||||||
this.incoming = [];
|
this.incoming = [];
|
||||||
|
|
||||||
const queueDispatch = () => this.addToQueue(() => {
|
const queueDispatch = () =>
|
||||||
|
this.addToQueue(() => {
|
||||||
console.log('drained');
|
console.log('drained');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -241,7 +249,7 @@ MessageReceiver.prototype.extend({
|
||||||
this.dispatchEvent(ev);
|
this.dispatchEvent(ev);
|
||||||
},
|
},
|
||||||
queueAllCached() {
|
queueAllCached() {
|
||||||
return this.getAllFromCache().then((items) => {
|
return this.getAllFromCache().then(items => {
|
||||||
for (let i = 0, max = items.length; i < max; i += 1) {
|
for (let i = 0, max = items.length; i < max; i += 1) {
|
||||||
this.queueCached(items[i]);
|
this.queueCached(items[i]);
|
||||||
}
|
}
|
||||||
|
@ -273,7 +281,9 @@ MessageReceiver.prototype.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getEnvelopeId(envelope) {
|
getEnvelopeId(envelope) {
|
||||||
return `${envelope.source}.${envelope.sourceDevice} ${envelope.timestamp.toNumber()}`;
|
return `${envelope.source}.${
|
||||||
|
envelope.sourceDevice
|
||||||
|
} ${envelope.timestamp.toNumber()}`;
|
||||||
},
|
},
|
||||||
stringToArrayBuffer(string) {
|
stringToArrayBuffer(string) {
|
||||||
// eslint-disable-next-line new-cap
|
// eslint-disable-next-line new-cap
|
||||||
|
@ -281,23 +291,28 @@ MessageReceiver.prototype.extend({
|
||||||
},
|
},
|
||||||
getAllFromCache() {
|
getAllFromCache() {
|
||||||
console.log('getAllFromCache');
|
console.log('getAllFromCache');
|
||||||
return textsecure.storage.unprocessed.getAll().then((items) => {
|
return textsecure.storage.unprocessed.getAll().then(items => {
|
||||||
console.log('getAllFromCache loaded', items.length, 'saved envelopes');
|
console.log('getAllFromCache loaded', items.length, 'saved envelopes');
|
||||||
|
|
||||||
return Promise.all(_.map(items, (item) => {
|
return Promise.all(
|
||||||
|
_.map(items, item => {
|
||||||
const attempts = 1 + (item.attempts || 0);
|
const attempts = 1 + (item.attempts || 0);
|
||||||
if (attempts >= 5) {
|
if (attempts >= 5) {
|
||||||
console.log('getAllFromCache final attempt for envelope', item.id);
|
console.log('getAllFromCache final attempt for envelope', item.id);
|
||||||
return textsecure.storage.unprocessed.remove(item.id);
|
return textsecure.storage.unprocessed.remove(item.id);
|
||||||
}
|
}
|
||||||
return textsecure.storage.unprocessed.update(item.id, { attempts });
|
return textsecure.storage.unprocessed.update(item.id, { attempts });
|
||||||
})).then(() => items, (error) => {
|
})
|
||||||
|
).then(
|
||||||
|
() => items,
|
||||||
|
error => {
|
||||||
console.log(
|
console.log(
|
||||||
'getAllFromCache error updating items after load:',
|
'getAllFromCache error updating items after load:',
|
||||||
error && error.stack ? error.stack : error
|
error && error.stack ? error.stack : error
|
||||||
);
|
);
|
||||||
return items;
|
return items;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
addToCache(envelope, plaintext) {
|
addToCache(envelope, plaintext) {
|
||||||
|
@ -332,7 +347,7 @@ MessageReceiver.prototype.extend({
|
||||||
);
|
);
|
||||||
const promise = this.addToQueue(taskWithTimeout);
|
const promise = this.addToQueue(taskWithTimeout);
|
||||||
|
|
||||||
return promise.catch((error) => {
|
return promise.catch(error => {
|
||||||
console.log(
|
console.log(
|
||||||
'queueDecryptedEnvelope error handling envelope',
|
'queueDecryptedEnvelope error handling envelope',
|
||||||
id,
|
id,
|
||||||
|
@ -346,10 +361,13 @@ MessageReceiver.prototype.extend({
|
||||||
console.log('queueing envelope', id);
|
console.log('queueing envelope', id);
|
||||||
|
|
||||||
const task = this.handleEnvelope.bind(this, envelope);
|
const task = this.handleEnvelope.bind(this, envelope);
|
||||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task, `queueEnvelope ${id}`);
|
const taskWithTimeout = textsecure.createTaskWithTimeout(
|
||||||
|
task,
|
||||||
|
`queueEnvelope ${id}`
|
||||||
|
);
|
||||||
const promise = this.addToQueue(taskWithTimeout);
|
const promise = this.addToQueue(taskWithTimeout);
|
||||||
|
|
||||||
return promise.catch((error) => {
|
return promise.catch(error => {
|
||||||
console.log(
|
console.log(
|
||||||
'queueEnvelope error handling envelope',
|
'queueEnvelope error handling envelope',
|
||||||
id,
|
id,
|
||||||
|
@ -448,26 +466,36 @@ MessageReceiver.prototype.extend({
|
||||||
switch (envelope.type) {
|
switch (envelope.type) {
|
||||||
case textsecure.protobuf.Envelope.Type.CIPHERTEXT:
|
case textsecure.protobuf.Envelope.Type.CIPHERTEXT:
|
||||||
console.log('message from', this.getEnvelopeId(envelope));
|
console.log('message from', this.getEnvelopeId(envelope));
|
||||||
promise = sessionCipher.decryptWhisperMessage(ciphertext).then(this.unpad);
|
promise = sessionCipher
|
||||||
|
.decryptWhisperMessage(ciphertext)
|
||||||
|
.then(this.unpad);
|
||||||
break;
|
break;
|
||||||
case textsecure.protobuf.Envelope.Type.PREKEY_BUNDLE:
|
case textsecure.protobuf.Envelope.Type.PREKEY_BUNDLE:
|
||||||
console.log('prekey message from', this.getEnvelopeId(envelope));
|
console.log('prekey message from', this.getEnvelopeId(envelope));
|
||||||
promise = this.decryptPreKeyWhisperMessage(ciphertext, sessionCipher, address);
|
promise = this.decryptPreKeyWhisperMessage(
|
||||||
|
ciphertext,
|
||||||
|
sessionCipher,
|
||||||
|
address
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
promise = Promise.reject(new Error('Unknown message type'));
|
promise = Promise.reject(new Error('Unknown message type'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return promise.then(plaintext => this.updateCache(
|
return promise
|
||||||
envelope,
|
.then(plaintext =>
|
||||||
plaintext
|
this.updateCache(envelope, plaintext).then(
|
||||||
).then(() => plaintext, (error) => {
|
() => plaintext,
|
||||||
|
error => {
|
||||||
console.log(
|
console.log(
|
||||||
'decrypt failed to save decrypted message contents to cache:',
|
'decrypt failed to save decrypted message contents to cache:',
|
||||||
error && error.stack ? error.stack : error
|
error && error.stack ? error.stack : error
|
||||||
);
|
);
|
||||||
return plaintext;
|
return plaintext;
|
||||||
})).catch((error) => {
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
let errorToThrow = error;
|
let errorToThrow = error;
|
||||||
|
|
||||||
if (error.message === 'Unknown identity key') {
|
if (error.message === 'Unknown identity key') {
|
||||||
|
@ -508,17 +536,20 @@ MessageReceiver.prototype.extend({
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleSentMessage(envelope, destination, timestamp, msg, expirationStartTimestamp) {
|
handleSentMessage(
|
||||||
|
envelope,
|
||||||
|
destination,
|
||||||
|
timestamp,
|
||||||
|
msg,
|
||||||
|
expirationStartTimestamp
|
||||||
|
) {
|
||||||
let p = Promise.resolve();
|
let p = Promise.resolve();
|
||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
if (msg.flags & textsecure.protobuf.DataMessage.Flags.END_SESSION) {
|
if (msg.flags & textsecure.protobuf.DataMessage.Flags.END_SESSION) {
|
||||||
p = this.handleEndSession(destination);
|
p = this.handleEndSession(destination);
|
||||||
}
|
}
|
||||||
return p.then(() => this.processDecrypted(
|
return p.then(() =>
|
||||||
envelope,
|
this.processDecrypted(envelope, msg, this.number).then(message => {
|
||||||
msg,
|
|
||||||
this.number
|
|
||||||
).then((message) => {
|
|
||||||
const ev = new Event('sent');
|
const ev = new Event('sent');
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
ev.data = {
|
ev.data = {
|
||||||
|
@ -531,7 +562,8 @@ MessageReceiver.prototype.extend({
|
||||||
ev.data.expirationStartTimestamp = expirationStartTimestamp.toNumber();
|
ev.data.expirationStartTimestamp = expirationStartTimestamp.toNumber();
|
||||||
}
|
}
|
||||||
return this.dispatchAndWait(ev);
|
return this.dispatchAndWait(ev);
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
},
|
},
|
||||||
handleDataMessage(envelope, msg) {
|
handleDataMessage(envelope, msg) {
|
||||||
console.log('data message from', this.getEnvelopeId(envelope));
|
console.log('data message from', this.getEnvelopeId(envelope));
|
||||||
|
@ -540,11 +572,8 @@ MessageReceiver.prototype.extend({
|
||||||
if (msg.flags & textsecure.protobuf.DataMessage.Flags.END_SESSION) {
|
if (msg.flags & textsecure.protobuf.DataMessage.Flags.END_SESSION) {
|
||||||
p = this.handleEndSession(envelope.source);
|
p = this.handleEndSession(envelope.source);
|
||||||
}
|
}
|
||||||
return p.then(() => this.processDecrypted(
|
return p.then(() =>
|
||||||
envelope,
|
this.processDecrypted(envelope, msg, envelope.source).then(message => {
|
||||||
msg,
|
|
||||||
envelope.source
|
|
||||||
).then((message) => {
|
|
||||||
const ev = new Event('message');
|
const ev = new Event('message');
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
ev.data = {
|
ev.data = {
|
||||||
|
@ -555,23 +584,22 @@ MessageReceiver.prototype.extend({
|
||||||
message,
|
message,
|
||||||
};
|
};
|
||||||
return this.dispatchAndWait(ev);
|
return this.dispatchAndWait(ev);
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
},
|
},
|
||||||
handleLegacyMessage(envelope) {
|
handleLegacyMessage(envelope) {
|
||||||
return this.decrypt(
|
return this.decrypt(envelope, envelope.legacyMessage).then(plaintext =>
|
||||||
envelope,
|
this.innerHandleLegacyMessage(envelope, plaintext)
|
||||||
envelope.legacyMessage
|
);
|
||||||
).then(plaintext => this.innerHandleLegacyMessage(envelope, plaintext));
|
|
||||||
},
|
},
|
||||||
innerHandleLegacyMessage(envelope, plaintext) {
|
innerHandleLegacyMessage(envelope, plaintext) {
|
||||||
const message = textsecure.protobuf.DataMessage.decode(plaintext);
|
const message = textsecure.protobuf.DataMessage.decode(plaintext);
|
||||||
return this.handleDataMessage(envelope, message);
|
return this.handleDataMessage(envelope, message);
|
||||||
},
|
},
|
||||||
handleContentMessage(envelope) {
|
handleContentMessage(envelope) {
|
||||||
return this.decrypt(
|
return this.decrypt(envelope, envelope.content).then(plaintext =>
|
||||||
envelope,
|
this.innerHandleContentMessage(envelope, plaintext)
|
||||||
envelope.content
|
);
|
||||||
).then(plaintext => this.innerHandleContentMessage(envelope, plaintext));
|
|
||||||
},
|
},
|
||||||
innerHandleContentMessage(envelope, plaintext) {
|
innerHandleContentMessage(envelope, plaintext) {
|
||||||
const content = textsecure.protobuf.Content.decode(plaintext);
|
const content = textsecure.protobuf.Content.decode(plaintext);
|
||||||
|
@ -595,7 +623,9 @@ MessageReceiver.prototype.extend({
|
||||||
},
|
},
|
||||||
handleReceiptMessage(envelope, receiptMessage) {
|
handleReceiptMessage(envelope, receiptMessage) {
|
||||||
const results = [];
|
const results = [];
|
||||||
if (receiptMessage.type === textsecure.protobuf.ReceiptMessage.Type.DELIVERY) {
|
if (
|
||||||
|
receiptMessage.type === textsecure.protobuf.ReceiptMessage.Type.DELIVERY
|
||||||
|
) {
|
||||||
for (let i = 0; i < receiptMessage.timestamp.length; i += 1) {
|
for (let i = 0; i < receiptMessage.timestamp.length; i += 1) {
|
||||||
const ev = new Event('delivery');
|
const ev = new Event('delivery');
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
|
@ -606,7 +636,9 @@ MessageReceiver.prototype.extend({
|
||||||
};
|
};
|
||||||
results.push(this.dispatchAndWait(ev));
|
results.push(this.dispatchAndWait(ev));
|
||||||
}
|
}
|
||||||
} else if (receiptMessage.type === textsecure.protobuf.ReceiptMessage.Type.READ) {
|
} else if (
|
||||||
|
receiptMessage.type === textsecure.protobuf.ReceiptMessage.Type.READ
|
||||||
|
) {
|
||||||
for (let i = 0; i < receiptMessage.timestamp.length; i += 1) {
|
for (let i = 0; i < receiptMessage.timestamp.length; i += 1) {
|
||||||
const ev = new Event('read');
|
const ev = new Event('read');
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
|
@ -734,12 +766,13 @@ MessageReceiver.prototype.extend({
|
||||||
let groupDetails = groupBuffer.next();
|
let groupDetails = groupBuffer.next();
|
||||||
const promises = [];
|
const promises = [];
|
||||||
while (groupDetails !== undefined) {
|
while (groupDetails !== undefined) {
|
||||||
const getGroupDetails = (details) => {
|
const getGroupDetails = details => {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
details.id = details.id.toBinary();
|
details.id = details.id.toBinary();
|
||||||
if (details.active) {
|
if (details.active) {
|
||||||
return textsecure.storage.groups.getGroup(details.id)
|
return textsecure.storage.groups
|
||||||
.then((existingGroup) => {
|
.getGroup(details.id)
|
||||||
|
.then(existingGroup => {
|
||||||
if (existingGroup === undefined) {
|
if (existingGroup === undefined) {
|
||||||
return textsecure.storage.groups.createNewGroup(
|
return textsecure.storage.groups.createNewGroup(
|
||||||
details.members,
|
details.members,
|
||||||
|
@ -750,17 +783,20 @@ MessageReceiver.prototype.extend({
|
||||||
details.id,
|
details.id,
|
||||||
details.members
|
details.members
|
||||||
);
|
);
|
||||||
}).then(() => details);
|
})
|
||||||
|
.then(() => details);
|
||||||
}
|
}
|
||||||
return Promise.resolve(details);
|
return Promise.resolve(details);
|
||||||
};
|
};
|
||||||
|
|
||||||
const promise = getGroupDetails(groupDetails).then((details) => {
|
const promise = getGroupDetails(groupDetails)
|
||||||
|
.then(details => {
|
||||||
const ev = new Event('group');
|
const ev = new Event('group');
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
ev.groupDetails = details;
|
ev.groupDetails = details;
|
||||||
return this.dispatchAndWait(ev);
|
return this.dispatchAndWait(ev);
|
||||||
}).catch((e) => {
|
})
|
||||||
|
.catch(e => {
|
||||||
console.log('error processing group', e);
|
console.log('error processing group', e);
|
||||||
});
|
});
|
||||||
groupDetails = groupBuffer.next();
|
groupDetails = groupBuffer.next();
|
||||||
|
@ -803,7 +839,8 @@ MessageReceiver.prototype.extend({
|
||||||
attachment.data = data;
|
attachment.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.server.getAttachment(attachment.id)
|
return this.server
|
||||||
|
.getAttachment(attachment.id)
|
||||||
.then(decryptAttachment)
|
.then(decryptAttachment)
|
||||||
.then(updateAttachment);
|
.then(updateAttachment);
|
||||||
},
|
},
|
||||||
|
@ -825,8 +862,14 @@ MessageReceiver.prototype.extend({
|
||||||
|
|
||||||
// It's most likely that dataMessage will be populated, so we look at it in detail
|
// It's most likely that dataMessage will be populated, so we look at it in detail
|
||||||
const data = content.dataMessage;
|
const data = content.dataMessage;
|
||||||
if (data && !data.attachments.length && !data.body && !data.expireTimer &&
|
if (
|
||||||
!data.flags && !data.group) {
|
data &&
|
||||||
|
!data.attachments.length &&
|
||||||
|
!data.body &&
|
||||||
|
!data.expireTimer &&
|
||||||
|
!data.flags &&
|
||||||
|
!data.group
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -857,7 +900,7 @@ MessageReceiver.prototype.extend({
|
||||||
ciphertext,
|
ciphertext,
|
||||||
sessionCipher,
|
sessionCipher,
|
||||||
address
|
address
|
||||||
).then((plaintext) => {
|
).then(plaintext => {
|
||||||
const envelope = {
|
const envelope = {
|
||||||
source: number,
|
source: number,
|
||||||
sourceDevice: device,
|
sourceDevice: device,
|
||||||
|
@ -901,7 +944,8 @@ MessageReceiver.prototype.extend({
|
||||||
console.log('got end session');
|
console.log('got end session');
|
||||||
const deviceIds = await textsecure.storage.protocol.getDeviceIds(number);
|
const deviceIds = await textsecure.storage.protocol.getDeviceIds(number);
|
||||||
|
|
||||||
return Promise.all(deviceIds.map((deviceId) => {
|
return Promise.all(
|
||||||
|
deviceIds.map(deviceId => {
|
||||||
const address = new libsignal.SignalProtocolAddress(number, deviceId);
|
const address = new libsignal.SignalProtocolAddress(number, deviceId);
|
||||||
const sessionCipher = new libsignal.SessionCipher(
|
const sessionCipher = new libsignal.SessionCipher(
|
||||||
textsecure.storage.protocol,
|
textsecure.storage.protocol,
|
||||||
|
@ -910,7 +954,8 @@ MessageReceiver.prototype.extend({
|
||||||
|
|
||||||
console.log('deleting sessions for', address.toString());
|
console.log('deleting sessions for', address.toString());
|
||||||
return sessionCipher.deleteAllSessionsForDevice();
|
return sessionCipher.deleteAllSessionsForDevice();
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
},
|
},
|
||||||
processDecrypted(envelope, decrypted, source) {
|
processDecrypted(envelope, decrypted, source) {
|
||||||
/* eslint-disable no-bitwise, no-param-reassign */
|
/* eslint-disable no-bitwise, no-param-reassign */
|
||||||
|
@ -928,7 +973,6 @@ MessageReceiver.prototype.extend({
|
||||||
decrypted.expireTimer = 0;
|
decrypted.expireTimer = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (decrypted.flags & FLAGS.END_SESSION) {
|
if (decrypted.flags & FLAGS.END_SESSION) {
|
||||||
decrypted.body = null;
|
decrypted.body = null;
|
||||||
decrypted.attachments = [];
|
decrypted.attachments = [];
|
||||||
|
@ -949,7 +993,9 @@ MessageReceiver.prototype.extend({
|
||||||
if (decrypted.group !== null) {
|
if (decrypted.group !== null) {
|
||||||
decrypted.group.id = decrypted.group.id.toBinary();
|
decrypted.group.id = decrypted.group.id.toBinary();
|
||||||
|
|
||||||
if (decrypted.group.type === textsecure.protobuf.GroupContext.Type.UPDATE) {
|
if (
|
||||||
|
decrypted.group.type === textsecure.protobuf.GroupContext.Type.UPDATE
|
||||||
|
) {
|
||||||
if (decrypted.group.avatar !== null) {
|
if (decrypted.group.avatar !== null) {
|
||||||
promises.push(this.handleAttachment(decrypted.group.avatar));
|
promises.push(this.handleAttachment(decrypted.group.avatar));
|
||||||
}
|
}
|
||||||
|
@ -957,9 +1003,13 @@ MessageReceiver.prototype.extend({
|
||||||
|
|
||||||
const storageGroups = textsecure.storage.groups;
|
const storageGroups = textsecure.storage.groups;
|
||||||
|
|
||||||
promises.push(storageGroups.getNumbers(decrypted.group.id).then((existingGroup) => {
|
promises.push(
|
||||||
|
storageGroups.getNumbers(decrypted.group.id).then(existingGroup => {
|
||||||
if (existingGroup === undefined) {
|
if (existingGroup === undefined) {
|
||||||
if (decrypted.group.type !== textsecure.protobuf.GroupContext.Type.UPDATE) {
|
if (
|
||||||
|
decrypted.group.type !==
|
||||||
|
textsecure.protobuf.GroupContext.Type.UPDATE
|
||||||
|
) {
|
||||||
decrypted.group.members = [source];
|
decrypted.group.members = [source];
|
||||||
console.log('Got message for unknown group');
|
console.log('Got message for unknown group');
|
||||||
}
|
}
|
||||||
|
@ -972,7 +1022,9 @@ MessageReceiver.prototype.extend({
|
||||||
|
|
||||||
if (fromIndex < 0) {
|
if (fromIndex < 0) {
|
||||||
// TODO: This could be indication of a race...
|
// TODO: This could be indication of a race...
|
||||||
console.log('Sender was not a member of the group they were sending from');
|
console.log(
|
||||||
|
'Sender was not a member of the group they were sending from'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (decrypted.group.type) {
|
switch (decrypted.group.type) {
|
||||||
|
@ -987,9 +1039,14 @@ MessageReceiver.prototype.extend({
|
||||||
decrypted.body = null;
|
decrypted.body = null;
|
||||||
decrypted.attachments = [];
|
decrypted.attachments = [];
|
||||||
if (source === this.number) {
|
if (source === this.number) {
|
||||||
return textsecure.storage.groups.deleteGroup(decrypted.group.id);
|
return textsecure.storage.groups.deleteGroup(
|
||||||
|
decrypted.group.id
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return textsecure.storage.groups.removeNumber(decrypted.group.id, source);
|
return textsecure.storage.groups.removeNumber(
|
||||||
|
decrypted.group.id,
|
||||||
|
source
|
||||||
|
);
|
||||||
case textsecure.protobuf.GroupContext.Type.DELIVER:
|
case textsecure.protobuf.GroupContext.Type.DELIVER:
|
||||||
decrypted.group.name = null;
|
decrypted.group.name = null;
|
||||||
decrypted.group.members = [];
|
decrypted.group.members = [];
|
||||||
|
@ -999,7 +1056,8 @@ MessageReceiver.prototype.extend({
|
||||||
this.removeFromCache(envelope);
|
this.removeFromCache(envelope);
|
||||||
throw new Error('Unknown group message type');
|
throw new Error('Unknown group message type');
|
||||||
}
|
}
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0, max = decrypted.attachments.length; i < max; i += 1) {
|
for (let i = 0, max = decrypted.attachments.length; i < max; i += 1) {
|
||||||
|
@ -1021,12 +1079,14 @@ MessageReceiver.prototype.extend({
|
||||||
if (thumbnail) {
|
if (thumbnail) {
|
||||||
// We don't want the failure of a thumbnail download to fail the handling of
|
// We don't want the failure of a thumbnail download to fail the handling of
|
||||||
// this message entirely, like we do for full attachments.
|
// this message entirely, like we do for full attachments.
|
||||||
promises.push(this.handleAttachment(thumbnail).catch((error) => {
|
promises.push(
|
||||||
|
this.handleAttachment(thumbnail).catch(error => {
|
||||||
console.log(
|
console.log(
|
||||||
'Problem loading thumbnail for quote',
|
'Problem loading thumbnail for quote',
|
||||||
error && error.stack ? error.stack : error
|
error && error.stack ? error.stack : error
|
||||||
);
|
);
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1052,8 +1112,12 @@ textsecure.MessageReceiver = function MessageReceiverWrapper(
|
||||||
signalingKey,
|
signalingKey,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
this.addEventListener = messageReceiver.addEventListener.bind(messageReceiver);
|
this.addEventListener = messageReceiver.addEventListener.bind(
|
||||||
this.removeEventListener = messageReceiver.removeEventListener.bind(messageReceiver);
|
messageReceiver
|
||||||
|
);
|
||||||
|
this.removeEventListener = messageReceiver.removeEventListener.bind(
|
||||||
|
messageReceiver
|
||||||
|
);
|
||||||
this.getStatus = messageReceiver.getStatus.bind(messageReceiver);
|
this.getStatus = messageReceiver.getStatus.bind(messageReceiver);
|
||||||
this.close = messageReceiver.close.bind(messageReceiver);
|
this.close = messageReceiver.close.bind(messageReceiver);
|
||||||
messageReceiver.connect();
|
messageReceiver.connect();
|
||||||
|
@ -1067,4 +1131,3 @@ textsecure.MessageReceiver = function MessageReceiverWrapper(
|
||||||
textsecure.MessageReceiver.prototype = {
|
textsecure.MessageReceiver.prototype = {
|
||||||
constructor: textsecure.MessageReceiver,
|
constructor: textsecure.MessageReceiver,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
function OutgoingMessage(server, timestamp, numbers, message, silent, callback) {
|
function OutgoingMessage(
|
||||||
|
server,
|
||||||
|
timestamp,
|
||||||
|
numbers,
|
||||||
|
message,
|
||||||
|
silent,
|
||||||
|
callback
|
||||||
|
) {
|
||||||
if (message instanceof textsecure.protobuf.DataMessage) {
|
if (message instanceof textsecure.protobuf.DataMessage) {
|
||||||
var content = new textsecure.protobuf.Content();
|
var content = new textsecure.protobuf.Content();
|
||||||
content.dataMessage = message;
|
content.dataMessage = message;
|
||||||
|
@ -21,12 +28,20 @@ OutgoingMessage.prototype = {
|
||||||
numberCompleted: function() {
|
numberCompleted: function() {
|
||||||
this.numbersCompleted++;
|
this.numbersCompleted++;
|
||||||
if (this.numbersCompleted >= this.numbers.length) {
|
if (this.numbersCompleted >= this.numbers.length) {
|
||||||
this.callback({successfulNumbers: this.successfulNumbers, errors: this.errors});
|
this.callback({
|
||||||
|
successfulNumbers: this.successfulNumbers,
|
||||||
|
errors: this.errors,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
registerError: function(number, reason, error) {
|
registerError: function(number, reason, error) {
|
||||||
if (!error || error.name === 'HTTPError' && error.code !== 404) {
|
if (!error || (error.name === 'HTTPError' && error.code !== 404)) {
|
||||||
error = new textsecure.OutgoingMessageError(number, this.message.toArrayBuffer(), this.timestamp, error);
|
error = new textsecure.OutgoingMessageError(
|
||||||
|
number,
|
||||||
|
this.message.toArrayBuffer(),
|
||||||
|
this.timestamp,
|
||||||
|
error
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
error.number = number;
|
error.number = number;
|
||||||
|
@ -36,44 +51,71 @@ OutgoingMessage.prototype = {
|
||||||
},
|
},
|
||||||
reloadDevicesAndSend: function(number, recurse) {
|
reloadDevicesAndSend: function(number, recurse) {
|
||||||
return function() {
|
return function() {
|
||||||
return textsecure.storage.protocol.getDeviceIds(number).then(function(deviceIds) {
|
return textsecure.storage.protocol.getDeviceIds(number).then(
|
||||||
|
function(deviceIds) {
|
||||||
if (deviceIds.length == 0) {
|
if (deviceIds.length == 0) {
|
||||||
return this.registerError(number, "Got empty device list when loading device keys", null);
|
return this.registerError(
|
||||||
|
number,
|
||||||
|
'Got empty device list when loading device keys',
|
||||||
|
null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return this.doSendMessage(number, deviceIds, recurse);
|
return this.doSendMessage(number, deviceIds, recurse);
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
},
|
},
|
||||||
|
|
||||||
getKeysForNumber: function(number, updateDevices) {
|
getKeysForNumber: function(number, updateDevices) {
|
||||||
var handleResult = function(response) {
|
var handleResult = function(response) {
|
||||||
return Promise.all(response.devices.map(function(device) {
|
return Promise.all(
|
||||||
|
response.devices.map(
|
||||||
|
function(device) {
|
||||||
device.identityKey = response.identityKey;
|
device.identityKey = response.identityKey;
|
||||||
if (updateDevices === undefined || updateDevices.indexOf(device.deviceId) > -1) {
|
if (
|
||||||
var address = new libsignal.SignalProtocolAddress(number, device.deviceId);
|
updateDevices === undefined ||
|
||||||
var builder = new libsignal.SessionBuilder(textsecure.storage.protocol, address);
|
updateDevices.indexOf(device.deviceId) > -1
|
||||||
|
) {
|
||||||
|
var address = new libsignal.SignalProtocolAddress(
|
||||||
|
number,
|
||||||
|
device.deviceId
|
||||||
|
);
|
||||||
|
var builder = new libsignal.SessionBuilder(
|
||||||
|
textsecure.storage.protocol,
|
||||||
|
address
|
||||||
|
);
|
||||||
if (device.registrationId === 0) {
|
if (device.registrationId === 0) {
|
||||||
console.log("device registrationId 0!");
|
console.log('device registrationId 0!');
|
||||||
}
|
}
|
||||||
return builder.processPreKey(device).catch(function(error) {
|
return builder.processPreKey(device).catch(
|
||||||
if (error.message === "Identity key changed") {
|
function(error) {
|
||||||
|
if (error.message === 'Identity key changed') {
|
||||||
error.timestamp = this.timestamp;
|
error.timestamp = this.timestamp;
|
||||||
error.originalMessage = this.message.toArrayBuffer();
|
error.originalMessage = this.message.toArrayBuffer();
|
||||||
error.identityKey = device.identityKey;
|
error.identityKey = device.identityKey;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}.bind(this)));
|
}.bind(this)
|
||||||
|
)
|
||||||
|
);
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
if (updateDevices === undefined) {
|
if (updateDevices === undefined) {
|
||||||
return this.server.getKeysForNumber(number).then(handleResult);
|
return this.server.getKeysForNumber(number).then(handleResult);
|
||||||
} else {
|
} else {
|
||||||
var promise = Promise.resolve();
|
var promise = Promise.resolve();
|
||||||
updateDevices.forEach(function(device) {
|
updateDevices.forEach(
|
||||||
promise = promise.then(function() {
|
function(device) {
|
||||||
return this.server.getKeysForNumber(number, device).then(handleResult).catch(function(e) {
|
promise = promise.then(
|
||||||
|
function() {
|
||||||
|
return this.server
|
||||||
|
.getKeysForNumber(number, device)
|
||||||
|
.then(handleResult)
|
||||||
|
.catch(
|
||||||
|
function(e) {
|
||||||
if (e.name === 'HTTPError' && e.code === 404) {
|
if (e.name === 'HTTPError' && e.code === 404) {
|
||||||
if (device !== 1) {
|
if (device !== 1) {
|
||||||
return this.removeDeviceIdsForNumber(number, [device]);
|
return this.removeDeviceIdsForNumber(number, [device]);
|
||||||
|
@ -83,16 +125,21 @@ OutgoingMessage.prototype = {
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
}.bind(this));
|
);
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
transmitMessage: function(number, jsonData, timestamp) {
|
transmitMessage: function(number, jsonData, timestamp) {
|
||||||
return this.server.sendMessages(number, jsonData, timestamp, this.silent).catch(function(e) {
|
return this.server
|
||||||
|
.sendMessages(number, jsonData, timestamp, this.silent)
|
||||||
|
.catch(function(e) {
|
||||||
if (e.name === 'HTTPError' && (e.code !== 409 && e.code !== 410)) {
|
if (e.name === 'HTTPError' && (e.code !== 409 && e.code !== 410)) {
|
||||||
// 409 and 410 should bubble and be handled by doSendMessage
|
// 409 and 410 should bubble and be handled by doSendMessage
|
||||||
// 404 should throw UnregisteredUserError
|
// 404 should throw UnregisteredUserError
|
||||||
|
@ -100,7 +147,12 @@ OutgoingMessage.prototype = {
|
||||||
if (e.code === 404) {
|
if (e.code === 404) {
|
||||||
throw new textsecure.UnregisteredUserError(number, e);
|
throw new textsecure.UnregisteredUserError(number, e);
|
||||||
}
|
}
|
||||||
throw new textsecure.SendMessageNetworkError(number, jsonData, e, timestamp);
|
throw new textsecure.SendMessageNetworkError(
|
||||||
|
number,
|
||||||
|
jsonData,
|
||||||
|
e,
|
||||||
|
timestamp
|
||||||
|
);
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
|
@ -133,7 +185,9 @@ OutgoingMessage.prototype = {
|
||||||
var ciphers = {};
|
var ciphers = {};
|
||||||
var plaintext = this.getPlaintext();
|
var plaintext = this.getPlaintext();
|
||||||
|
|
||||||
return Promise.all(deviceIds.map(function(deviceId) {
|
return Promise.all(
|
||||||
|
deviceIds.map(
|
||||||
|
function(deviceId) {
|
||||||
var address = new libsignal.SignalProtocolAddress(number, deviceId);
|
var address = new libsignal.SignalProtocolAddress(number, deviceId);
|
||||||
|
|
||||||
var ourNumber = textsecure.storage.user.getNumber();
|
var ourNumber = textsecure.storage.user.getNumber();
|
||||||
|
@ -144,66 +198,114 @@ OutgoingMessage.prototype = {
|
||||||
options.messageKeysLimit = false;
|
options.messageKeysLimit = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sessionCipher = new libsignal.SessionCipher(textsecure.storage.protocol, address, options);
|
var sessionCipher = new libsignal.SessionCipher(
|
||||||
|
textsecure.storage.protocol,
|
||||||
|
address,
|
||||||
|
options
|
||||||
|
);
|
||||||
ciphers[address.getDeviceId()] = sessionCipher;
|
ciphers[address.getDeviceId()] = sessionCipher;
|
||||||
return sessionCipher.encrypt(plaintext).then(function(ciphertext) {
|
return sessionCipher.encrypt(plaintext).then(function(ciphertext) {
|
||||||
return {
|
return {
|
||||||
type: ciphertext.type,
|
type: ciphertext.type,
|
||||||
destinationDeviceId: address.getDeviceId(),
|
destinationDeviceId: address.getDeviceId(),
|
||||||
destinationRegistrationId: ciphertext.registrationId,
|
destinationRegistrationId: ciphertext.registrationId,
|
||||||
content : btoa(ciphertext.body)
|
content: btoa(ciphertext.body),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}.bind(this))).then(function(jsonData) {
|
}.bind(this)
|
||||||
return this.transmitMessage(number, jsonData, this.timestamp).then(function() {
|
)
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
function(jsonData) {
|
||||||
|
return this.transmitMessage(number, jsonData, this.timestamp).then(
|
||||||
|
function() {
|
||||||
this.successfulNumbers[this.successfulNumbers.length] = number;
|
this.successfulNumbers[this.successfulNumbers.length] = number;
|
||||||
this.numberCompleted();
|
this.numberCompleted();
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
}.bind(this)).catch(function(error) {
|
);
|
||||||
if (error instanceof Error && error.name == "HTTPError" && (error.code == 410 || error.code == 409)) {
|
}.bind(this)
|
||||||
|
)
|
||||||
|
.catch(
|
||||||
|
function(error) {
|
||||||
|
if (
|
||||||
|
error instanceof Error &&
|
||||||
|
error.name == 'HTTPError' &&
|
||||||
|
(error.code == 410 || error.code == 409)
|
||||||
|
) {
|
||||||
if (!recurse)
|
if (!recurse)
|
||||||
return this.registerError(number, "Hit retry limit attempting to reload device list", error);
|
return this.registerError(
|
||||||
|
number,
|
||||||
|
'Hit retry limit attempting to reload device list',
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
var p;
|
var p;
|
||||||
if (error.code == 409) {
|
if (error.code == 409) {
|
||||||
p = this.removeDeviceIdsForNumber(number, error.response.extraDevices);
|
p = this.removeDeviceIdsForNumber(
|
||||||
|
number,
|
||||||
|
error.response.extraDevices
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
p = Promise.all(error.response.staleDevices.map(function(deviceId) {
|
p = Promise.all(
|
||||||
|
error.response.staleDevices.map(function(deviceId) {
|
||||||
return ciphers[deviceId].closeOpenSessionForDevice();
|
return ciphers[deviceId].closeOpenSessionForDevice();
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.then(function() {
|
return p.then(
|
||||||
var resetDevices = ((error.code == 410) ? error.response.staleDevices : error.response.missingDevices);
|
function() {
|
||||||
return this.getKeysForNumber(number, resetDevices)
|
var resetDevices =
|
||||||
.then(this.reloadDevicesAndSend(number, error.code == 409));
|
error.code == 410
|
||||||
}.bind(this));
|
? error.response.staleDevices
|
||||||
} else if (error.message === "Identity key changed") {
|
: error.response.missingDevices;
|
||||||
|
return this.getKeysForNumber(number, resetDevices).then(
|
||||||
|
this.reloadDevicesAndSend(number, error.code == 409)
|
||||||
|
);
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
} else if (error.message === 'Identity key changed') {
|
||||||
error.timestamp = this.timestamp;
|
error.timestamp = this.timestamp;
|
||||||
error.originalMessage = this.message.toArrayBuffer();
|
error.originalMessage = this.message.toArrayBuffer();
|
||||||
console.log('Got "key changed" error from encrypt - no identityKey for application layer', number, deviceIds)
|
console.log(
|
||||||
|
'Got "key changed" error from encrypt - no identityKey for application layer',
|
||||||
|
number,
|
||||||
|
deviceIds
|
||||||
|
);
|
||||||
throw error;
|
throw error;
|
||||||
} else {
|
} else {
|
||||||
this.registerError(number, "Failed to create or send message", error);
|
this.registerError(
|
||||||
|
number,
|
||||||
|
'Failed to create or send message',
|
||||||
|
error
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
getStaleDeviceIdsForNumber: function(number) {
|
getStaleDeviceIdsForNumber: function(number) {
|
||||||
return textsecure.storage.protocol.getDeviceIds(number).then(function(deviceIds) {
|
return textsecure.storage.protocol
|
||||||
|
.getDeviceIds(number)
|
||||||
|
.then(function(deviceIds) {
|
||||||
if (deviceIds.length === 0) {
|
if (deviceIds.length === 0) {
|
||||||
return [1];
|
return [1];
|
||||||
}
|
}
|
||||||
var updateDevices = [];
|
var updateDevices = [];
|
||||||
return Promise.all(deviceIds.map(function(deviceId) {
|
return Promise.all(
|
||||||
|
deviceIds.map(function(deviceId) {
|
||||||
var address = new libsignal.SignalProtocolAddress(number, deviceId);
|
var address = new libsignal.SignalProtocolAddress(number, deviceId);
|
||||||
var sessionCipher = new libsignal.SessionCipher(textsecure.storage.protocol, address);
|
var sessionCipher = new libsignal.SessionCipher(
|
||||||
|
textsecure.storage.protocol,
|
||||||
|
address
|
||||||
|
);
|
||||||
return sessionCipher.hasOpenSession().then(function(hasSession) {
|
return sessionCipher.hasOpenSession().then(function(hasSession) {
|
||||||
if (!hasSession) {
|
if (!hasSession) {
|
||||||
updateDevices.push(deviceId);
|
updateDevices.push(deviceId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})).then(function() {
|
})
|
||||||
|
).then(function() {
|
||||||
return updateDevices;
|
return updateDevices;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -213,7 +315,7 @@ OutgoingMessage.prototype = {
|
||||||
var promise = Promise.resolve();
|
var promise = Promise.resolve();
|
||||||
for (var j in deviceIdsToRemove) {
|
for (var j in deviceIdsToRemove) {
|
||||||
promise = promise.then(function() {
|
promise = promise.then(function() {
|
||||||
var encodedNumber = number + "." + deviceIdsToRemove[j];
|
var encodedNumber = number + '.' + deviceIdsToRemove[j];
|
||||||
return textsecure.storage.protocol.removeSession(encodedNumber);
|
return textsecure.storage.protocol.removeSession(encodedNumber);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -221,21 +323,30 @@ OutgoingMessage.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
sendToNumber: function(number) {
|
sendToNumber: function(number) {
|
||||||
return this.getStaleDeviceIdsForNumber(number).then(function(updateDevices) {
|
return this.getStaleDeviceIdsForNumber(number).then(
|
||||||
|
function(updateDevices) {
|
||||||
return this.getKeysForNumber(number, updateDevices)
|
return this.getKeysForNumber(number, updateDevices)
|
||||||
.then(this.reloadDevicesAndSend(number, true))
|
.then(this.reloadDevicesAndSend(number, true))
|
||||||
.catch(function(error) {
|
.catch(
|
||||||
if (error.message === "Identity key changed") {
|
function(error) {
|
||||||
|
if (error.message === 'Identity key changed') {
|
||||||
error = new textsecure.OutgoingIdentityKeyError(
|
error = new textsecure.OutgoingIdentityKeyError(
|
||||||
number, error.originalMessage, error.timestamp, error.identityKey
|
number,
|
||||||
|
error.originalMessage,
|
||||||
|
error.timestamp,
|
||||||
|
error.identityKey
|
||||||
);
|
);
|
||||||
this.registerError(number, "Identity key changed", error);
|
this.registerError(number, 'Identity key changed', error);
|
||||||
} else {
|
} else {
|
||||||
this.registerError(
|
this.registerError(
|
||||||
number, "Failed to retrieve new device keys for number " + number, error
|
number,
|
||||||
|
'Failed to retrieve new device keys for number ' + number,
|
||||||
|
error
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
}.bind(this));
|
);
|
||||||
}
|
}.bind(this)
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,27 +1,40 @@
|
||||||
;(function() {
|
(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
window.textsecure = window.textsecure || {};
|
window.textsecure = window.textsecure || {};
|
||||||
window.textsecure.protobuf = {};
|
window.textsecure.protobuf = {};
|
||||||
|
|
||||||
function loadProtoBufs(filename) {
|
function loadProtoBufs(filename) {
|
||||||
return dcodeIO.ProtoBuf.loadProtoFile({root: window.PROTO_ROOT, file: filename}, function(error, result) {
|
return dcodeIO.ProtoBuf.loadProtoFile(
|
||||||
|
{ root: window.PROTO_ROOT, file: filename },
|
||||||
|
function(error, result) {
|
||||||
if (error) {
|
if (error) {
|
||||||
var text = 'Error loading protos from ' + filename + ' (root: ' + window.PROTO_ROOT + ') '
|
var text =
|
||||||
+ (error && error.stack ? error.stack : error);
|
'Error loading protos from ' +
|
||||||
|
filename +
|
||||||
|
' (root: ' +
|
||||||
|
window.PROTO_ROOT +
|
||||||
|
') ' +
|
||||||
|
(error && error.stack ? error.stack : error);
|
||||||
console.log(text);
|
console.log(text);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
var protos = result.build('signalservice');
|
var protos = result.build('signalservice');
|
||||||
if (!protos) {
|
if (!protos) {
|
||||||
var text = 'Error loading protos from ' + filename + ' (root: ' + window.PROTO_ROOT + ')';
|
var text =
|
||||||
|
'Error loading protos from ' +
|
||||||
|
filename +
|
||||||
|
' (root: ' +
|
||||||
|
window.PROTO_ROOT +
|
||||||
|
')';
|
||||||
console.log(text);
|
console.log(text);
|
||||||
throw new Error(text);
|
throw new Error(text);
|
||||||
}
|
}
|
||||||
for (var protoName in protos) {
|
for (var protoName in protos) {
|
||||||
textsecure.protobuf[protoName] = protos[protoName];
|
textsecure.protobuf[protoName] = protos[protoName];
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
};
|
);
|
||||||
|
}
|
||||||
|
|
||||||
loadProtoBufs('SignalService.proto');
|
loadProtoBufs('SignalService.proto');
|
||||||
loadProtoBufs('SubProtocol.proto');
|
loadProtoBufs('SubProtocol.proto');
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
;(function() {
|
(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
window.textsecure = window.textsecure || {};
|
window.textsecure = window.textsecure || {};
|
||||||
window.textsecure.storage = window.textsecure.storage || {};
|
window.textsecure.storage = window.textsecure.storage || {};
|
||||||
|
|
|
@ -51,17 +51,25 @@ function Message(options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.isEndSession()) {
|
if (this.isEndSession()) {
|
||||||
if (this.body !== null || this.group !== null || this.attachments.length !== 0) {
|
if (
|
||||||
|
this.body !== null ||
|
||||||
|
this.group !== null ||
|
||||||
|
this.attachments.length !== 0
|
||||||
|
) {
|
||||||
throw new Error('Invalid end session message');
|
throw new Error('Invalid end session message');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ( (typeof this.timestamp !== 'number') ||
|
if (
|
||||||
(this.body && typeof this.body !== 'string') ) {
|
typeof this.timestamp !== 'number' ||
|
||||||
|
(this.body && typeof this.body !== 'string')
|
||||||
|
) {
|
||||||
throw new Error('Invalid message body');
|
throw new Error('Invalid message body');
|
||||||
}
|
}
|
||||||
if (this.group) {
|
if (this.group) {
|
||||||
if ( (typeof this.group.id !== 'string') ||
|
if (
|
||||||
(typeof this.group.type !== 'number') ) {
|
typeof this.group.id !== 'string' ||
|
||||||
|
typeof this.group.type !== 'number'
|
||||||
|
) {
|
||||||
throw new Error('Invalid group context');
|
throw new Error('Invalid group context');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,7 +79,7 @@ function Message(options) {
|
||||||
Message.prototype = {
|
Message.prototype = {
|
||||||
constructor: Message,
|
constructor: Message,
|
||||||
isEndSession: function() {
|
isEndSession: function() {
|
||||||
return (this.flags & textsecure.protobuf.DataMessage.Flags.END_SESSION);
|
return this.flags & textsecure.protobuf.DataMessage.Flags.END_SESSION;
|
||||||
},
|
},
|
||||||
toProto: function() {
|
toProto: function() {
|
||||||
if (this.dataMessage instanceof textsecure.protobuf.DataMessage) {
|
if (this.dataMessage instanceof textsecure.protobuf.DataMessage) {
|
||||||
|
@ -88,10 +96,11 @@ Message.prototype = {
|
||||||
if (this.group) {
|
if (this.group) {
|
||||||
proto.group = new textsecure.protobuf.GroupContext();
|
proto.group = new textsecure.protobuf.GroupContext();
|
||||||
proto.group.id = stringToArrayBuffer(this.group.id);
|
proto.group.id = stringToArrayBuffer(this.group.id);
|
||||||
proto.group.type = this.group.type
|
proto.group.type = this.group.type;
|
||||||
}
|
}
|
||||||
if (this.quote) {
|
if (this.quote) {
|
||||||
var QuotedAttachment = textsecure.protobuf.DataMessage.Quote.QuotedAttachment;
|
var QuotedAttachment =
|
||||||
|
textsecure.protobuf.DataMessage.Quote.QuotedAttachment;
|
||||||
var Quote = textsecure.protobuf.DataMessage.Quote;
|
var Quote = textsecure.protobuf.DataMessage.Quote;
|
||||||
|
|
||||||
proto.quote = new Quote();
|
proto.quote = new Quote();
|
||||||
|
@ -100,7 +109,9 @@ Message.prototype = {
|
||||||
quote.id = this.quote.id;
|
quote.id = this.quote.id;
|
||||||
quote.author = this.quote.author;
|
quote.author = this.quote.author;
|
||||||
quote.text = this.quote.text;
|
quote.text = this.quote.text;
|
||||||
quote.attachments = (this.quote.attachments || []).map(function(attachment) {
|
quote.attachments = (this.quote.attachments || []).map(function(
|
||||||
|
attachment
|
||||||
|
) {
|
||||||
var quotedAttachment = new QuotedAttachment();
|
var quotedAttachment = new QuotedAttachment();
|
||||||
|
|
||||||
quotedAttachment.contentType = attachment.contentType;
|
quotedAttachment.contentType = attachment.contentType;
|
||||||
|
@ -125,7 +136,7 @@ Message.prototype = {
|
||||||
},
|
},
|
||||||
toArrayBuffer: function() {
|
toArrayBuffer: function() {
|
||||||
return this.toProto().toArrayBuffer();
|
return this.toProto().toArrayBuffer();
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function MessageSender(url, username, password, cdn_url) {
|
function MessageSender(url, username, password, cdn_url) {
|
||||||
|
@ -142,20 +153,29 @@ MessageSender.prototype = {
|
||||||
return Promise.resolve(undefined);
|
return Promise.resolve(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(attachment.data instanceof ArrayBuffer) &&
|
if (
|
||||||
!ArrayBuffer.isView(attachment.data)) {
|
!(attachment.data instanceof ArrayBuffer) &&
|
||||||
return Promise.reject(new TypeError(
|
!ArrayBuffer.isView(attachment.data)
|
||||||
|
) {
|
||||||
|
return Promise.reject(
|
||||||
|
new TypeError(
|
||||||
'`attachment.data` must be an `ArrayBuffer` or `ArrayBufferView`; got: ' +
|
'`attachment.data` must be an `ArrayBuffer` or `ArrayBufferView`; got: ' +
|
||||||
typeof attachment.data
|
typeof attachment.data
|
||||||
));
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var proto = new textsecure.protobuf.AttachmentPointer();
|
var proto = new textsecure.protobuf.AttachmentPointer();
|
||||||
proto.key = libsignal.crypto.getRandomBytes(64);
|
proto.key = libsignal.crypto.getRandomBytes(64);
|
||||||
|
|
||||||
var iv = libsignal.crypto.getRandomBytes(16);
|
var iv = libsignal.crypto.getRandomBytes(16);
|
||||||
return textsecure.crypto.encryptAttachment(attachment.data, proto.key, iv).then(function(result) {
|
return textsecure.crypto
|
||||||
return this.server.putAttachment(result.ciphertext).then(function(id) {
|
.encryptAttachment(attachment.data, proto.key, iv)
|
||||||
|
.then(
|
||||||
|
function(result) {
|
||||||
|
return this.server
|
||||||
|
.putAttachment(result.ciphertext)
|
||||||
|
.then(function(id) {
|
||||||
proto.id = id;
|
proto.id = id;
|
||||||
proto.contentType = attachment.contentType;
|
proto.contentType = attachment.contentType;
|
||||||
proto.digest = result.digest;
|
proto.digest = result.digest;
|
||||||
|
@ -170,7 +190,8 @@ MessageSender.prototype = {
|
||||||
}
|
}
|
||||||
return proto;
|
return proto;
|
||||||
});
|
});
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
retransmitMessage: function(number, jsonData, timestamp) {
|
retransmitMessage: function(number, jsonData, timestamp) {
|
||||||
|
@ -191,7 +212,14 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
// It's most likely that dataMessage will be populated, so we look at it in detail
|
// It's most likely that dataMessage will be populated, so we look at it in detail
|
||||||
var data = content.dataMessage;
|
var data = content.dataMessage;
|
||||||
if (data && !data.attachments.length && !data.body && !data.expireTimer && !data.flags && !data.group) {
|
if (
|
||||||
|
data &&
|
||||||
|
!data.attachments.length &&
|
||||||
|
!data.body &&
|
||||||
|
!data.expireTimer &&
|
||||||
|
!data.flags &&
|
||||||
|
!data.group
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,23 +259,33 @@ MessageSender.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
queueJobForNumber: function(number, runJob) {
|
queueJobForNumber: function(number, runJob) {
|
||||||
var taskWithTimeout = textsecure.createTaskWithTimeout(runJob, 'queueJobForNumber ' + number);
|
var taskWithTimeout = textsecure.createTaskWithTimeout(
|
||||||
|
runJob,
|
||||||
|
'queueJobForNumber ' + number
|
||||||
|
);
|
||||||
|
|
||||||
var runPrevious = this.pendingMessages[number] || Promise.resolve();
|
var runPrevious = this.pendingMessages[number] || Promise.resolve();
|
||||||
var runCurrent = this.pendingMessages[number] = runPrevious.then(taskWithTimeout, taskWithTimeout);
|
var runCurrent = (this.pendingMessages[number] = runPrevious.then(
|
||||||
runCurrent.then(function() {
|
taskWithTimeout,
|
||||||
|
taskWithTimeout
|
||||||
|
));
|
||||||
|
runCurrent.then(
|
||||||
|
function() {
|
||||||
if (this.pendingMessages[number] === runCurrent) {
|
if (this.pendingMessages[number] === runCurrent) {
|
||||||
delete this.pendingMessages[number];
|
delete this.pendingMessages[number];
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
uploadAttachments: function(message) {
|
uploadAttachments: function(message) {
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
message.attachments.map(this.makeAttachmentPointer.bind(this))
|
message.attachments.map(this.makeAttachmentPointer.bind(this))
|
||||||
).then(function(attachmentPointers) {
|
)
|
||||||
|
.then(function(attachmentPointers) {
|
||||||
message.attachmentPointers = attachmentPointers;
|
message.attachmentPointers = attachmentPointers;
|
||||||
}).catch(function(error) {
|
})
|
||||||
|
.catch(function(error) {
|
||||||
if (error instanceof Error && error.name === 'HTTPError') {
|
if (error instanceof Error && error.name === 'HTTPError') {
|
||||||
throw new textsecure.MessageError(message, error);
|
throw new textsecure.MessageError(message, error);
|
||||||
} else {
|
} else {
|
||||||
|
@ -264,7 +302,8 @@ MessageSender.prototype = {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(quote.attachments.map(function(attachment) {
|
return Promise.all(
|
||||||
|
quote.attachments.map(function(attachment) {
|
||||||
const thumbnail = attachment.thumbnail;
|
const thumbnail = attachment.thumbnail;
|
||||||
if (!thumbnail) {
|
if (!thumbnail) {
|
||||||
return;
|
return;
|
||||||
|
@ -273,7 +312,8 @@ MessageSender.prototype = {
|
||||||
return makePointer(thumbnail).then(function(pointer) {
|
return makePointer(thumbnail).then(function(pointer) {
|
||||||
attachment.attachmentPointer = pointer;
|
attachment.attachmentPointer = pointer;
|
||||||
});
|
});
|
||||||
})).catch(function(error) {
|
})
|
||||||
|
).catch(function(error) {
|
||||||
if (error instanceof Error && error.name === 'HTTPError') {
|
if (error instanceof Error && error.name === 'HTTPError') {
|
||||||
throw new textsecure.MessageError(message, error);
|
throw new textsecure.MessageError(message, error);
|
||||||
} else {
|
} else {
|
||||||
|
@ -287,8 +327,10 @@ MessageSender.prototype = {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
this.uploadAttachments(message),
|
this.uploadAttachments(message),
|
||||||
this.uploadThumbnails(message),
|
this.uploadThumbnails(message),
|
||||||
]).then(function() {
|
]).then(
|
||||||
return new Promise(function(resolve, reject) {
|
function() {
|
||||||
|
return new Promise(
|
||||||
|
function(resolve, reject) {
|
||||||
this.sendMessageProto(
|
this.sendMessageProto(
|
||||||
message.timestamp,
|
message.timestamp,
|
||||||
message.recipients,
|
message.recipients,
|
||||||
|
@ -302,27 +344,43 @@ MessageSender.prototype = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
}.bind(this));
|
);
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
sendMessageProto: function(timestamp, numbers, message, callback, silent) {
|
sendMessageProto: function(timestamp, numbers, message, callback, silent) {
|
||||||
var rejections = textsecure.storage.get('signedKeyRotationRejected', 0);
|
var rejections = textsecure.storage.get('signedKeyRotationRejected', 0);
|
||||||
if (rejections > 5) {
|
if (rejections > 5) {
|
||||||
throw new textsecure.SignedPreKeyRotationError(numbers, message.toArrayBuffer(), timestamp);
|
throw new textsecure.SignedPreKeyRotationError(
|
||||||
|
numbers,
|
||||||
|
message.toArrayBuffer(),
|
||||||
|
timestamp
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var outgoing = new OutgoingMessage(this.server, timestamp, numbers, message, silent, callback);
|
var outgoing = new OutgoingMessage(
|
||||||
|
this.server,
|
||||||
|
timestamp,
|
||||||
|
numbers,
|
||||||
|
message,
|
||||||
|
silent,
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
|
||||||
numbers.forEach(function(number) {
|
numbers.forEach(
|
||||||
|
function(number) {
|
||||||
this.queueJobForNumber(number, function() {
|
this.queueJobForNumber(number, function() {
|
||||||
return outgoing.sendToNumber(number);
|
return outgoing.sendToNumber(number);
|
||||||
});
|
});
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
retrySendMessageProto: function(numbers, encodedMessage, timestamp) {
|
retrySendMessageProto: function(numbers, encodedMessage, timestamp) {
|
||||||
var proto = textsecure.protobuf.DataMessage.decode(encodedMessage);
|
var proto = textsecure.protobuf.DataMessage.decode(encodedMessage);
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(
|
||||||
|
function(resolve, reject) {
|
||||||
this.sendMessageProto(timestamp, numbers, proto, function(res) {
|
this.sendMessageProto(timestamp, numbers, proto, function(res) {
|
||||||
if (res.errors.length > 0) {
|
if (res.errors.length > 0) {
|
||||||
reject(res);
|
reject(res);
|
||||||
|
@ -330,11 +388,13 @@ MessageSender.prototype = {
|
||||||
resolve(res);
|
resolve(res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
sendIndividualProto: function(number, proto, timestamp, silent) {
|
sendIndividualProto: function(number, proto, timestamp, silent) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(
|
||||||
|
function(resolve, reject) {
|
||||||
var callback = function(res) {
|
var callback = function(res) {
|
||||||
if (res.errors.length > 0) {
|
if (res.errors.length > 0) {
|
||||||
reject(res);
|
reject(res);
|
||||||
|
@ -343,7 +403,8 @@ MessageSender.prototype = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.sendMessageProto(timestamp, [number], proto, callback, silent);
|
this.sendMessageProto(timestamp, [number], proto, callback, silent);
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
createSyncMessage: function() {
|
createSyncMessage: function() {
|
||||||
|
@ -359,14 +420,21 @@ MessageSender.prototype = {
|
||||||
return syncMessage;
|
return syncMessage;
|
||||||
},
|
},
|
||||||
|
|
||||||
sendSyncMessage: function(encodedDataMessage, timestamp, destination, expirationStartTimestamp) {
|
sendSyncMessage: function(
|
||||||
|
encodedDataMessage,
|
||||||
|
timestamp,
|
||||||
|
destination,
|
||||||
|
expirationStartTimestamp
|
||||||
|
) {
|
||||||
var myNumber = textsecure.storage.user.getNumber();
|
var myNumber = textsecure.storage.user.getNumber();
|
||||||
var myDevice = textsecure.storage.user.getDeviceId();
|
var myDevice = textsecure.storage.user.getDeviceId();
|
||||||
if (myDevice == 1) {
|
if (myDevice == 1) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
var dataMessage = textsecure.protobuf.DataMessage.decode(encodedDataMessage);
|
var dataMessage = textsecure.protobuf.DataMessage.decode(
|
||||||
|
encodedDataMessage
|
||||||
|
);
|
||||||
var sentMessage = new textsecure.protobuf.SyncMessage.Sent();
|
var sentMessage = new textsecure.protobuf.SyncMessage.Sent();
|
||||||
sentMessage.timestamp = timestamp;
|
sentMessage.timestamp = timestamp;
|
||||||
sentMessage.message = dataMessage;
|
sentMessage.message = dataMessage;
|
||||||
|
@ -382,7 +450,12 @@ MessageSender.prototype = {
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
var silent = true;
|
var silent = true;
|
||||||
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
|
return this.sendIndividualProto(
|
||||||
|
myNumber,
|
||||||
|
contentMessage,
|
||||||
|
Date.now(),
|
||||||
|
silent
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
getProfile: function(number) {
|
getProfile: function(number) {
|
||||||
|
@ -404,7 +477,12 @@ MessageSender.prototype = {
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
var silent = true;
|
var silent = true;
|
||||||
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
|
return this.sendIndividualProto(
|
||||||
|
myNumber,
|
||||||
|
contentMessage,
|
||||||
|
Date.now(),
|
||||||
|
silent
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
@ -421,7 +499,12 @@ MessageSender.prototype = {
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
var silent = true;
|
var silent = true;
|
||||||
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
|
return this.sendIndividualProto(
|
||||||
|
myNumber,
|
||||||
|
contentMessage,
|
||||||
|
Date.now(),
|
||||||
|
silent
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
@ -439,7 +522,12 @@ MessageSender.prototype = {
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
var silent = true;
|
var silent = true;
|
||||||
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
|
return this.sendIndividualProto(
|
||||||
|
myNumber,
|
||||||
|
contentMessage,
|
||||||
|
Date.now(),
|
||||||
|
silent
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
@ -471,7 +559,12 @@ MessageSender.prototype = {
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
var silent = true;
|
var silent = true;
|
||||||
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
|
return this.sendIndividualProto(
|
||||||
|
myNumber,
|
||||||
|
contentMessage,
|
||||||
|
Date.now(),
|
||||||
|
silent
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
@ -501,7 +594,8 @@ MessageSender.prototype = {
|
||||||
// We want the NullMessage to look like a normal outgoing message; not silent
|
// We want the NullMessage to look like a normal outgoing message; not silent
|
||||||
const promise = this.sendIndividualProto(destination, contentMessage, now);
|
const promise = this.sendIndividualProto(destination, contentMessage, now);
|
||||||
|
|
||||||
return promise.then(function() {
|
return promise.then(
|
||||||
|
function() {
|
||||||
var verified = new textsecure.protobuf.Verified();
|
var verified = new textsecure.protobuf.Verified();
|
||||||
verified.state = state;
|
verified.state = state;
|
||||||
verified.destination = destination;
|
verified.destination = destination;
|
||||||
|
@ -516,18 +610,22 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
var silent = true;
|
var silent = true;
|
||||||
return this.sendIndividualProto(myNumber, contentMessage, now, silent);
|
return this.sendIndividualProto(myNumber, contentMessage, now, silent);
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
sendGroupProto: function(numbers, proto, timestamp) {
|
sendGroupProto: function(numbers, proto, timestamp) {
|
||||||
timestamp = timestamp || Date.now();
|
timestamp = timestamp || Date.now();
|
||||||
var me = textsecure.storage.user.getNumber();
|
var me = textsecure.storage.user.getNumber();
|
||||||
numbers = numbers.filter(function(number) { return number != me; });
|
numbers = numbers.filter(function(number) {
|
||||||
|
return number != me;
|
||||||
|
});
|
||||||
if (numbers.length === 0) {
|
if (numbers.length === 0) {
|
||||||
return Promise.reject(new Error('No other members in the group'));
|
return Promise.reject(new Error('No other members in the group'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(
|
||||||
|
function(resolve, reject) {
|
||||||
var silent = true;
|
var silent = true;
|
||||||
var callback = function(res) {
|
var callback = function(res) {
|
||||||
res.dataMessage = proto.toArrayBuffer();
|
res.dataMessage = proto.toArrayBuffer();
|
||||||
|
@ -539,10 +637,19 @@ MessageSender.prototype = {
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
this.sendMessageProto(timestamp, numbers, proto, callback, silent);
|
this.sendMessageProto(timestamp, numbers, proto, callback, silent);
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
sendMessageToNumber: function(number, messageText, attachments, quote, timestamp, expireTimer, profileKey) {
|
sendMessageToNumber: function(
|
||||||
|
number,
|
||||||
|
messageText,
|
||||||
|
attachments,
|
||||||
|
quote,
|
||||||
|
timestamp,
|
||||||
|
expireTimer,
|
||||||
|
profileKey
|
||||||
|
) {
|
||||||
return this.sendMessage({
|
return this.sendMessage({
|
||||||
recipients: [number],
|
recipients: [number],
|
||||||
body: messageText,
|
body: messageText,
|
||||||
|
@ -551,69 +658,87 @@ MessageSender.prototype = {
|
||||||
quote: quote,
|
quote: quote,
|
||||||
needsSync: true,
|
needsSync: true,
|
||||||
expireTimer: expireTimer,
|
expireTimer: expireTimer,
|
||||||
profileKey : profileKey
|
profileKey: profileKey,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
resetSession: function(number, timestamp) {
|
resetSession: function(number, timestamp) {
|
||||||
console.log('resetting secure session');
|
console.log('resetting secure session');
|
||||||
var proto = new textsecure.protobuf.DataMessage();
|
var proto = new textsecure.protobuf.DataMessage();
|
||||||
proto.body = "TERMINATE";
|
proto.body = 'TERMINATE';
|
||||||
proto.flags = textsecure.protobuf.DataMessage.Flags.END_SESSION;
|
proto.flags = textsecure.protobuf.DataMessage.Flags.END_SESSION;
|
||||||
|
|
||||||
var logError = function(prefix) {
|
var logError = function(prefix) {
|
||||||
return function(error) {
|
return function(error) {
|
||||||
console.log(
|
console.log(prefix, error && error.stack ? error.stack : error);
|
||||||
prefix,
|
|
||||||
error && error.stack ? error.stack : error
|
|
||||||
);
|
|
||||||
throw error;
|
throw error;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
var deleteAllSessions = function(number) {
|
var deleteAllSessions = function(number) {
|
||||||
return textsecure.storage.protocol.getDeviceIds(number)
|
return textsecure.storage.protocol
|
||||||
|
.getDeviceIds(number)
|
||||||
.then(function(deviceIds) {
|
.then(function(deviceIds) {
|
||||||
return Promise.all(deviceIds.map(function(deviceId) {
|
return Promise.all(
|
||||||
var address = new libsignal.SignalProtocolAddress(number, deviceId);
|
deviceIds.map(function(deviceId) {
|
||||||
|
var address = new libsignal.SignalProtocolAddress(
|
||||||
|
number,
|
||||||
|
deviceId
|
||||||
|
);
|
||||||
console.log('deleting sessions for', address.toString());
|
console.log('deleting sessions for', address.toString());
|
||||||
var sessionCipher = new libsignal.SessionCipher(
|
var sessionCipher = new libsignal.SessionCipher(
|
||||||
textsecure.storage.protocol,
|
textsecure.storage.protocol,
|
||||||
address
|
address
|
||||||
);
|
);
|
||||||
return sessionCipher.deleteAllSessionsForDevice();
|
return sessionCipher.deleteAllSessionsForDevice();
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var sendToContact = deleteAllSessions(number)
|
var sendToContact = deleteAllSessions(number)
|
||||||
.catch(logError('resetSession/deleteAllSessions1 error:'))
|
.catch(logError('resetSession/deleteAllSessions1 error:'))
|
||||||
|
.then(
|
||||||
|
function() {
|
||||||
|
console.log(
|
||||||
|
'finished closing local sessions, now sending to contact'
|
||||||
|
);
|
||||||
|
return this.sendIndividualProto(number, proto, timestamp).catch(
|
||||||
|
logError('resetSession/sendToContact error:')
|
||||||
|
);
|
||||||
|
}.bind(this)
|
||||||
|
)
|
||||||
.then(function() {
|
.then(function() {
|
||||||
console.log('finished closing local sessions, now sending to contact');
|
return deleteAllSessions(number).catch(
|
||||||
return this.sendIndividualProto(number, proto, timestamp)
|
logError('resetSession/deleteAllSessions2 error:')
|
||||||
.catch(logError('resetSession/sendToContact error:'))
|
);
|
||||||
}.bind(this))
|
|
||||||
.then(function() {
|
|
||||||
return deleteAllSessions(number)
|
|
||||||
.catch(logError('resetSession/deleteAllSessions2 error:'));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var buffer = proto.toArrayBuffer();
|
var buffer = proto.toArrayBuffer();
|
||||||
var sendSync = this.sendSyncMessage(buffer, timestamp, number)
|
var sendSync = this.sendSyncMessage(buffer, timestamp, number).catch(
|
||||||
.catch(logError('resetSession/sendSync error:'));
|
logError('resetSession/sendSync error:')
|
||||||
|
);
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([sendToContact, sendSync]);
|
||||||
sendToContact,
|
|
||||||
sendSync
|
|
||||||
]);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
sendMessageToGroup: function(groupId, messageText, attachments, quote, timestamp, expireTimer, profileKey) {
|
sendMessageToGroup: function(
|
||||||
return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) {
|
groupId,
|
||||||
|
messageText,
|
||||||
|
attachments,
|
||||||
|
quote,
|
||||||
|
timestamp,
|
||||||
|
expireTimer,
|
||||||
|
profileKey
|
||||||
|
) {
|
||||||
|
return textsecure.storage.groups.getNumbers(groupId).then(
|
||||||
|
function(numbers) {
|
||||||
if (numbers === undefined)
|
if (numbers === undefined)
|
||||||
return Promise.reject(new Error("Unknown Group"));
|
return Promise.reject(new Error('Unknown Group'));
|
||||||
|
|
||||||
var me = textsecure.storage.user.getNumber();
|
var me = textsecure.storage.user.getNumber();
|
||||||
numbers = numbers.filter(function(number) { return number != me; });
|
numbers = numbers.filter(function(number) {
|
||||||
|
return number != me;
|
||||||
|
});
|
||||||
if (numbers.length === 0) {
|
if (numbers.length === 0) {
|
||||||
return Promise.reject(new Error('No other members in the group'));
|
return Promise.reject(new Error('No other members in the group'));
|
||||||
}
|
}
|
||||||
|
@ -629,17 +754,19 @@ MessageSender.prototype = {
|
||||||
profileKey: profileKey,
|
profileKey: profileKey,
|
||||||
group: {
|
group: {
|
||||||
id: groupId,
|
id: groupId,
|
||||||
type: textsecure.protobuf.GroupContext.Type.DELIVER
|
type: textsecure.protobuf.GroupContext.Type.DELIVER,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
createGroup: function(numbers, name, avatar) {
|
createGroup: function(numbers, name, avatar) {
|
||||||
var proto = new textsecure.protobuf.DataMessage();
|
var proto = new textsecure.protobuf.DataMessage();
|
||||||
proto.group = new textsecure.protobuf.GroupContext();
|
proto.group = new textsecure.protobuf.GroupContext();
|
||||||
|
|
||||||
return textsecure.storage.groups.createNewGroup(numbers).then(function(group) {
|
return textsecure.storage.groups.createNewGroup(numbers).then(
|
||||||
|
function(group) {
|
||||||
proto.group.id = stringToArrayBuffer(group.id);
|
proto.group.id = stringToArrayBuffer(group.id);
|
||||||
var numbers = group.numbers;
|
var numbers = group.numbers;
|
||||||
|
|
||||||
|
@ -647,13 +774,16 @@ MessageSender.prototype = {
|
||||||
proto.group.members = numbers;
|
proto.group.members = numbers;
|
||||||
proto.group.name = name;
|
proto.group.name = name;
|
||||||
|
|
||||||
return this.makeAttachmentPointer(avatar).then(function(attachment) {
|
return this.makeAttachmentPointer(avatar).then(
|
||||||
|
function(attachment) {
|
||||||
proto.group.avatar = attachment;
|
proto.group.avatar = attachment;
|
||||||
return this.sendGroupProto(numbers, proto).then(function() {
|
return this.sendGroupProto(numbers, proto).then(function() {
|
||||||
return proto.group.id;
|
return proto.group.id;
|
||||||
});
|
});
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
}.bind(this));
|
);
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
updateGroup: function(groupId, name, avatar, numbers) {
|
updateGroup: function(groupId, name, avatar, numbers) {
|
||||||
|
@ -664,19 +794,23 @@ MessageSender.prototype = {
|
||||||
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
||||||
proto.group.name = name;
|
proto.group.name = name;
|
||||||
|
|
||||||
return textsecure.storage.groups.addNumbers(groupId, numbers).then(function(numbers) {
|
return textsecure.storage.groups.addNumbers(groupId, numbers).then(
|
||||||
|
function(numbers) {
|
||||||
if (numbers === undefined) {
|
if (numbers === undefined) {
|
||||||
return Promise.reject(new Error("Unknown Group"));
|
return Promise.reject(new Error('Unknown Group'));
|
||||||
}
|
}
|
||||||
proto.group.members = numbers;
|
proto.group.members = numbers;
|
||||||
|
|
||||||
return this.makeAttachmentPointer(avatar).then(function(attachment) {
|
return this.makeAttachmentPointer(avatar).then(
|
||||||
|
function(attachment) {
|
||||||
proto.group.avatar = attachment;
|
proto.group.avatar = attachment;
|
||||||
return this.sendGroupProto(numbers, proto).then(function() {
|
return this.sendGroupProto(numbers, proto).then(function() {
|
||||||
return proto.group.id;
|
return proto.group.id;
|
||||||
});
|
});
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
}.bind(this));
|
);
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
addNumberToGroup: function(groupId, number) {
|
addNumberToGroup: function(groupId, number) {
|
||||||
|
@ -685,13 +819,15 @@ MessageSender.prototype = {
|
||||||
proto.group.id = stringToArrayBuffer(groupId);
|
proto.group.id = stringToArrayBuffer(groupId);
|
||||||
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
||||||
|
|
||||||
return textsecure.storage.groups.addNumbers(groupId, [number]).then(function(numbers) {
|
return textsecure.storage.groups.addNumbers(groupId, [number]).then(
|
||||||
|
function(numbers) {
|
||||||
if (numbers === undefined)
|
if (numbers === undefined)
|
||||||
return Promise.reject(new Error("Unknown Group"));
|
return Promise.reject(new Error('Unknown Group'));
|
||||||
proto.group.members = numbers;
|
proto.group.members = numbers;
|
||||||
|
|
||||||
return this.sendGroupProto(numbers, proto);
|
return this.sendGroupProto(numbers, proto);
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
setGroupName: function(groupId, name) {
|
setGroupName: function(groupId, name) {
|
||||||
|
@ -701,13 +837,15 @@ MessageSender.prototype = {
|
||||||
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
||||||
proto.group.name = name;
|
proto.group.name = name;
|
||||||
|
|
||||||
return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) {
|
return textsecure.storage.groups.getNumbers(groupId).then(
|
||||||
|
function(numbers) {
|
||||||
if (numbers === undefined)
|
if (numbers === undefined)
|
||||||
return Promise.reject(new Error("Unknown Group"));
|
return Promise.reject(new Error('Unknown Group'));
|
||||||
proto.group.members = numbers;
|
proto.group.members = numbers;
|
||||||
|
|
||||||
return this.sendGroupProto(numbers, proto);
|
return this.sendGroupProto(numbers, proto);
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
setGroupAvatar: function(groupId, avatar) {
|
setGroupAvatar: function(groupId, avatar) {
|
||||||
|
@ -716,16 +854,20 @@ MessageSender.prototype = {
|
||||||
proto.group.id = stringToArrayBuffer(groupId);
|
proto.group.id = stringToArrayBuffer(groupId);
|
||||||
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
||||||
|
|
||||||
return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) {
|
return textsecure.storage.groups.getNumbers(groupId).then(
|
||||||
|
function(numbers) {
|
||||||
if (numbers === undefined)
|
if (numbers === undefined)
|
||||||
return Promise.reject(new Error("Unknown Group"));
|
return Promise.reject(new Error('Unknown Group'));
|
||||||
proto.group.members = numbers;
|
proto.group.members = numbers;
|
||||||
|
|
||||||
return this.makeAttachmentPointer(avatar).then(function(attachment) {
|
return this.makeAttachmentPointer(avatar).then(
|
||||||
|
function(attachment) {
|
||||||
proto.group.avatar = attachment;
|
proto.group.avatar = attachment;
|
||||||
return this.sendGroupProto(numbers, proto);
|
return this.sendGroupProto(numbers, proto);
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
}.bind(this));
|
);
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
leaveGroup: function(groupId) {
|
leaveGroup: function(groupId) {
|
||||||
|
@ -734,21 +876,33 @@ MessageSender.prototype = {
|
||||||
proto.group.id = stringToArrayBuffer(groupId);
|
proto.group.id = stringToArrayBuffer(groupId);
|
||||||
proto.group.type = textsecure.protobuf.GroupContext.Type.QUIT;
|
proto.group.type = textsecure.protobuf.GroupContext.Type.QUIT;
|
||||||
|
|
||||||
return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) {
|
return textsecure.storage.groups
|
||||||
|
.getNumbers(groupId)
|
||||||
|
.then(function(numbers) {
|
||||||
if (numbers === undefined)
|
if (numbers === undefined)
|
||||||
return Promise.reject(new Error("Unknown Group"));
|
return Promise.reject(new Error('Unknown Group'));
|
||||||
return textsecure.storage.groups.deleteGroup(groupId).then(function() {
|
return textsecure.storage.groups.deleteGroup(groupId).then(
|
||||||
|
function() {
|
||||||
return this.sendGroupProto(numbers, proto);
|
return this.sendGroupProto(numbers, proto);
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
sendExpirationTimerUpdateToGroup: function(groupId, expireTimer, timestamp, profileKey) {
|
sendExpirationTimerUpdateToGroup: function(
|
||||||
return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) {
|
groupId,
|
||||||
|
expireTimer,
|
||||||
|
timestamp,
|
||||||
|
profileKey
|
||||||
|
) {
|
||||||
|
return textsecure.storage.groups.getNumbers(groupId).then(
|
||||||
|
function(numbers) {
|
||||||
if (numbers === undefined)
|
if (numbers === undefined)
|
||||||
return Promise.reject(new Error("Unknown Group"));
|
return Promise.reject(new Error('Unknown Group'));
|
||||||
|
|
||||||
var me = textsecure.storage.user.getNumber();
|
var me = textsecure.storage.user.getNumber();
|
||||||
numbers = numbers.filter(function(number) { return number != me; });
|
numbers = numbers.filter(function(number) {
|
||||||
|
return number != me;
|
||||||
|
});
|
||||||
if (numbers.length === 0) {
|
if (numbers.length === 0) {
|
||||||
return Promise.reject(new Error('No other members in the group'));
|
return Promise.reject(new Error('No other members in the group'));
|
||||||
}
|
}
|
||||||
|
@ -761,12 +915,18 @@ MessageSender.prototype = {
|
||||||
flags: textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
|
flags: textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
|
||||||
group: {
|
group: {
|
||||||
id: groupId,
|
id: groupId,
|
||||||
type: textsecure.protobuf.GroupContext.Type.DELIVER
|
type: textsecure.protobuf.GroupContext.Type.DELIVER,
|
||||||
}
|
|
||||||
});
|
|
||||||
}.bind(this));
|
|
||||||
},
|
},
|
||||||
sendExpirationTimerUpdateToNumber: function(number, expireTimer, timestamp, profileKey) {
|
});
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
sendExpirationTimerUpdateToNumber: function(
|
||||||
|
number,
|
||||||
|
expireTimer,
|
||||||
|
timestamp,
|
||||||
|
profileKey
|
||||||
|
) {
|
||||||
var proto = new textsecure.protobuf.DataMessage();
|
var proto = new textsecure.protobuf.DataMessage();
|
||||||
return this.sendMessage({
|
return this.sendMessage({
|
||||||
recipients: [number],
|
recipients: [number],
|
||||||
|
@ -774,25 +934,47 @@ MessageSender.prototype = {
|
||||||
needsSync: true,
|
needsSync: true,
|
||||||
expireTimer: expireTimer,
|
expireTimer: expireTimer,
|
||||||
profileKey: profileKey,
|
profileKey: profileKey,
|
||||||
flags : textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE
|
flags: textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
window.textsecure = window.textsecure || {};
|
window.textsecure = window.textsecure || {};
|
||||||
|
|
||||||
textsecure.MessageSender = function(url, username, password, cdn_url) {
|
textsecure.MessageSender = function(url, username, password, cdn_url) {
|
||||||
var sender = new MessageSender(url, username, password, cdn_url);
|
var sender = new MessageSender(url, username, password, cdn_url);
|
||||||
textsecure.replay.registerFunction(sender.tryMessageAgain.bind(sender), textsecure.replay.Type.ENCRYPT_MESSAGE);
|
textsecure.replay.registerFunction(
|
||||||
textsecure.replay.registerFunction(sender.retransmitMessage.bind(sender), textsecure.replay.Type.TRANSMIT_MESSAGE);
|
sender.tryMessageAgain.bind(sender),
|
||||||
textsecure.replay.registerFunction(sender.sendMessage.bind(sender), textsecure.replay.Type.REBUILD_MESSAGE);
|
textsecure.replay.Type.ENCRYPT_MESSAGE
|
||||||
textsecure.replay.registerFunction(sender.retrySendMessageProto.bind(sender), textsecure.replay.Type.RETRY_SEND_MESSAGE_PROTO);
|
);
|
||||||
|
textsecure.replay.registerFunction(
|
||||||
|
sender.retransmitMessage.bind(sender),
|
||||||
|
textsecure.replay.Type.TRANSMIT_MESSAGE
|
||||||
|
);
|
||||||
|
textsecure.replay.registerFunction(
|
||||||
|
sender.sendMessage.bind(sender),
|
||||||
|
textsecure.replay.Type.REBUILD_MESSAGE
|
||||||
|
);
|
||||||
|
textsecure.replay.registerFunction(
|
||||||
|
sender.retrySendMessageProto.bind(sender),
|
||||||
|
textsecure.replay.Type.RETRY_SEND_MESSAGE_PROTO
|
||||||
|
);
|
||||||
|
|
||||||
this.sendExpirationTimerUpdateToNumber = sender.sendExpirationTimerUpdateToNumber.bind(sender);
|
this.sendExpirationTimerUpdateToNumber = sender.sendExpirationTimerUpdateToNumber.bind(
|
||||||
this.sendExpirationTimerUpdateToGroup = sender.sendExpirationTimerUpdateToGroup .bind(sender);
|
sender
|
||||||
this.sendRequestGroupSyncMessage = sender.sendRequestGroupSyncMessage .bind(sender);
|
);
|
||||||
this.sendRequestContactSyncMessage = sender.sendRequestContactSyncMessage .bind(sender);
|
this.sendExpirationTimerUpdateToGroup = sender.sendExpirationTimerUpdateToGroup.bind(
|
||||||
this.sendRequestConfigurationSyncMessage = sender.sendRequestConfigurationSyncMessage.bind(sender);
|
sender
|
||||||
|
);
|
||||||
|
this.sendRequestGroupSyncMessage = sender.sendRequestGroupSyncMessage.bind(
|
||||||
|
sender
|
||||||
|
);
|
||||||
|
this.sendRequestContactSyncMessage = sender.sendRequestContactSyncMessage.bind(
|
||||||
|
sender
|
||||||
|
);
|
||||||
|
this.sendRequestConfigurationSyncMessage = sender.sendRequestConfigurationSyncMessage.bind(
|
||||||
|
sender
|
||||||
|
);
|
||||||
this.sendMessageToNumber = sender.sendMessageToNumber.bind(sender);
|
this.sendMessageToNumber = sender.sendMessageToNumber.bind(sender);
|
||||||
this.resetSession = sender.resetSession.bind(sender);
|
this.resetSession = sender.resetSession.bind(sender);
|
||||||
this.sendMessageToGroup = sender.sendMessageToGroup.bind(sender);
|
this.sendMessageToGroup = sender.sendMessageToGroup.bind(sender);
|
||||||
|
@ -811,5 +993,5 @@ textsecure.MessageSender = function(url, username, password, cdn_url) {
|
||||||
};
|
};
|
||||||
|
|
||||||
textsecure.MessageSender.prototype = {
|
textsecure.MessageSender.prototype = {
|
||||||
constructor: textsecure.MessageSender
|
constructor: textsecure.MessageSender,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
;(function() {
|
(function() {
|
||||||
|
|
||||||
/************************************************
|
/************************************************
|
||||||
*** Utilities to store data in local storage ***
|
*** Utilities to store data in local storage ***
|
||||||
************************************************/
|
************************************************/
|
||||||
|
@ -14,20 +13,18 @@
|
||||||
*** Base Storage Routines ***
|
*** Base Storage Routines ***
|
||||||
*****************************/
|
*****************************/
|
||||||
put: function(key, value) {
|
put: function(key, value) {
|
||||||
if (value === undefined)
|
if (value === undefined) throw new Error('Tried to store undefined');
|
||||||
throw new Error("Tried to store undefined");
|
localStorage.setItem('' + key, textsecure.utils.jsonThing(value));
|
||||||
localStorage.setItem("" + key, textsecure.utils.jsonThing(value));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
get: function(key, defaultValue) {
|
get: function(key, defaultValue) {
|
||||||
var value = localStorage.getItem("" + key);
|
var value = localStorage.getItem('' + key);
|
||||||
if (value === null)
|
if (value === null) return defaultValue;
|
||||||
return defaultValue;
|
|
||||||
return JSON.parse(value);
|
return JSON.parse(value);
|
||||||
},
|
},
|
||||||
|
|
||||||
remove: function(key) {
|
remove: function(key) {
|
||||||
localStorage.removeItem("" + key);
|
localStorage.removeItem('' + key);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,4 +40,3 @@
|
||||||
return textsecure.storage.impl.remove(key);
|
return textsecure.storage.impl.remove(key);
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
;(function() {
|
(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/*********************
|
/*********************
|
||||||
|
@ -25,15 +25,19 @@
|
||||||
var groupId = groupId;
|
var groupId = groupId;
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function(resolve) {
|
||||||
if (groupId !== undefined) {
|
if (groupId !== undefined) {
|
||||||
resolve(textsecure.storage.protocol.getGroup(groupId).then(function(group) {
|
resolve(
|
||||||
|
textsecure.storage.protocol.getGroup(groupId).then(function(group) {
|
||||||
if (group !== undefined) {
|
if (group !== undefined) {
|
||||||
throw new Error("Tried to recreate group");
|
throw new Error('Tried to recreate group');
|
||||||
}
|
}
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
resolve(generateNewGroupId().then(function(newGroupId) {
|
resolve(
|
||||||
|
generateNewGroupId().then(function(newGroupId) {
|
||||||
groupId = newGroupId;
|
groupId = newGroupId;
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
var me = textsecure.storage.user.getNumber();
|
var me = textsecure.storage.user.getNumber();
|
||||||
|
@ -42,49 +46,54 @@
|
||||||
for (var i in numbers) {
|
for (var i in numbers) {
|
||||||
var number = numbers[i];
|
var number = numbers[i];
|
||||||
if (!textsecure.utils.isNumberSane(number))
|
if (!textsecure.utils.isNumberSane(number))
|
||||||
throw new Error("Invalid number in group");
|
throw new Error('Invalid number in group');
|
||||||
if (number == me)
|
if (number == me) haveMe = true;
|
||||||
haveMe = true;
|
if (finalNumbers.indexOf(number) < 0) finalNumbers.push(number);
|
||||||
if (finalNumbers.indexOf(number) < 0)
|
|
||||||
finalNumbers.push(number);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!haveMe)
|
if (!haveMe) finalNumbers.push(me);
|
||||||
finalNumbers.push(me);
|
|
||||||
|
|
||||||
var groupObject = { numbers: finalNumbers, numberRegistrationIds: {} };
|
var groupObject = { numbers: finalNumbers, numberRegistrationIds: {} };
|
||||||
for (var i in finalNumbers)
|
for (var i in finalNumbers)
|
||||||
groupObject.numberRegistrationIds[finalNumbers[i]] = {};
|
groupObject.numberRegistrationIds[finalNumbers[i]] = {};
|
||||||
|
|
||||||
return textsecure.storage.protocol.putGroup(groupId, groupObject).then(function() {
|
return textsecure.storage.protocol
|
||||||
|
.putGroup(groupId, groupObject)
|
||||||
|
.then(function() {
|
||||||
return { id: groupId, numbers: finalNumbers };
|
return { id: groupId, numbers: finalNumbers };
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getNumbers: function(groupId) {
|
getNumbers: function(groupId) {
|
||||||
return textsecure.storage.protocol.getGroup(groupId).then(function(group) {
|
return textsecure.storage.protocol
|
||||||
if (group === undefined)
|
.getGroup(groupId)
|
||||||
return undefined;
|
.then(function(group) {
|
||||||
|
if (group === undefined) return undefined;
|
||||||
|
|
||||||
return group.numbers;
|
return group.numbers;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
removeNumber: function(groupId, number) {
|
removeNumber: function(groupId, number) {
|
||||||
return textsecure.storage.protocol.getGroup(groupId).then(function(group) {
|
return textsecure.storage.protocol
|
||||||
if (group === undefined)
|
.getGroup(groupId)
|
||||||
return undefined;
|
.then(function(group) {
|
||||||
|
if (group === undefined) return undefined;
|
||||||
|
|
||||||
var me = textsecure.storage.user.getNumber();
|
var me = textsecure.storage.user.getNumber();
|
||||||
if (number == me)
|
if (number == me)
|
||||||
throw new Error("Cannot remove ourselves from a group, leave the group instead");
|
throw new Error(
|
||||||
|
'Cannot remove ourselves from a group, leave the group instead'
|
||||||
|
);
|
||||||
|
|
||||||
var i = group.numbers.indexOf(number);
|
var i = group.numbers.indexOf(number);
|
||||||
if (i > -1) {
|
if (i > -1) {
|
||||||
group.numbers.splice(i, 1);
|
group.numbers.splice(i, 1);
|
||||||
delete group.numberRegistrationIds[number];
|
delete group.numberRegistrationIds[number];
|
||||||
return textsecure.storage.protocol.putGroup(groupId, group).then(function() {
|
return textsecure.storage.protocol
|
||||||
|
.putGroup(groupId, group)
|
||||||
|
.then(function() {
|
||||||
return group.numbers;
|
return group.numbers;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -94,21 +103,24 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
addNumbers: function(groupId, numbers) {
|
addNumbers: function(groupId, numbers) {
|
||||||
return textsecure.storage.protocol.getGroup(groupId).then(function(group) {
|
return textsecure.storage.protocol
|
||||||
if (group === undefined)
|
.getGroup(groupId)
|
||||||
return undefined;
|
.then(function(group) {
|
||||||
|
if (group === undefined) return undefined;
|
||||||
|
|
||||||
for (var i in numbers) {
|
for (var i in numbers) {
|
||||||
var number = numbers[i];
|
var number = numbers[i];
|
||||||
if (!textsecure.utils.isNumberSane(number))
|
if (!textsecure.utils.isNumberSane(number))
|
||||||
throw new Error("Invalid number in set to add to group");
|
throw new Error('Invalid number in set to add to group');
|
||||||
if (group.numbers.indexOf(number) < 0) {
|
if (group.numbers.indexOf(number) < 0) {
|
||||||
group.numbers.push(number);
|
group.numbers.push(number);
|
||||||
group.numberRegistrationIds[number] = {};
|
group.numberRegistrationIds[number] = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return textsecure.storage.protocol.putGroup(groupId, group).then(function() {
|
return textsecure.storage.protocol
|
||||||
|
.putGroup(groupId, group)
|
||||||
|
.then(function() {
|
||||||
return group.numbers;
|
return group.numbers;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -119,26 +131,34 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
getGroup: function(groupId) {
|
getGroup: function(groupId) {
|
||||||
return textsecure.storage.protocol.getGroup(groupId).then(function(group) {
|
return textsecure.storage.protocol
|
||||||
if (group === undefined)
|
.getGroup(groupId)
|
||||||
return undefined;
|
.then(function(group) {
|
||||||
|
if (group === undefined) return undefined;
|
||||||
|
|
||||||
return { id: groupId, numbers: group.numbers };
|
return { id: groupId, numbers: group.numbers };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
updateNumbers: function(groupId, numbers) {
|
updateNumbers: function(groupId, numbers) {
|
||||||
return textsecure.storage.protocol.getGroup(groupId).then(function(group) {
|
return textsecure.storage.protocol
|
||||||
|
.getGroup(groupId)
|
||||||
|
.then(function(group) {
|
||||||
if (group === undefined)
|
if (group === undefined)
|
||||||
throw new Error("Tried to update numbers for unknown group");
|
throw new Error('Tried to update numbers for unknown group');
|
||||||
|
|
||||||
if (numbers.filter(textsecure.utils.isNumberSane).length < numbers.length)
|
if (
|
||||||
throw new Error("Invalid number in new group members");
|
numbers.filter(textsecure.utils.isNumberSane).length <
|
||||||
|
numbers.length
|
||||||
|
)
|
||||||
|
throw new Error('Invalid number in new group members');
|
||||||
|
|
||||||
var added = numbers.filter(function(number) { return group.numbers.indexOf(number) < 0; });
|
var added = numbers.filter(function(number) {
|
||||||
|
return group.numbers.indexOf(number) < 0;
|
||||||
|
});
|
||||||
|
|
||||||
return textsecure.storage.groups.addNumbers(groupId, added);
|
return textsecure.storage.groups.addNumbers(groupId, added);
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
;(function() {
|
(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/*****************************************
|
/*****************************************
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
;(function() {
|
(function() {
|
||||||
/*********************************************
|
/*********************************************
|
||||||
*** Utilities to store data about the user ***
|
*** Utilities to store data about the user ***
|
||||||
**********************************************/
|
**********************************************/
|
||||||
|
@ -9,28 +9,26 @@
|
||||||
|
|
||||||
window.textsecure.storage.user = {
|
window.textsecure.storage.user = {
|
||||||
setNumberAndDeviceId: function(number, deviceId, deviceName) {
|
setNumberAndDeviceId: function(number, deviceId, deviceName) {
|
||||||
textsecure.storage.put("number_id", number + "." + deviceId);
|
textsecure.storage.put('number_id', number + '.' + deviceId);
|
||||||
if (deviceName) {
|
if (deviceName) {
|
||||||
textsecure.storage.put("device_name", deviceName);
|
textsecure.storage.put('device_name', deviceName);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getNumber: function(key, defaultValue) {
|
getNumber: function(key, defaultValue) {
|
||||||
var number_id = textsecure.storage.get("number_id");
|
var number_id = textsecure.storage.get('number_id');
|
||||||
if (number_id === undefined)
|
if (number_id === undefined) return undefined;
|
||||||
return undefined;
|
|
||||||
return textsecure.utils.unencodeNumber(number_id)[0];
|
return textsecure.utils.unencodeNumber(number_id)[0];
|
||||||
},
|
},
|
||||||
|
|
||||||
getDeviceId: function(key) {
|
getDeviceId: function(key) {
|
||||||
var number_id = textsecure.storage.get("number_id");
|
var number_id = textsecure.storage.get('number_id');
|
||||||
if (number_id === undefined)
|
if (number_id === undefined) return undefined;
|
||||||
return undefined;
|
|
||||||
return textsecure.utils.unencodeNumber(number_id)[1];
|
return textsecure.utils.unencodeNumber(number_id)[1];
|
||||||
},
|
},
|
||||||
|
|
||||||
getDeviceName: function(key) {
|
getDeviceName: function(key) {
|
||||||
return textsecure.storage.get("device_name");
|
return textsecure.storage.get('device_name');
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
;(function() {
|
(function() {
|
||||||
"use strict";
|
'use strict';
|
||||||
|
|
||||||
window.StringView = {
|
window.StringView = {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* These functions from the Mozilla Developer Network
|
* These functions from the Mozilla Developer Network
|
||||||
* and have been placed in the public domain.
|
* and have been placed in the public domain.
|
||||||
|
@ -11,33 +10,39 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
b64ToUint6: function(nChr) {
|
b64ToUint6: function(nChr) {
|
||||||
return nChr > 64 && nChr < 91 ?
|
return nChr > 64 && nChr < 91
|
||||||
nChr - 65
|
? nChr - 65
|
||||||
: nChr > 96 && nChr < 123 ?
|
: nChr > 96 && nChr < 123
|
||||||
nChr - 71
|
? nChr - 71
|
||||||
: nChr > 47 && nChr < 58 ?
|
: nChr > 47 && nChr < 58
|
||||||
nChr + 4
|
? nChr + 4
|
||||||
: nChr === 43 ?
|
: nChr === 43
|
||||||
62
|
? 62
|
||||||
: nChr === 47 ?
|
: nChr === 47
|
||||||
63
|
? 63
|
||||||
:
|
: 0;
|
||||||
0;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
base64ToBytes: function(sBase64, nBlocksSize) {
|
base64ToBytes: function(sBase64, nBlocksSize) {
|
||||||
var
|
var sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ''),
|
||||||
sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length,
|
nInLen = sB64Enc.length,
|
||||||
nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2;
|
nOutLen = nBlocksSize
|
||||||
|
? Math.ceil(((nInLen * 3 + 1) >> 2) / nBlocksSize) * nBlocksSize
|
||||||
|
: (nInLen * 3 + 1) >> 2;
|
||||||
var aBBytes = new ArrayBuffer(nOutLen);
|
var aBBytes = new ArrayBuffer(nOutLen);
|
||||||
var taBytes = new Uint8Array(aBBytes);
|
var taBytes = new Uint8Array(aBBytes);
|
||||||
|
|
||||||
for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
|
for (
|
||||||
|
var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0;
|
||||||
|
nInIdx < nInLen;
|
||||||
|
nInIdx++
|
||||||
|
) {
|
||||||
nMod4 = nInIdx & 3;
|
nMod4 = nInIdx & 3;
|
||||||
nUint24 |= StringView.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
|
nUint24 |=
|
||||||
|
StringView.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (18 - 6 * nMod4);
|
||||||
if (nMod4 === 3 || nInLen - nInIdx === 1) {
|
if (nMod4 === 3 || nInLen - nInIdx === 1) {
|
||||||
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
|
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
|
||||||
taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
|
taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255;
|
||||||
}
|
}
|
||||||
nUint24 = 0;
|
nUint24 = 0;
|
||||||
}
|
}
|
||||||
|
@ -46,37 +51,43 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
uint6ToB64: function(nUint6) {
|
uint6ToB64: function(nUint6) {
|
||||||
return nUint6 < 26 ?
|
return nUint6 < 26
|
||||||
nUint6 + 65
|
? nUint6 + 65
|
||||||
: nUint6 < 52 ?
|
: nUint6 < 52
|
||||||
nUint6 + 71
|
? nUint6 + 71
|
||||||
: nUint6 < 62 ?
|
: nUint6 < 62
|
||||||
nUint6 - 4
|
? nUint6 - 4
|
||||||
: nUint6 === 62 ?
|
: nUint6 === 62
|
||||||
43
|
? 43
|
||||||
: nUint6 === 63 ?
|
: nUint6 === 63
|
||||||
47
|
? 47
|
||||||
:
|
: 65;
|
||||||
65;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
bytesToBase64: function(aBytes) {
|
bytesToBase64: function(aBytes) {
|
||||||
var nMod3, sB64Enc = "";
|
var nMod3,
|
||||||
for (var nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) {
|
sB64Enc = '';
|
||||||
|
for (
|
||||||
|
var nLen = aBytes.length, nUint24 = 0, nIdx = 0;
|
||||||
|
nIdx < nLen;
|
||||||
|
nIdx++
|
||||||
|
) {
|
||||||
nMod3 = nIdx % 3;
|
nMod3 = nIdx % 3;
|
||||||
if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { sB64Enc += "\r\n"; }
|
if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) {
|
||||||
nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24);
|
sB64Enc += '\r\n';
|
||||||
|
}
|
||||||
|
nUint24 |= aBytes[nIdx] << ((16 >>> nMod3) & 24);
|
||||||
if (nMod3 === 2 || aBytes.length - nIdx === 1) {
|
if (nMod3 === 2 || aBytes.length - nIdx === 1) {
|
||||||
sB64Enc += String.fromCharCode(
|
sB64Enc += String.fromCharCode(
|
||||||
StringView.uint6ToB64(nUint24 >>> 18 & 63),
|
StringView.uint6ToB64((nUint24 >>> 18) & 63),
|
||||||
StringView.uint6ToB64(nUint24 >>> 12 & 63),
|
StringView.uint6ToB64((nUint24 >>> 12) & 63),
|
||||||
StringView.uint6ToB64(nUint24 >>> 6 & 63),
|
StringView.uint6ToB64((nUint24 >>> 6) & 63),
|
||||||
StringView.uint6ToB64(nUint24 & 63)
|
StringView.uint6ToB64(nUint24 & 63)
|
||||||
);
|
);
|
||||||
nUint24 = 0;
|
nUint24 = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sB64Enc.replace(/A(?=A$|$)/g, "=");
|
return sB64Enc.replace(/A(?=A$|$)/g, '=');
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}());
|
})();
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
;(function () {
|
(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
window.textsecure = window.textsecure || {};
|
window.textsecure = window.textsecure || {};
|
||||||
|
|
||||||
function SyncRequest(sender, receiver) {
|
function SyncRequest(sender, receiver) {
|
||||||
if (!(sender instanceof textsecure.MessageSender) || !(receiver instanceof textsecure.MessageReceiver)) {
|
if (
|
||||||
throw new Error('Tried to construct a SyncRequest without MessageSender and MessageReceiver');
|
!(sender instanceof textsecure.MessageSender) ||
|
||||||
|
!(receiver instanceof textsecure.MessageReceiver)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
'Tried to construct a SyncRequest without MessageSender and MessageReceiver'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.receiver = receiver;
|
this.receiver = receiver;
|
||||||
|
|
||||||
|
@ -15,10 +20,13 @@
|
||||||
receiver.addEventListener('groupsync', this.ongroup);
|
receiver.addEventListener('groupsync', this.ongroup);
|
||||||
|
|
||||||
console.log('SyncRequest created. Sending contact sync message...');
|
console.log('SyncRequest created. Sending contact sync message...');
|
||||||
sender.sendRequestContactSyncMessage().then(function() {
|
sender
|
||||||
|
.sendRequestContactSyncMessage()
|
||||||
|
.then(function() {
|
||||||
console.log('SyncRequest now sending group sync messsage...');
|
console.log('SyncRequest now sending group sync messsage...');
|
||||||
return sender.sendRequestGroupSyncMessage();
|
return sender.sendRequestGroupSyncMessage();
|
||||||
}).catch(function(error) {
|
})
|
||||||
|
.catch(function(error) {
|
||||||
console.log(
|
console.log(
|
||||||
'SyncRequest error:',
|
'SyncRequest error:',
|
||||||
error && error.stack ? error.stack : error
|
error && error.stack ? error.stack : error
|
||||||
|
@ -57,18 +65,18 @@
|
||||||
this.receiver.removeEventListener('contactsync', this.oncontact);
|
this.receiver.removeEventListener('contactsync', this.oncontact);
|
||||||
this.receiver.removeEventListener('groupSync', this.ongroup);
|
this.receiver.removeEventListener('groupSync', this.ongroup);
|
||||||
delete this.listeners;
|
delete this.listeners;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
textsecure.SyncRequest = function(sender, receiver) {
|
textsecure.SyncRequest = function(sender, receiver) {
|
||||||
var syncRequest = new SyncRequest(sender, receiver);
|
var syncRequest = new SyncRequest(sender, receiver);
|
||||||
this.addEventListener = syncRequest.addEventListener.bind(syncRequest);
|
this.addEventListener = syncRequest.addEventListener.bind(syncRequest);
|
||||||
this.removeEventListener = syncRequest.removeEventListener.bind(syncRequest);
|
this.removeEventListener = syncRequest.removeEventListener.bind(
|
||||||
|
syncRequest
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
textsecure.SyncRequest.prototype = {
|
textsecure.SyncRequest.prototype = {
|
||||||
constructor: textsecure.SyncRequest
|
constructor: textsecure.SyncRequest,
|
||||||
};
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
}());
|
|
||||||
|
|
|
@ -3,23 +3,26 @@
|
||||||
|
|
||||||
window.textsecure.createTaskWithTimeout = function(task, id, options) {
|
window.textsecure.createTaskWithTimeout = function(task, id, options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
options.timeout = options.timeout || (1000 * 60 * 2); // two minutes
|
options.timeout = options.timeout || 1000 * 60 * 2; // two minutes
|
||||||
|
|
||||||
var errorForStack = new Error('for stack');
|
var errorForStack = new Error('for stack');
|
||||||
return function() {
|
return function() {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
var complete = false;
|
var complete = false;
|
||||||
var timer = setTimeout(function() {
|
var timer = setTimeout(
|
||||||
|
function() {
|
||||||
if (!complete) {
|
if (!complete) {
|
||||||
var message =
|
var message =
|
||||||
(id || '')
|
(id || '') +
|
||||||
+ ' task did not complete in time. Calling stack: '
|
' task did not complete in time. Calling stack: ' +
|
||||||
+ errorForStack.stack;
|
errorForStack.stack;
|
||||||
|
|
||||||
console.log(message);
|
console.log(message);
|
||||||
return reject(new Error(message));
|
return reject(new Error(message));
|
||||||
}
|
}
|
||||||
}.bind(this), options.timeout);
|
}.bind(this),
|
||||||
|
options.timeout
|
||||||
|
);
|
||||||
var clearTimer = function() {
|
var clearTimer = function() {
|
||||||
try {
|
try {
|
||||||
var localTimer = timer;
|
var localTimer = timer;
|
||||||
|
@ -27,8 +30,7 @@
|
||||||
timer = null;
|
timer = null;
|
||||||
clearTimeout(localTimer);
|
clearTimeout(localTimer);
|
||||||
}
|
}
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
console.log(
|
console.log(
|
||||||
id || '',
|
id || '',
|
||||||
'task ran into problem canceling timer. Calling stack:',
|
'task ran into problem canceling timer. Calling stack:',
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
mocha.setup("bdd");
|
mocha.setup('bdd');
|
||||||
window.assert = chai.assert;
|
window.assert = chai.assert;
|
||||||
window.PROTO_ROOT = '../../protos';
|
window.PROTO_ROOT = '../../protos';
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ window.PROTO_ROOT = '../../protos';
|
||||||
result: false,
|
result: false,
|
||||||
message: err.message,
|
message: err.message,
|
||||||
stack: err.stack,
|
stack: err.stack,
|
||||||
titles: flattenTitles(test)
|
titles: flattenTitles(test),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -37,14 +37,14 @@ window.PROTO_ROOT = '../../protos';
|
||||||
SauceReporter.prototype = OriginalReporter.prototype;
|
SauceReporter.prototype = OriginalReporter.prototype;
|
||||||
|
|
||||||
mocha.reporter(SauceReporter);
|
mocha.reporter(SauceReporter);
|
||||||
}());
|
})();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* global helpers for tests
|
* global helpers for tests
|
||||||
*/
|
*/
|
||||||
function assertEqualArrayBuffers(ab1, ab2) {
|
function assertEqualArrayBuffers(ab1, ab2) {
|
||||||
assert.deepEqual(new Uint8Array(ab1), new Uint8Array(ab2));
|
assert.deepEqual(new Uint8Array(ab1), new Uint8Array(ab2));
|
||||||
};
|
}
|
||||||
|
|
||||||
function hexToArrayBuffer(str) {
|
function hexToArrayBuffer(str) {
|
||||||
var ret = new ArrayBuffer(str.length / 2);
|
var ret = new ArrayBuffer(str.length / 2);
|
||||||
|
@ -52,6 +52,6 @@ function hexToArrayBuffer(str) {
|
||||||
for (var i = 0; i < str.length / 2; i++)
|
for (var i = 0; i < str.length / 2; i++)
|
||||||
array[i] = parseInt(str.substr(i * 2, 2), 16);
|
array[i] = parseInt(str.substr(i * 2, 2), 16);
|
||||||
return ret;
|
return ret;
|
||||||
};
|
}
|
||||||
|
|
||||||
window.MockSocket.prototype.addEventListener = function() {};
|
window.MockSocket.prototype.addEventListener = function() {};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
describe("AccountManager", function() {
|
describe('AccountManager', function() {
|
||||||
let accountManager;
|
let accountManager;
|
||||||
let originalServer;
|
let originalServer;
|
||||||
|
|
||||||
|
@ -35,19 +35,23 @@ describe("AccountManager", function() {
|
||||||
|
|
||||||
it('keeps three confirmed keys even if over a week old', function() {
|
it('keeps three confirmed keys even if over a week old', function() {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
signedPreKeys = [{
|
signedPreKeys = [
|
||||||
|
{
|
||||||
keyId: 1,
|
keyId: 1,
|
||||||
created_at: now - DAY * 21,
|
created_at: now - DAY * 21,
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
keyId: 2,
|
keyId: 2,
|
||||||
created_at: now - DAY * 14,
|
created_at: now - DAY * 14,
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
keyId: 3,
|
keyId: 3,
|
||||||
created_at: now - DAY * 18,
|
created_at: now - DAY * 18,
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
}];
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// should be no calls to store.removeSignedPreKey, would cause crash
|
// should be no calls to store.removeSignedPreKey, would cause crash
|
||||||
return accountManager.cleanSignedPreKeys();
|
return accountManager.cleanSignedPreKeys();
|
||||||
|
@ -55,27 +59,33 @@ describe("AccountManager", function() {
|
||||||
|
|
||||||
it('eliminates confirmed keys over a week old, if more than three', function() {
|
it('eliminates confirmed keys over a week old, if more than three', function() {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
signedPreKeys = [{
|
signedPreKeys = [
|
||||||
|
{
|
||||||
keyId: 1,
|
keyId: 1,
|
||||||
created_at: now - DAY * 21,
|
created_at: now - DAY * 21,
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
keyId: 2,
|
keyId: 2,
|
||||||
created_at: now - DAY * 14,
|
created_at: now - DAY * 14,
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
keyId: 3,
|
keyId: 3,
|
||||||
created_at: now - DAY * 4,
|
created_at: now - DAY * 4,
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
keyId: 4,
|
keyId: 4,
|
||||||
created_at: now - DAY * 18,
|
created_at: now - DAY * 18,
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
keyId: 5,
|
keyId: 5,
|
||||||
created_at: now - DAY,
|
created_at: now - DAY,
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
}];
|
},
|
||||||
|
];
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) {
|
window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) {
|
||||||
|
@ -93,19 +103,24 @@ describe("AccountManager", function() {
|
||||||
|
|
||||||
it('keeps at least three unconfirmed keys if no confirmed', function() {
|
it('keeps at least three unconfirmed keys if no confirmed', function() {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
signedPreKeys = [{
|
signedPreKeys = [
|
||||||
|
{
|
||||||
keyId: 1,
|
keyId: 1,
|
||||||
created_at: now - DAY * 14,
|
created_at: now - DAY * 14,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
keyId: 2,
|
keyId: 2,
|
||||||
created_at: now - DAY * 21,
|
created_at: now - DAY * 21,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
keyId: 3,
|
keyId: 3,
|
||||||
created_at: now - DAY * 18,
|
created_at: now - DAY * 18,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
keyId: 4,
|
keyId: 4,
|
||||||
created_at: now - DAY
|
created_at: now - DAY,
|
||||||
}];
|
},
|
||||||
|
];
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) {
|
window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) {
|
||||||
|
@ -123,21 +138,26 @@ describe("AccountManager", function() {
|
||||||
|
|
||||||
it('if some confirmed keys, keeps unconfirmed to addd up to three total', function() {
|
it('if some confirmed keys, keeps unconfirmed to addd up to three total', function() {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
signedPreKeys = [{
|
signedPreKeys = [
|
||||||
|
{
|
||||||
keyId: 1,
|
keyId: 1,
|
||||||
created_at: now - DAY * 21,
|
created_at: now - DAY * 21,
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
keyId: 2,
|
keyId: 2,
|
||||||
created_at: now - DAY * 14,
|
created_at: now - DAY * 14,
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
keyId: 3,
|
keyId: 3,
|
||||||
created_at: now - DAY * 12,
|
created_at: now - DAY * 12,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
keyId: 4,
|
keyId: 4,
|
||||||
created_at: now - DAY * 8,
|
created_at: now - DAY * 8,
|
||||||
}];
|
},
|
||||||
|
];
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) {
|
window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
describe("ContactBuffer", function() {
|
describe('ContactBuffer', function() {
|
||||||
function getTestBuffer() {
|
function getTestBuffer() {
|
||||||
var buffer = new dcodeIO.ByteBuffer();
|
var buffer = new dcodeIO.ByteBuffer();
|
||||||
var avatarBuffer = new dcodeIO.ByteBuffer();
|
var avatarBuffer = new dcodeIO.ByteBuffer();
|
||||||
|
@ -11,9 +11,9 @@ describe("ContactBuffer", function() {
|
||||||
avatarBuffer.limit = avatarBuffer.offset;
|
avatarBuffer.limit = avatarBuffer.offset;
|
||||||
avatarBuffer.offset = 0;
|
avatarBuffer.offset = 0;
|
||||||
var contactInfo = new textsecure.protobuf.ContactDetails({
|
var contactInfo = new textsecure.protobuf.ContactDetails({
|
||||||
name: "Zero Cool",
|
name: 'Zero Cool',
|
||||||
number: "+10000000000",
|
number: '+10000000000',
|
||||||
avatar: { contentType: "image/jpeg", length: avatarLen }
|
avatar: { contentType: 'image/jpeg', length: avatarLen },
|
||||||
});
|
});
|
||||||
var contactInfoBuffer = contactInfo.encode().toArrayBuffer();
|
var contactInfoBuffer = contactInfo.encode().toArrayBuffer();
|
||||||
|
|
||||||
|
@ -28,16 +28,16 @@ describe("ContactBuffer", function() {
|
||||||
return buffer.toArrayBuffer();
|
return buffer.toArrayBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
it("parses an array buffer of contacts", function() {
|
it('parses an array buffer of contacts', function() {
|
||||||
var arrayBuffer = getTestBuffer();
|
var arrayBuffer = getTestBuffer();
|
||||||
var contactBuffer = new ContactBuffer(arrayBuffer);
|
var contactBuffer = new ContactBuffer(arrayBuffer);
|
||||||
var contact = contactBuffer.next();
|
var contact = contactBuffer.next();
|
||||||
var count = 0;
|
var count = 0;
|
||||||
while (contact !== undefined) {
|
while (contact !== undefined) {
|
||||||
count++;
|
count++;
|
||||||
assert.strictEqual(contact.name, "Zero Cool");
|
assert.strictEqual(contact.name, 'Zero Cool');
|
||||||
assert.strictEqual(contact.number, "+10000000000");
|
assert.strictEqual(contact.number, '+10000000000');
|
||||||
assert.strictEqual(contact.avatar.contentType, "image/jpeg");
|
assert.strictEqual(contact.avatar.contentType, 'image/jpeg');
|
||||||
assert.strictEqual(contact.avatar.length, 255);
|
assert.strictEqual(contact.avatar.length, 255);
|
||||||
assert.strictEqual(contact.avatar.data.byteLength, 255);
|
assert.strictEqual(contact.avatar.data.byteLength, 255);
|
||||||
var avatarBytes = new Uint8Array(contact.avatar.data);
|
var avatarBytes = new Uint8Array(contact.avatar.data);
|
||||||
|
@ -50,7 +50,7 @@ describe("ContactBuffer", function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("GroupBuffer", function() {
|
describe('GroupBuffer', function() {
|
||||||
function getTestBuffer() {
|
function getTestBuffer() {
|
||||||
var buffer = new dcodeIO.ByteBuffer();
|
var buffer = new dcodeIO.ByteBuffer();
|
||||||
var avatarBuffer = new dcodeIO.ByteBuffer();
|
var avatarBuffer = new dcodeIO.ByteBuffer();
|
||||||
|
@ -62,9 +62,9 @@ describe("GroupBuffer", function() {
|
||||||
avatarBuffer.offset = 0;
|
avatarBuffer.offset = 0;
|
||||||
var groupInfo = new textsecure.protobuf.GroupDetails({
|
var groupInfo = new textsecure.protobuf.GroupDetails({
|
||||||
id: new Uint8Array([1, 3, 3, 7]).buffer,
|
id: new Uint8Array([1, 3, 3, 7]).buffer,
|
||||||
name: "Hackers",
|
name: 'Hackers',
|
||||||
members: ['cereal', 'burn', 'phreak', 'joey'],
|
members: ['cereal', 'burn', 'phreak', 'joey'],
|
||||||
avatar: { contentType: "image/jpeg", length: avatarLen }
|
avatar: { contentType: 'image/jpeg', length: avatarLen },
|
||||||
});
|
});
|
||||||
var groupInfoBuffer = groupInfo.encode().toArrayBuffer();
|
var groupInfoBuffer = groupInfo.encode().toArrayBuffer();
|
||||||
|
|
||||||
|
@ -79,17 +79,20 @@ describe("GroupBuffer", function() {
|
||||||
return buffer.toArrayBuffer();
|
return buffer.toArrayBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
it("parses an array buffer of groups", function() {
|
it('parses an array buffer of groups', function() {
|
||||||
var arrayBuffer = getTestBuffer();
|
var arrayBuffer = getTestBuffer();
|
||||||
var groupBuffer = new GroupBuffer(arrayBuffer);
|
var groupBuffer = new GroupBuffer(arrayBuffer);
|
||||||
var group = groupBuffer.next();
|
var group = groupBuffer.next();
|
||||||
var count = 0;
|
var count = 0;
|
||||||
while (group !== undefined) {
|
while (group !== undefined) {
|
||||||
count++;
|
count++;
|
||||||
assert.strictEqual(group.name, "Hackers");
|
assert.strictEqual(group.name, 'Hackers');
|
||||||
assertEqualArrayBuffers(group.id.toArrayBuffer(), new Uint8Array([1,3,3,7]).buffer);
|
assertEqualArrayBuffers(
|
||||||
|
group.id.toArrayBuffer(),
|
||||||
|
new Uint8Array([1, 3, 3, 7]).buffer
|
||||||
|
);
|
||||||
assert.sameMembers(group.members, ['cereal', 'burn', 'phreak', 'joey']);
|
assert.sameMembers(group.members, ['cereal', 'burn', 'phreak', 'joey']);
|
||||||
assert.strictEqual(group.avatar.contentType, "image/jpeg");
|
assert.strictEqual(group.avatar.contentType, 'image/jpeg');
|
||||||
assert.strictEqual(group.avatar.length, 255);
|
assert.strictEqual(group.avatar.length, 255);
|
||||||
assert.strictEqual(group.avatar.data.byteLength, 255);
|
assert.strictEqual(group.avatar.data.byteLength, 255);
|
||||||
var avatarBytes = new Uint8Array(group.avatar.data);
|
var avatarBytes = new Uint8Array(group.avatar.data);
|
||||||
|
|
|
@ -6,10 +6,17 @@ describe('encrypting and decrypting profile data', function() {
|
||||||
var buffer = dcodeIO.ByteBuffer.wrap(name).toArrayBuffer();
|
var buffer = dcodeIO.ByteBuffer.wrap(name).toArrayBuffer();
|
||||||
var key = libsignal.crypto.getRandomBytes(32);
|
var key = libsignal.crypto.getRandomBytes(32);
|
||||||
|
|
||||||
return textsecure.crypto.encryptProfileName(buffer, key).then(function(encrypted) {
|
return textsecure.crypto
|
||||||
|
.encryptProfileName(buffer, key)
|
||||||
|
.then(function(encrypted) {
|
||||||
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
|
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
|
||||||
return textsecure.crypto.decryptProfileName(encrypted, key).then(function(decrypted) {
|
return textsecure.crypto
|
||||||
assert.strictEqual(dcodeIO.ByteBuffer.wrap(decrypted).toString('utf8'), 'Alice');
|
.decryptProfileName(encrypted, key)
|
||||||
|
.then(function(decrypted) {
|
||||||
|
assert.strictEqual(
|
||||||
|
dcodeIO.ByteBuffer.wrap(decrypted).toString('utf8'),
|
||||||
|
'Alice'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -17,11 +24,18 @@ describe('encrypting and decrypting profile data', function() {
|
||||||
var name = dcodeIO.ByteBuffer.wrap('').toArrayBuffer();
|
var name = dcodeIO.ByteBuffer.wrap('').toArrayBuffer();
|
||||||
var key = libsignal.crypto.getRandomBytes(32);
|
var key = libsignal.crypto.getRandomBytes(32);
|
||||||
|
|
||||||
return textsecure.crypto.encryptProfileName(name.buffer, key).then(function(encrypted) {
|
return textsecure.crypto
|
||||||
|
.encryptProfileName(name.buffer, key)
|
||||||
|
.then(function(encrypted) {
|
||||||
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
|
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
|
||||||
return textsecure.crypto.decryptProfileName(encrypted, key).then(function(decrypted) {
|
return textsecure.crypto
|
||||||
|
.decryptProfileName(encrypted, key)
|
||||||
|
.then(function(decrypted) {
|
||||||
assert.strictEqual(decrypted.byteLength, 0);
|
assert.strictEqual(decrypted.byteLength, 0);
|
||||||
assert.strictEqual(dcodeIO.ByteBuffer.wrap(decrypted).toString('utf8'), '');
|
assert.strictEqual(
|
||||||
|
dcodeIO.ByteBuffer.wrap(decrypted).toString('utf8'),
|
||||||
|
''
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -31,10 +45,14 @@ describe('encrypting and decrypting profile data', function() {
|
||||||
var buffer = dcodeIO.ByteBuffer.wrap('This is an avatar').toArrayBuffer();
|
var buffer = dcodeIO.ByteBuffer.wrap('This is an avatar').toArrayBuffer();
|
||||||
var key = libsignal.crypto.getRandomBytes(32);
|
var key = libsignal.crypto.getRandomBytes(32);
|
||||||
|
|
||||||
return textsecure.crypto.encryptProfile(buffer, key).then(function(encrypted) {
|
return textsecure.crypto
|
||||||
|
.encryptProfile(buffer, key)
|
||||||
|
.then(function(encrypted) {
|
||||||
assert(encrypted.byteLength === buffer.byteLength + 16 + 12);
|
assert(encrypted.byteLength === buffer.byteLength + 16 + 12);
|
||||||
return textsecure.crypto.decryptProfile(encrypted, key).then(function(decrypted) {
|
return textsecure.crypto
|
||||||
assertEqualArrayBuffers(buffer, decrypted)
|
.decryptProfile(encrypted, key)
|
||||||
|
.then(function(decrypted) {
|
||||||
|
assertEqualArrayBuffers(buffer, decrypted);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -43,9 +61,13 @@ describe('encrypting and decrypting profile data', function() {
|
||||||
var key = libsignal.crypto.getRandomBytes(32);
|
var key = libsignal.crypto.getRandomBytes(32);
|
||||||
var bad_key = libsignal.crypto.getRandomBytes(32);
|
var bad_key = libsignal.crypto.getRandomBytes(32);
|
||||||
|
|
||||||
return textsecure.crypto.encryptProfile(buffer, key).then(function(encrypted) {
|
return textsecure.crypto
|
||||||
|
.encryptProfile(buffer, key)
|
||||||
|
.then(function(encrypted) {
|
||||||
assert(encrypted.byteLength === buffer.byteLength + 16 + 12);
|
assert(encrypted.byteLength === buffer.byteLength + 16 + 12);
|
||||||
return textsecure.crypto.decryptProfile(encrypted, bad_key).catch(function(error) {
|
return textsecure.crypto
|
||||||
|
.decryptProfile(encrypted, bad_key)
|
||||||
|
.catch(function(error) {
|
||||||
assert.strictEqual(error.name, 'ProfileDecryptError');
|
assert.strictEqual(error.name, 'ProfileDecryptError');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,23 +4,26 @@ TextSecureServer.getKeysForNumber = function(number, deviceId) {
|
||||||
if (res !== undefined) {
|
if (res !== undefined) {
|
||||||
delete getKeysForNumberMap[number];
|
delete getKeysForNumberMap[number];
|
||||||
return Promise.resolve(res);
|
return Promise.resolve(res);
|
||||||
} else
|
} else throw new Error('getKeysForNumber of unknown/used number');
|
||||||
throw new Error("getKeysForNumber of unknown/used number");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var messagesSentMap = {};
|
var messagesSentMap = {};
|
||||||
TextSecureServer.sendMessages = function(destination, messageArray) {
|
TextSecureServer.sendMessages = function(destination, messageArray) {
|
||||||
for (i in messageArray) {
|
for (i in messageArray) {
|
||||||
var msg = messageArray[i];
|
var msg = messageArray[i];
|
||||||
if ((msg.type != 1 && msg.type != 3) ||
|
if (
|
||||||
|
(msg.type != 1 && msg.type != 3) ||
|
||||||
msg.destinationDeviceId === undefined ||
|
msg.destinationDeviceId === undefined ||
|
||||||
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)
|
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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
describe("Key generation", function() {
|
describe('Key generation', function() {
|
||||||
var count = 10;
|
var count = 10;
|
||||||
this.timeout(count * 2000);
|
this.timeout(count * 2000);
|
||||||
|
|
||||||
|
@ -15,34 +15,46 @@ describe("Key generation", function() {
|
||||||
}
|
}
|
||||||
function itStoresPreKey(keyId) {
|
function itStoresPreKey(keyId) {
|
||||||
it('prekey ' + keyId + ' is valid', function(done) {
|
it('prekey ' + keyId + ' is valid', function(done) {
|
||||||
return textsecure.storage.protocol.loadPreKey(keyId).then(function(keyPair) {
|
return textsecure.storage.protocol
|
||||||
|
.loadPreKey(keyId)
|
||||||
|
.then(function(keyPair) {
|
||||||
validateStoredKeyPair(keyPair);
|
validateStoredKeyPair(keyPair);
|
||||||
}).then(done,done);
|
})
|
||||||
|
.then(done, done);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function itStoresSignedPreKey(keyId) {
|
function itStoresSignedPreKey(keyId) {
|
||||||
it('signed prekey ' + keyId + ' is valid', function(done) {
|
it('signed prekey ' + keyId + ' is valid', function(done) {
|
||||||
return textsecure.storage.protocol.loadSignedPreKey(keyId).then(function(keyPair) {
|
return textsecure.storage.protocol
|
||||||
|
.loadSignedPreKey(keyId)
|
||||||
|
.then(function(keyPair) {
|
||||||
validateStoredKeyPair(keyPair);
|
validateStoredKeyPair(keyPair);
|
||||||
}).then(done,done);
|
})
|
||||||
|
.then(done, done);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function validateResultKey(resultKey) {
|
function validateResultKey(resultKey) {
|
||||||
return textsecure.storage.protocol.loadPreKey(resultKey.keyId).then(function(keyPair) {
|
return textsecure.storage.protocol
|
||||||
|
.loadPreKey(resultKey.keyId)
|
||||||
|
.then(function(keyPair) {
|
||||||
assertEqualArrayBuffers(resultKey.publicKey, keyPair.pubKey);
|
assertEqualArrayBuffers(resultKey.publicKey, keyPair.pubKey);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function validateResultSignedKey(resultSignedKey) {
|
function validateResultSignedKey(resultSignedKey) {
|
||||||
return textsecure.storage.protocol.loadSignedPreKey(resultSignedKey.keyId).then(function(keyPair) {
|
return textsecure.storage.protocol
|
||||||
|
.loadSignedPreKey(resultSignedKey.keyId)
|
||||||
|
.then(function(keyPair) {
|
||||||
assertEqualArrayBuffers(resultSignedKey.publicKey, keyPair.pubKey);
|
assertEqualArrayBuffers(resultSignedKey.publicKey, keyPair.pubKey);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
before(function(done) {
|
before(function(done) {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
libsignal.KeyHelper.generateIdentityKeyPair().then(function(keyPair) {
|
libsignal.KeyHelper.generateIdentityKeyPair()
|
||||||
|
.then(function(keyPair) {
|
||||||
return textsecure.storage.protocol.put('identityKey', keyPair);
|
return textsecure.storage.protocol.put('identityKey', keyPair);
|
||||||
}).then(done, done);
|
})
|
||||||
|
.then(done, done);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('the first time', function() {
|
describe('the first time', function() {
|
||||||
|
@ -56,9 +68,12 @@ describe("Key generation", function() {
|
||||||
*/
|
*/
|
||||||
before(function(done) {
|
before(function(done) {
|
||||||
var accountManager = new textsecure.AccountManager('');
|
var accountManager = new textsecure.AccountManager('');
|
||||||
accountManager.generateKeys(count).then(function(res) {
|
accountManager
|
||||||
|
.generateKeys(count)
|
||||||
|
.then(function(res) {
|
||||||
result = res;
|
result = res;
|
||||||
}).then(done,done);
|
})
|
||||||
|
.then(done, done);
|
||||||
});
|
});
|
||||||
for (var i = 1; i <= count; i++) {
|
for (var i = 1; i <= count; i++) {
|
||||||
itStoresPreKey(i);
|
itStoresPreKey(i);
|
||||||
|
@ -78,9 +93,11 @@ describe("Key generation", function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('result contains the correct public keys', function(done) {
|
it('result contains the correct public keys', function(done) {
|
||||||
Promise.all(result.preKeys.map(validateResultKey)).then(function() {
|
Promise.all(result.preKeys.map(validateResultKey))
|
||||||
|
.then(function() {
|
||||||
done();
|
done();
|
||||||
}).catch(done);
|
})
|
||||||
|
.catch(done);
|
||||||
});
|
});
|
||||||
it('returns a signed prekey', function(done) {
|
it('returns a signed prekey', function(done) {
|
||||||
assert.strictEqual(result.signedPreKey.keyId, 1);
|
assert.strictEqual(result.signedPreKey.keyId, 1);
|
||||||
|
@ -92,9 +109,12 @@ describe("Key generation", function() {
|
||||||
var result;
|
var result;
|
||||||
before(function(done) {
|
before(function(done) {
|
||||||
var accountManager = new textsecure.AccountManager('');
|
var accountManager = new textsecure.AccountManager('');
|
||||||
accountManager.generateKeys(count).then(function(res) {
|
accountManager
|
||||||
|
.generateKeys(count)
|
||||||
|
.then(function(res) {
|
||||||
result = res;
|
result = res;
|
||||||
}).then(done,done);
|
})
|
||||||
|
.then(done, done);
|
||||||
});
|
});
|
||||||
for (var i = 1; i <= 2 * count; i++) {
|
for (var i = 1; i <= 2 * count; i++) {
|
||||||
itStoresPreKey(i);
|
itStoresPreKey(i);
|
||||||
|
@ -114,9 +134,11 @@ describe("Key generation", function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('result contains the correct public keys', function(done) {
|
it('result contains the correct public keys', function(done) {
|
||||||
Promise.all(result.preKeys.map(validateResultKey)).then(function() {
|
Promise.all(result.preKeys.map(validateResultKey))
|
||||||
|
.then(function() {
|
||||||
done();
|
done();
|
||||||
}).catch(done);
|
})
|
||||||
|
.catch(done);
|
||||||
});
|
});
|
||||||
it('returns a signed prekey', function(done) {
|
it('returns a signed prekey', function(done) {
|
||||||
assert.strictEqual(result.signedPreKey.keyId, 2);
|
assert.strictEqual(result.signedPreKey.keyId, 2);
|
||||||
|
@ -128,9 +150,12 @@ describe("Key generation", function() {
|
||||||
var result;
|
var result;
|
||||||
before(function(done) {
|
before(function(done) {
|
||||||
var accountManager = new textsecure.AccountManager('');
|
var accountManager = new textsecure.AccountManager('');
|
||||||
accountManager.generateKeys(count).then(function(res) {
|
accountManager
|
||||||
|
.generateKeys(count)
|
||||||
|
.then(function(res) {
|
||||||
result = res;
|
result = res;
|
||||||
}).then(done,done);
|
})
|
||||||
|
.then(done, done);
|
||||||
});
|
});
|
||||||
for (var i = 1; i <= 3 * count; i++) {
|
for (var i = 1; i <= 3 * count; i++) {
|
||||||
itStoresPreKey(i);
|
itStoresPreKey(i);
|
||||||
|
@ -150,9 +175,11 @@ describe("Key generation", function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('result contains the correct public keys', function(done) {
|
it('result contains the correct public keys', function(done) {
|
||||||
Promise.all(result.preKeys.map(validateResultKey)).then(function() {
|
Promise.all(result.preKeys.map(validateResultKey))
|
||||||
|
.then(function() {
|
||||||
done();
|
done();
|
||||||
}).catch(done);
|
})
|
||||||
|
.catch(done);
|
||||||
});
|
});
|
||||||
it('result contains a signed prekey', function(done) {
|
it('result contains a signed prekey', function(done) {
|
||||||
assert.strictEqual(result.signedPreKey.keyId, 3);
|
assert.strictEqual(result.signedPreKey.keyId, 3);
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
describe("Helpers", function() {
|
describe('Helpers', function() {
|
||||||
describe("ArrayBuffer->String conversion", function() {
|
describe('ArrayBuffer->String conversion', function() {
|
||||||
it('works', function() {
|
it('works', function() {
|
||||||
var b = new ArrayBuffer(3);
|
var b = new ArrayBuffer(3);
|
||||||
var a = new Uint8Array(b);
|
var a = new Uint8Array(b);
|
||||||
a[0] = 0;
|
a[0] = 0;
|
||||||
a[1] = 255;
|
a[1] = 255;
|
||||||
a[2] = 128;
|
a[2] = 128;
|
||||||
assert.equal(getString(b), "\x00\xff\x80");
|
assert.equal(getString(b), '\x00\xff\x80');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("stringToArrayBuffer", function() {
|
describe('stringToArrayBuffer', function() {
|
||||||
it('returns ArrayBuffer when passed string', function() {
|
it('returns ArrayBuffer when passed string', function() {
|
||||||
var StaticArrayBufferProto = new ArrayBuffer().__proto__;
|
var StaticArrayBufferProto = new ArrayBuffer().__proto__;
|
||||||
var anArrayBuffer = new ArrayBuffer(1);
|
var anArrayBuffer = new ArrayBuffer(1);
|
||||||
|
@ -23,7 +23,9 @@ describe("Helpers", function() {
|
||||||
it('throws an error when passed a non string', function() {
|
it('throws an error when passed a non string', function() {
|
||||||
var notStringable = [{}, undefined, null, new ArrayBuffer()];
|
var notStringable = [{}, undefined, null, new ArrayBuffer()];
|
||||||
notStringable.forEach(function(notString) {
|
notStringable.forEach(function(notString) {
|
||||||
assert.throw(function() { stringToArrayBuffer(notString) }, Error);
|
assert.throw(function() {
|
||||||
|
stringToArrayBuffer(notString);
|
||||||
|
}, Error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,13 +11,18 @@ SignalProtocolStore.prototype = {
|
||||||
return Promise.resolve(this.get('registrationId'));
|
return Promise.resolve(this.get('registrationId'));
|
||||||
},
|
},
|
||||||
put: function(key, value) {
|
put: function(key, value) {
|
||||||
if (key === undefined || value === undefined || key === null || value === null)
|
if (
|
||||||
throw new Error("Tried to store undefined/null");
|
key === undefined ||
|
||||||
|
value === undefined ||
|
||||||
|
key === null ||
|
||||||
|
value === null
|
||||||
|
)
|
||||||
|
throw new Error('Tried to store undefined/null');
|
||||||
this.store[key] = value;
|
this.store[key] = value;
|
||||||
},
|
},
|
||||||
get: function(key, defaultValue) {
|
get: function(key, defaultValue) {
|
||||||
if (key === null || key === undefined)
|
if (key === null || key === undefined)
|
||||||
throw new Error("Tried to get value for undefined/null key");
|
throw new Error('Tried to get value for undefined/null key');
|
||||||
if (key in this.store) {
|
if (key in this.store) {
|
||||||
return this.store[key];
|
return this.store[key];
|
||||||
} else {
|
} else {
|
||||||
|
@ -26,16 +31,16 @@ SignalProtocolStore.prototype = {
|
||||||
},
|
},
|
||||||
remove: function(key) {
|
remove: function(key) {
|
||||||
if (key === null || key === undefined)
|
if (key === null || key === undefined)
|
||||||
throw new Error("Tried to remove value for undefined/null key");
|
throw new Error('Tried to remove value for undefined/null key');
|
||||||
delete this.store[key];
|
delete this.store[key];
|
||||||
},
|
},
|
||||||
|
|
||||||
isTrustedIdentity: function(identifier, identityKey) {
|
isTrustedIdentity: function(identifier, identityKey) {
|
||||||
if (identifier === null || identifier === undefined) {
|
if (identifier === null || identifier === undefined) {
|
||||||
throw new error("tried to check identity key for undefined/null key");
|
throw new error('tried to check identity key for undefined/null key');
|
||||||
}
|
}
|
||||||
if (!(identityKey instanceof ArrayBuffer)) {
|
if (!(identityKey instanceof ArrayBuffer)) {
|
||||||
throw new error("Expected identityKey to be an ArrayBuffer");
|
throw new error('Expected identityKey to be an ArrayBuffer');
|
||||||
}
|
}
|
||||||
var trusted = this.get('identityKey' + identifier);
|
var trusted = this.get('identityKey' + identifier);
|
||||||
if (trusted === undefined) {
|
if (trusted === undefined) {
|
||||||
|
@ -45,15 +50,18 @@ SignalProtocolStore.prototype = {
|
||||||
},
|
},
|
||||||
loadIdentityKey: function(identifier) {
|
loadIdentityKey: function(identifier) {
|
||||||
if (identifier === null || identifier === undefined)
|
if (identifier === null || identifier === undefined)
|
||||||
throw new Error("Tried to get identity key for undefined/null key");
|
throw new Error('Tried to get identity key for undefined/null key');
|
||||||
return new Promise(function(resolve) {
|
return new Promise(
|
||||||
|
function(resolve) {
|
||||||
resolve(this.get('identityKey' + identifier));
|
resolve(this.get('identityKey' + identifier));
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
saveIdentity: function(identifier, identityKey) {
|
saveIdentity: function(identifier, identityKey) {
|
||||||
if (identifier === null || identifier === undefined)
|
if (identifier === null || identifier === undefined)
|
||||||
throw new Error("Tried to put identity key for undefined/null key");
|
throw new Error('Tried to put identity key for undefined/null key');
|
||||||
return new Promise(function(resolve) {
|
return new Promise(
|
||||||
|
function(resolve) {
|
||||||
var existing = this.get('identityKey' + identifier);
|
var existing = this.get('identityKey' + identifier);
|
||||||
this.put('identityKey' + identifier, identityKey);
|
this.put('identityKey' + identifier, identityKey);
|
||||||
if (existing && existing !== identityKey) {
|
if (existing && existing !== identityKey) {
|
||||||
|
@ -61,36 +69,46 @@ SignalProtocolStore.prototype = {
|
||||||
} else {
|
} else {
|
||||||
resolve(false);
|
resolve(false);
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Returns a prekeypair object or undefined */
|
/* Returns a prekeypair object or undefined */
|
||||||
loadPreKey: function(keyId) {
|
loadPreKey: function(keyId) {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(
|
||||||
|
function(resolve) {
|
||||||
var res = this.get('25519KeypreKey' + keyId);
|
var res = this.get('25519KeypreKey' + keyId);
|
||||||
resolve(res);
|
resolve(res);
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
storePreKey: function(keyId, keyPair) {
|
storePreKey: function(keyId, keyPair) {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(
|
||||||
|
function(resolve) {
|
||||||
resolve(this.put('25519KeypreKey' + keyId, keyPair));
|
resolve(this.put('25519KeypreKey' + keyId, keyPair));
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
removePreKey: function(keyId) {
|
removePreKey: function(keyId) {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(
|
||||||
|
function(resolve) {
|
||||||
resolve(this.remove('25519KeypreKey' + keyId));
|
resolve(this.remove('25519KeypreKey' + keyId));
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Returns a signed keypair object or undefined */
|
/* Returns a signed keypair object or undefined */
|
||||||
loadSignedPreKey: function(keyId) {
|
loadSignedPreKey: function(keyId) {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(
|
||||||
|
function(resolve) {
|
||||||
var res = this.get('25519KeysignedKey' + keyId);
|
var res = this.get('25519KeysignedKey' + keyId);
|
||||||
resolve(res);
|
resolve(res);
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
loadSignedPreKeys: function() {
|
loadSignedPreKeys: function() {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(
|
||||||
|
function(resolve) {
|
||||||
var res = [];
|
var res = [];
|
||||||
for (var i in this.store) {
|
for (var i in this.store) {
|
||||||
if (i.startsWith('25519KeysignedKey')) {
|
if (i.startsWith('25519KeysignedKey')) {
|
||||||
|
@ -98,48 +116,69 @@ SignalProtocolStore.prototype = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resolve(res);
|
resolve(res);
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
storeSignedPreKey: function(keyId, keyPair) {
|
storeSignedPreKey: function(keyId, keyPair) {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(
|
||||||
|
function(resolve) {
|
||||||
resolve(this.put('25519KeysignedKey' + keyId, keyPair));
|
resolve(this.put('25519KeysignedKey' + keyId, keyPair));
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
removeSignedPreKey: function(keyId) {
|
removeSignedPreKey: function(keyId) {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(
|
||||||
|
function(resolve) {
|
||||||
resolve(this.remove('25519KeysignedKey' + keyId));
|
resolve(this.remove('25519KeysignedKey' + keyId));
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
loadSession: function(identifier) {
|
loadSession: function(identifier) {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(
|
||||||
|
function(resolve) {
|
||||||
resolve(this.get('session' + identifier));
|
resolve(this.get('session' + identifier));
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
storeSession: function(identifier, record) {
|
storeSession: function(identifier, record) {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(
|
||||||
|
function(resolve) {
|
||||||
resolve(this.put('session' + identifier, record));
|
resolve(this.put('session' + identifier, record));
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
removeAllSessions: function(identifier) {
|
removeAllSessions: function(identifier) {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(
|
||||||
|
function(resolve) {
|
||||||
for (key in this.store) {
|
for (key in this.store) {
|
||||||
if (key.match(RegExp('^session' + identifier.replace('\+','\\\+') + '.+'))) {
|
if (
|
||||||
|
key.match(
|
||||||
|
RegExp('^session' + identifier.replace('+', '\\+') + '.+')
|
||||||
|
)
|
||||||
|
) {
|
||||||
delete this.store[key];
|
delete this.store[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
getDeviceIds: function(identifier) {
|
getDeviceIds: function(identifier) {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(
|
||||||
|
function(resolve) {
|
||||||
var deviceIds = [];
|
var deviceIds = [];
|
||||||
for (key in this.store) {
|
for (key in this.store) {
|
||||||
if (key.match(RegExp('^session' + identifier.replace('\+','\\\+') + '.+'))) {
|
if (
|
||||||
|
key.match(
|
||||||
|
RegExp('^session' + identifier.replace('+', '\\+') + '.+')
|
||||||
|
)
|
||||||
|
) {
|
||||||
deviceIds.push(parseInt(key.split('.')[1]));
|
deviceIds.push(parseInt(key.split('.')[1]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resolve(deviceIds);
|
resolve(deviceIds);
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
}
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,10 +7,12 @@ describe('MessageReceiver', function() {
|
||||||
before(function() {
|
before(function() {
|
||||||
window.WebSocket = MockSocket;
|
window.WebSocket = MockSocket;
|
||||||
textsecure.storage.user.setNumberAndDeviceId(number, deviceId, 'name');
|
textsecure.storage.user.setNumberAndDeviceId(number, deviceId, 'name');
|
||||||
textsecure.storage.put("password", "password");
|
textsecure.storage.put('password', 'password');
|
||||||
textsecure.storage.put("signaling_key", signalingKey);
|
textsecure.storage.put('signaling_key', signalingKey);
|
||||||
|
});
|
||||||
|
after(function() {
|
||||||
|
window.WebSocket = WebSocket;
|
||||||
});
|
});
|
||||||
after (function() { window.WebSocket = WebSocket; });
|
|
||||||
|
|
||||||
describe('connecting', function() {
|
describe('connecting', function() {
|
||||||
var blob = null;
|
var blob = null;
|
||||||
|
@ -22,7 +24,7 @@ describe('MessageReceiver', function() {
|
||||||
};
|
};
|
||||||
var websocketmessage = new textsecure.protobuf.WebSocketMessage({
|
var websocketmessage = new textsecure.protobuf.WebSocketMessage({
|
||||||
type: textsecure.protobuf.WebSocketMessage.Type.REQUEST,
|
type: textsecure.protobuf.WebSocketMessage.Type.REQUEST,
|
||||||
request: { verb: 'PUT', path: '/messages' }
|
request: { verb: 'PUT', path: '/messages' },
|
||||||
});
|
});
|
||||||
|
|
||||||
before(function(done) {
|
before(function(done) {
|
||||||
|
@ -33,13 +35,32 @@ describe('MessageReceiver', function() {
|
||||||
var aes_key = signaling_key.slice(0, 32);
|
var aes_key = signaling_key.slice(0, 32);
|
||||||
var mac_key = signaling_key.slice(32, 32 + 20);
|
var mac_key = signaling_key.slice(32, 32 + 20);
|
||||||
|
|
||||||
window.crypto.subtle.importKey('raw', aes_key, {name: 'AES-CBC'}, false, ['encrypt']).then(function(key) {
|
window.crypto.subtle
|
||||||
|
.importKey('raw', aes_key, { name: 'AES-CBC' }, false, ['encrypt'])
|
||||||
|
.then(function(key) {
|
||||||
var iv = libsignal.crypto.getRandomBytes(16);
|
var iv = libsignal.crypto.getRandomBytes(16);
|
||||||
window.crypto.subtle.encrypt({name: 'AES-CBC', iv: new Uint8Array(iv)}, key, signal).then(function(ciphertext) {
|
window.crypto.subtle
|
||||||
window.crypto.subtle.importKey('raw', mac_key, {name: 'HMAC', hash: {name: 'SHA-256'}}, false, ['sign']).then(function(key) {
|
.encrypt({ name: 'AES-CBC', iv: new Uint8Array(iv) }, key, signal)
|
||||||
window.crypto.subtle.sign( {name: 'HMAC', hash: 'SHA-256'}, key, signal).then(function(mac) {
|
.then(function(ciphertext) {
|
||||||
|
window.crypto.subtle
|
||||||
|
.importKey(
|
||||||
|
'raw',
|
||||||
|
mac_key,
|
||||||
|
{ name: 'HMAC', hash: { name: 'SHA-256' } },
|
||||||
|
false,
|
||||||
|
['sign']
|
||||||
|
)
|
||||||
|
.then(function(key) {
|
||||||
|
window.crypto.subtle
|
||||||
|
.sign({ name: 'HMAC', hash: 'SHA-256' }, key, signal)
|
||||||
|
.then(function(mac) {
|
||||||
var version = new Uint8Array([1]);
|
var version = new Uint8Array([1]);
|
||||||
var message = dcodeIO.ByteBuffer.concat([version, iv, ciphertext, mac ]);
|
var message = dcodeIO.ByteBuffer.concat([
|
||||||
|
version,
|
||||||
|
iv,
|
||||||
|
ciphertext,
|
||||||
|
mac,
|
||||||
|
]);
|
||||||
websocketmessage.request.body = message.toArrayBuffer();
|
websocketmessage.request.body = message.toArrayBuffer();
|
||||||
console.log(new Uint8Array(message.toArrayBuffer()));
|
console.log(new Uint8Array(message.toArrayBuffer()));
|
||||||
done();
|
done();
|
||||||
|
@ -50,7 +71,11 @@ describe('MessageReceiver', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('connects', function(done) {
|
it('connects', function(done) {
|
||||||
var mockServer = new MockServer('ws://localhost:8080/v1/websocket/?login='+ encodeURIComponent(number) +'.1&password=password');
|
var mockServer = new MockServer(
|
||||||
|
'ws://localhost:8080/v1/websocket/?login=' +
|
||||||
|
encodeURIComponent(number) +
|
||||||
|
'.1&password=password'
|
||||||
|
);
|
||||||
|
|
||||||
mockServer.on('connection', function(server) {
|
mockServer.on('connection', function(server) {
|
||||||
server.send(new Blob([websocketmessage.toArrayBuffer()]));
|
server.send(new Blob([websocketmessage.toArrayBuffer()]));
|
||||||
|
@ -65,7 +90,10 @@ describe('MessageReceiver', function() {
|
||||||
server.close();
|
server.close();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
var messageReceiver = new textsecure.MessageReceiver('ws://localhost:8080', window);
|
var messageReceiver = new textsecure.MessageReceiver(
|
||||||
|
'ws://localhost:8080',
|
||||||
|
window
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,30 +1,36 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
describe('Protocol', function() {
|
describe('Protocol', function() {
|
||||||
|
|
||||||
describe('Unencrypted PushMessageProto "decrypt"', function() {
|
describe('Unencrypted PushMessageProto "decrypt"', function() {
|
||||||
//exclusive
|
//exclusive
|
||||||
it('works', function(done) {
|
it('works', function(done) {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
|
|
||||||
var text_message = new textsecure.protobuf.DataMessage();
|
var text_message = new textsecure.protobuf.DataMessage();
|
||||||
text_message.body = "Hi Mom";
|
text_message.body = 'Hi Mom';
|
||||||
var server_message = {
|
var server_message = {
|
||||||
type: 4, // unencrypted
|
type: 4, // unencrypted
|
||||||
source: "+19999999999",
|
source: '+19999999999',
|
||||||
timestamp: 42,
|
timestamp: 42,
|
||||||
message: text_message.encode()
|
message: text_message.encode(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return textsecure.protocol_wrapper.handleEncryptedMessage(
|
return textsecure.protocol_wrapper
|
||||||
|
.handleEncryptedMessage(
|
||||||
server_message.source,
|
server_message.source,
|
||||||
server_message.source_device,
|
server_message.source_device,
|
||||||
server_message.type,
|
server_message.type,
|
||||||
server_message.message
|
server_message.message
|
||||||
).then(function(message) {
|
)
|
||||||
|
.then(function(message) {
|
||||||
assert.equal(message.body, text_message.body);
|
assert.equal(message.body, text_message.body);
|
||||||
assert.equal(message.attachments.length, text_message.attachments.length);
|
assert.equal(
|
||||||
|
message.attachments.length,
|
||||||
|
text_message.attachments.length
|
||||||
|
);
|
||||||
assert.equal(text_message.attachments.length, 0);
|
assert.equal(text_message.attachments.length, 0);
|
||||||
}).then(done).catch(done);
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,14 @@ describe('Protocol Wrapper', function() {
|
||||||
this.timeout(5000);
|
this.timeout(5000);
|
||||||
before(function(done) {
|
before(function(done) {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
libsignal.KeyHelper.generateIdentityKeyPair().then(function(identityKey) {
|
libsignal.KeyHelper.generateIdentityKeyPair()
|
||||||
return textsecure.storage.protocol.saveIdentity(identifier, identityKey);
|
.then(function(identityKey) {
|
||||||
}).then(function() {
|
return textsecure.storage.protocol.saveIdentity(
|
||||||
|
identifier,
|
||||||
|
identityKey
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -18,12 +23,15 @@ describe('Protocol Wrapper', function() {
|
||||||
it('rejects if the identity key changes', function(done) {
|
it('rejects if the identity key changes', function(done) {
|
||||||
var address = new libsignal.SignalProtocolAddress(identifier, 1);
|
var address = new libsignal.SignalProtocolAddress(identifier, 1);
|
||||||
var builder = new libsignal.SessionBuilder(store, address);
|
var builder = new libsignal.SessionBuilder(store, address);
|
||||||
return builder.processPreKey({
|
return builder
|
||||||
|
.processPreKey({
|
||||||
identityKey: textsecure.crypto.getRandomBytes(33),
|
identityKey: textsecure.crypto.getRandomBytes(33),
|
||||||
encodedNumber: address.toString()
|
encodedNumber: address.toString(),
|
||||||
}).then(function() {
|
})
|
||||||
|
.then(function() {
|
||||||
done(new Error('Allowed to overwrite identity key'));
|
done(new Error('Allowed to overwrite identity key'));
|
||||||
}).catch(function(e) {
|
})
|
||||||
|
.catch(function(e) {
|
||||||
assert.strictEqual(e.message, 'Identity key changed');
|
assert.strictEqual(e.message, 'Identity key changed');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
describe("SignalProtocolStore", function() {
|
describe('SignalProtocolStore', function() {
|
||||||
before(function() { localStorage.clear(); });
|
before(function() {
|
||||||
|
localStorage.clear();
|
||||||
|
});
|
||||||
var store = textsecure.storage.protocol;
|
var store = textsecure.storage.protocol;
|
||||||
var identifier = '+5558675309';
|
var identifier = '+5558675309';
|
||||||
var another_identifier = '+5555590210';
|
var another_identifier = '+5555590210';
|
||||||
|
@ -15,144 +17,184 @@ describe("SignalProtocolStore", function() {
|
||||||
};
|
};
|
||||||
it('retrieves my registration id', function(done) {
|
it('retrieves my registration id', function(done) {
|
||||||
store.put('registrationId', 1337);
|
store.put('registrationId', 1337);
|
||||||
store.getLocalRegistrationId().then(function(reg) {
|
store
|
||||||
|
.getLocalRegistrationId()
|
||||||
|
.then(function(reg) {
|
||||||
assert.strictEqual(reg, 1337);
|
assert.strictEqual(reg, 1337);
|
||||||
}).then(done, done);
|
})
|
||||||
|
.then(done, done);
|
||||||
});
|
});
|
||||||
it('retrieves my identity key', function(done) {
|
it('retrieves my identity key', function(done) {
|
||||||
store.put('identityKey', identityKey);
|
store.put('identityKey', identityKey);
|
||||||
store.getIdentityKeyPair().then(function(key) {
|
store
|
||||||
|
.getIdentityKeyPair()
|
||||||
|
.then(function(key) {
|
||||||
assertEqualArrayBuffers(key.pubKey, identityKey.pubKey);
|
assertEqualArrayBuffers(key.pubKey, identityKey.pubKey);
|
||||||
assertEqualArrayBuffers(key.privKey, identityKey.privKey);
|
assertEqualArrayBuffers(key.privKey, identityKey.privKey);
|
||||||
}).then(done,done);
|
})
|
||||||
|
.then(done, done);
|
||||||
});
|
});
|
||||||
it('stores identity keys', function(done) {
|
it('stores identity keys', function(done) {
|
||||||
store.saveIdentity(identifier, testKey.pubKey).then(function() {
|
store
|
||||||
|
.saveIdentity(identifier, testKey.pubKey)
|
||||||
|
.then(function() {
|
||||||
return store.loadIdentityKey(identifier).then(function(key) {
|
return store.loadIdentityKey(identifier).then(function(key) {
|
||||||
assertEqualArrayBuffers(key, testKey.pubKey);
|
assertEqualArrayBuffers(key, testKey.pubKey);
|
||||||
});
|
});
|
||||||
}).then(done,done);
|
})
|
||||||
|
.then(done, done);
|
||||||
});
|
});
|
||||||
it('returns whether a key is trusted', function(done) {
|
it('returns whether a key is trusted', function(done) {
|
||||||
var newIdentity = libsignal.crypto.getRandomBytes(33);
|
var newIdentity = libsignal.crypto.getRandomBytes(33);
|
||||||
store.saveIdentity(identifier, testKey.pubKey).then(function() {
|
store.saveIdentity(identifier, testKey.pubKey).then(function() {
|
||||||
store.isTrustedIdentity(identifier, newIdentity).then(function(trusted) {
|
store
|
||||||
|
.isTrustedIdentity(identifier, newIdentity)
|
||||||
|
.then(function(trusted) {
|
||||||
if (trusted) {
|
if (trusted) {
|
||||||
done(new Error('Allowed to overwrite identity key'));
|
done(new Error('Allowed to overwrite identity key'));
|
||||||
} else {
|
} else {
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
}).catch(done);
|
})
|
||||||
|
.catch(done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('returns whether a key is untrusted', function(done) {
|
it('returns whether a key is untrusted', function(done) {
|
||||||
var newIdentity = libsignal.crypto.getRandomBytes(33);
|
var newIdentity = libsignal.crypto.getRandomBytes(33);
|
||||||
store.saveIdentity(identifier, testKey.pubKey).then(function() {
|
store.saveIdentity(identifier, testKey.pubKey).then(function() {
|
||||||
store.isTrustedIdentity(identifier, testKey.pubKey).then(function(trusted) {
|
store
|
||||||
|
.isTrustedIdentity(identifier, testKey.pubKey)
|
||||||
|
.then(function(trusted) {
|
||||||
if (trusted) {
|
if (trusted) {
|
||||||
done();
|
done();
|
||||||
} else {
|
} else {
|
||||||
done(new Error('Allowed to overwrite identity key'));
|
done(new Error('Allowed to overwrite identity key'));
|
||||||
}
|
}
|
||||||
}).catch(done);
|
})
|
||||||
|
.catch(done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('stores prekeys', function(done) {
|
it('stores prekeys', function(done) {
|
||||||
store.storePreKey(1, testKey).then(function() {
|
store
|
||||||
|
.storePreKey(1, testKey)
|
||||||
|
.then(function() {
|
||||||
return store.loadPreKey(1).then(function(key) {
|
return store.loadPreKey(1).then(function(key) {
|
||||||
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
|
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
|
||||||
assertEqualArrayBuffers(key.privKey, testKey.privKey);
|
assertEqualArrayBuffers(key.privKey, testKey.privKey);
|
||||||
});
|
});
|
||||||
}).then(done,done);
|
})
|
||||||
|
.then(done, done);
|
||||||
});
|
});
|
||||||
it('deletes prekeys', function(done) {
|
it('deletes prekeys', function(done) {
|
||||||
before(function(done) {
|
before(function(done) {
|
||||||
store.storePreKey(2, testKey).then(done);
|
store.storePreKey(2, testKey).then(done);
|
||||||
});
|
});
|
||||||
store.removePreKey(2, testKey).then(function() {
|
store
|
||||||
|
.removePreKey(2, testKey)
|
||||||
|
.then(function() {
|
||||||
return store.loadPreKey(2).then(function(key) {
|
return store.loadPreKey(2).then(function(key) {
|
||||||
assert.isUndefined(key);
|
assert.isUndefined(key);
|
||||||
});
|
});
|
||||||
}).then(done,done);
|
})
|
||||||
|
.then(done, done);
|
||||||
});
|
});
|
||||||
it('stores signed prekeys', function(done) {
|
it('stores signed prekeys', function(done) {
|
||||||
store.storeSignedPreKey(3, testKey).then(function() {
|
store
|
||||||
|
.storeSignedPreKey(3, testKey)
|
||||||
|
.then(function() {
|
||||||
return store.loadSignedPreKey(3).then(function(key) {
|
return store.loadSignedPreKey(3).then(function(key) {
|
||||||
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
|
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
|
||||||
assertEqualArrayBuffers(key.privKey, testKey.privKey);
|
assertEqualArrayBuffers(key.privKey, testKey.privKey);
|
||||||
});
|
});
|
||||||
}).then(done,done);
|
})
|
||||||
|
.then(done, done);
|
||||||
});
|
});
|
||||||
it('deletes signed prekeys', function(done) {
|
it('deletes signed prekeys', function(done) {
|
||||||
before(function(done) {
|
before(function(done) {
|
||||||
store.storeSignedPreKey(4, testKey).then(done);
|
store.storeSignedPreKey(4, testKey).then(done);
|
||||||
});
|
});
|
||||||
store.removeSignedPreKey(4, testKey).then(function() {
|
store
|
||||||
|
.removeSignedPreKey(4, testKey)
|
||||||
|
.then(function() {
|
||||||
return store.loadSignedPreKey(4).then(function(key) {
|
return store.loadSignedPreKey(4).then(function(key) {
|
||||||
assert.isUndefined(key);
|
assert.isUndefined(key);
|
||||||
});
|
});
|
||||||
}).then(done,done);
|
})
|
||||||
|
.then(done, done);
|
||||||
});
|
});
|
||||||
it('stores sessions', function(done) {
|
it('stores sessions', function(done) {
|
||||||
var testRecord = "an opaque string";
|
var testRecord = 'an opaque string';
|
||||||
var devices = [1, 2, 3].map(function(deviceId) {
|
var devices = [1, 2, 3].map(function(deviceId) {
|
||||||
return [identifier, deviceId].join('.');
|
return [identifier, deviceId].join('.');
|
||||||
});
|
});
|
||||||
var promise = Promise.resolve();
|
var promise = Promise.resolve();
|
||||||
devices.forEach(function(encodedNumber) {
|
devices.forEach(function(encodedNumber) {
|
||||||
promise = promise.then(function() {
|
promise = promise.then(function() {
|
||||||
return store.storeSession(encodedNumber, testRecord + encodedNumber)
|
return store.storeSession(encodedNumber, testRecord + encodedNumber);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
promise.then(function() {
|
promise
|
||||||
return Promise.all(devices.map(store.loadSession.bind(store))).then(function(records) {
|
.then(function() {
|
||||||
|
return Promise.all(devices.map(store.loadSession.bind(store))).then(
|
||||||
|
function(records) {
|
||||||
for (var i in records) {
|
for (var i in records) {
|
||||||
assert.strictEqual(records[i], testRecord + devices[i]);
|
assert.strictEqual(records[i], testRecord + devices[i]);
|
||||||
};
|
}
|
||||||
});
|
}
|
||||||
}).then(done,done);
|
);
|
||||||
|
})
|
||||||
|
.then(done, done);
|
||||||
});
|
});
|
||||||
it('removes all sessions for a number', function(done) {
|
it('removes all sessions for a number', function(done) {
|
||||||
var testRecord = "an opaque string";
|
var testRecord = 'an opaque string';
|
||||||
var devices = [1, 2, 3].map(function(deviceId) {
|
var devices = [1, 2, 3].map(function(deviceId) {
|
||||||
return [identifier, deviceId].join('.');
|
return [identifier, deviceId].join('.');
|
||||||
});
|
});
|
||||||
var promise = Promise.resolve();
|
var promise = Promise.resolve();
|
||||||
devices.forEach(function(encodedNumber) {
|
devices.forEach(function(encodedNumber) {
|
||||||
promise = promise.then(function() {
|
promise = promise.then(function() {
|
||||||
return store.storeSession(encodedNumber, testRecord + encodedNumber)
|
return store.storeSession(encodedNumber, testRecord + encodedNumber);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
promise.then(function() {
|
promise
|
||||||
|
.then(function() {
|
||||||
return store.removeAllSessions(identifier).then(function(record) {
|
return store.removeAllSessions(identifier).then(function(record) {
|
||||||
return Promise.all(devices.map(store.loadSession.bind(store))).then(function(records) {
|
return Promise.all(devices.map(store.loadSession.bind(store))).then(
|
||||||
|
function(records) {
|
||||||
for (var i in records) {
|
for (var i in records) {
|
||||||
assert.isUndefined(records[i]);
|
assert.isUndefined(records[i]);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
})
|
||||||
}).then(done,done);
|
.then(done, done);
|
||||||
});
|
});
|
||||||
it('returns deviceIds for a number', function(done) {
|
it('returns deviceIds for a number', function(done) {
|
||||||
var testRecord = "an opaque string";
|
var testRecord = 'an opaque string';
|
||||||
var devices = [1, 2, 3].map(function(deviceId) {
|
var devices = [1, 2, 3].map(function(deviceId) {
|
||||||
return [identifier, deviceId].join('.');
|
return [identifier, deviceId].join('.');
|
||||||
});
|
});
|
||||||
var promise = Promise.resolve();
|
var promise = Promise.resolve();
|
||||||
devices.forEach(function(encodedNumber) {
|
devices.forEach(function(encodedNumber) {
|
||||||
promise = promise.then(function() {
|
promise = promise.then(function() {
|
||||||
return store.storeSession(encodedNumber, testRecord + encodedNumber)
|
return store.storeSession(encodedNumber, testRecord + encodedNumber);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
promise.then(function() {
|
promise
|
||||||
|
.then(function() {
|
||||||
return store.getDeviceIds(identifier).then(function(deviceIds) {
|
return store.getDeviceIds(identifier).then(function(deviceIds) {
|
||||||
assert.sameMembers(deviceIds, [1, 2, 3]);
|
assert.sameMembers(deviceIds, [1, 2, 3]);
|
||||||
});
|
});
|
||||||
}).then(done,done);
|
})
|
||||||
|
.then(done, done);
|
||||||
});
|
});
|
||||||
it('returns empty array for a number with no device ids', function(done) {
|
it('returns empty array for a number with no device ids', function(done) {
|
||||||
return store.getDeviceIds('foo').then(function(deviceIds) {
|
return store
|
||||||
|
.getDeviceIds('foo')
|
||||||
|
.then(function(deviceIds) {
|
||||||
assert.sameMembers(deviceIds, []);
|
assert.sameMembers(deviceIds, []);
|
||||||
}).then(done,done);
|
})
|
||||||
|
.then(done, done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,7 +8,7 @@ describe('createTaskWithTimeout', function() {
|
||||||
var taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
var taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
||||||
|
|
||||||
return taskWithTimeout().then(function(result) {
|
return taskWithTimeout().then(function(result) {
|
||||||
assert.strictEqual(result, 'hi!')
|
assert.strictEqual(result, 'hi!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('flows error from promise back', function() {
|
it('flows error from promise back', function() {
|
||||||
|
@ -34,14 +34,17 @@ describe('createTaskWithTimeout', function() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
var taskWithTimeout = textsecure.createTaskWithTimeout(task, this.name, {
|
var taskWithTimeout = textsecure.createTaskWithTimeout(task, this.name, {
|
||||||
timeout: 10
|
timeout: 10,
|
||||||
});
|
});
|
||||||
|
|
||||||
return taskWithTimeout().then(function() {
|
return taskWithTimeout().then(
|
||||||
|
function() {
|
||||||
throw new Error('it was not supposed to resolve!');
|
throw new Error('it was not supposed to resolve!');
|
||||||
}, function() {
|
},
|
||||||
|
function() {
|
||||||
assert.strictEqual(complete, false);
|
assert.strictEqual(complete, false);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
it('resolves if task returns something falsey', function() {
|
it('resolves if task returns something falsey', function() {
|
||||||
var task = function() {};
|
var task = function() {};
|
||||||
|
@ -54,7 +57,7 @@ describe('createTaskWithTimeout', function() {
|
||||||
};
|
};
|
||||||
var taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
var taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
||||||
return taskWithTimeout().then(function(result) {
|
return taskWithTimeout().then(function(result) {
|
||||||
assert.strictEqual(result, 'hi!')
|
assert.strictEqual(result, 'hi!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects if task throws (and does not log about taking too long)', function() {
|
it('rejects if task throws (and does not log about taking too long)', function() {
|
||||||
|
@ -63,12 +66,15 @@ describe('createTaskWithTimeout', function() {
|
||||||
throw error;
|
throw error;
|
||||||
};
|
};
|
||||||
var taskWithTimeout = textsecure.createTaskWithTimeout(task, this.name, {
|
var taskWithTimeout = textsecure.createTaskWithTimeout(task, this.name, {
|
||||||
timeout: 10
|
timeout: 10,
|
||||||
});
|
});
|
||||||
return taskWithTimeout().then(function(result) {
|
return taskWithTimeout().then(
|
||||||
throw new Error('Overall task should reject!')
|
function(result) {
|
||||||
}, function(flowedError) {
|
throw new Error('Overall task should reject!');
|
||||||
|
},
|
||||||
|
function(flowedError) {
|
||||||
assert.strictEqual(flowedError, error);
|
assert.strictEqual(flowedError, error);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
;(function() {
|
(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
describe('WebSocket-Resource', function() {
|
describe('WebSocket-Resource', function() {
|
||||||
|
@ -9,7 +9,10 @@
|
||||||
var socket = {
|
var socket = {
|
||||||
send: function(data) {
|
send: function(data) {
|
||||||
var message = textsecure.protobuf.WebSocketMessage.decode(data);
|
var message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||||
assert.strictEqual(message.type, textsecure.protobuf.WebSocketMessage.Type.RESPONSE);
|
assert.strictEqual(
|
||||||
|
message.type,
|
||||||
|
textsecure.protobuf.WebSocketMessage.Type.RESPONSE
|
||||||
|
);
|
||||||
assert.strictEqual(message.response.message, 'OK');
|
assert.strictEqual(message.response.message, 'OK');
|
||||||
assert.strictEqual(message.response.status, 200);
|
assert.strictEqual(message.response.status, 200);
|
||||||
assert.strictEqual(message.response.id.toString(), request_id);
|
assert.strictEqual(message.response.id.toString(), request_id);
|
||||||
|
@ -23,9 +26,12 @@
|
||||||
handleRequest: function(request) {
|
handleRequest: function(request) {
|
||||||
assert.strictEqual(request.verb, 'PUT');
|
assert.strictEqual(request.verb, 'PUT');
|
||||||
assert.strictEqual(request.path, '/some/path');
|
assert.strictEqual(request.path, '/some/path');
|
||||||
assertEqualArrayBuffers(request.body.toArrayBuffer(), new Uint8Array([1,2,3]).buffer);
|
assertEqualArrayBuffers(
|
||||||
|
request.body.toArrayBuffer(),
|
||||||
|
new Uint8Array([1, 2, 3]).buffer
|
||||||
|
);
|
||||||
request.respond(200, 'OK');
|
request.respond(200, 'OK');
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// mock socket request
|
// mock socket request
|
||||||
|
@ -37,10 +43,12 @@
|
||||||
id: request_id,
|
id: request_id,
|
||||||
verb: 'PUT',
|
verb: 'PUT',
|
||||||
path: '/some/path',
|
path: '/some/path',
|
||||||
body: new Uint8Array([1,2,3]).buffer
|
body: new Uint8Array([1, 2, 3]).buffer,
|
||||||
}
|
},
|
||||||
}).encode().toArrayBuffer()
|
})
|
||||||
])
|
.encode()
|
||||||
|
.toArrayBuffer(),
|
||||||
|
]),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -50,10 +58,16 @@
|
||||||
var socket = {
|
var socket = {
|
||||||
send: function(data) {
|
send: function(data) {
|
||||||
var message = textsecure.protobuf.WebSocketMessage.decode(data);
|
var message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||||
assert.strictEqual(message.type, textsecure.protobuf.WebSocketMessage.Type.REQUEST);
|
assert.strictEqual(
|
||||||
|
message.type,
|
||||||
|
textsecure.protobuf.WebSocketMessage.Type.REQUEST
|
||||||
|
);
|
||||||
assert.strictEqual(message.request.verb, 'PUT');
|
assert.strictEqual(message.request.verb, 'PUT');
|
||||||
assert.strictEqual(message.request.path, '/some/path');
|
assert.strictEqual(message.request.path, '/some/path');
|
||||||
assertEqualArrayBuffers(message.request.body.toArrayBuffer(), new Uint8Array([1,2,3]).buffer);
|
assertEqualArrayBuffers(
|
||||||
|
message.request.body.toArrayBuffer(),
|
||||||
|
new Uint8Array([1, 2, 3]).buffer
|
||||||
|
);
|
||||||
request_id = message.request.id;
|
request_id = message.request.id;
|
||||||
},
|
},
|
||||||
addEventListener: function() {},
|
addEventListener: function() {},
|
||||||
|
@ -70,7 +84,7 @@
|
||||||
assert.strictEqual(message, 'OK');
|
assert.strictEqual(message, 'OK');
|
||||||
assert.strictEqual(status, 200);
|
assert.strictEqual(status, 200);
|
||||||
done();
|
done();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// mock socket response
|
// mock socket response
|
||||||
|
@ -78,36 +92,51 @@
|
||||||
data: new Blob([
|
data: new Blob([
|
||||||
new textsecure.protobuf.WebSocketMessage({
|
new textsecure.protobuf.WebSocketMessage({
|
||||||
type: textsecure.protobuf.WebSocketMessage.Type.RESPONSE,
|
type: textsecure.protobuf.WebSocketMessage.Type.RESPONSE,
|
||||||
response: { id: request_id, message: 'OK', status: 200 }
|
response: { id: request_id, message: 'OK', status: 200 },
|
||||||
}).encode().toArrayBuffer()
|
})
|
||||||
])
|
.encode()
|
||||||
|
.toArrayBuffer(),
|
||||||
|
]),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('close', function() {
|
describe('close', function() {
|
||||||
before(function() { window.WebSocket = MockSocket; });
|
before(function() {
|
||||||
after (function() { window.WebSocket = WebSocket; });
|
window.WebSocket = MockSocket;
|
||||||
|
});
|
||||||
|
after(function() {
|
||||||
|
window.WebSocket = WebSocket;
|
||||||
|
});
|
||||||
it('closes the connection', function(done) {
|
it('closes the connection', function(done) {
|
||||||
var mockServer = new MockServer('ws://localhost:8081');
|
var mockServer = new MockServer('ws://localhost:8081');
|
||||||
mockServer.on('connection', function(server) {
|
mockServer.on('connection', function(server) {
|
||||||
server.on('close', done);
|
server.on('close', done);
|
||||||
});
|
});
|
||||||
var resource = new WebSocketResource(new WebSocket('ws://localhost:8081'));
|
var resource = new WebSocketResource(
|
||||||
|
new WebSocket('ws://localhost:8081')
|
||||||
|
);
|
||||||
resource.close();
|
resource.close();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe.skip('with a keepalive config', function() {
|
describe.skip('with a keepalive config', function() {
|
||||||
before(function() { window.WebSocket = MockSocket; });
|
before(function() {
|
||||||
after (function() { window.WebSocket = WebSocket; });
|
window.WebSocket = MockSocket;
|
||||||
|
});
|
||||||
|
after(function() {
|
||||||
|
window.WebSocket = WebSocket;
|
||||||
|
});
|
||||||
this.timeout(60000);
|
this.timeout(60000);
|
||||||
it('sends keepalives once a minute', function(done) {
|
it('sends keepalives once a minute', function(done) {
|
||||||
var mockServer = new MockServer('ws://localhost:8081');
|
var mockServer = new MockServer('ws://localhost:8081');
|
||||||
mockServer.on('connection', function(server) {
|
mockServer.on('connection', function(server) {
|
||||||
server.on('message', function(data) {
|
server.on('message', function(data) {
|
||||||
var message = textsecure.protobuf.WebSocketMessage.decode(data);
|
var message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||||
assert.strictEqual(message.type, textsecure.protobuf.WebSocketMessage.Type.REQUEST);
|
assert.strictEqual(
|
||||||
|
message.type,
|
||||||
|
textsecure.protobuf.WebSocketMessage.Type.REQUEST
|
||||||
|
);
|
||||||
assert.strictEqual(message.request.verb, 'GET');
|
assert.strictEqual(message.request.verb, 'GET');
|
||||||
assert.strictEqual(message.request.path, '/v1/keepalive');
|
assert.strictEqual(message.request.path, '/v1/keepalive');
|
||||||
server.close();
|
server.close();
|
||||||
|
@ -115,7 +144,7 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
new WebSocketResource(new WebSocket('ws://localhost:8081'), {
|
new WebSocketResource(new WebSocket('ws://localhost:8081'), {
|
||||||
keepalive: { path: '/v1/keepalive' }
|
keepalive: { path: '/v1/keepalive' },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -124,7 +153,10 @@
|
||||||
mockServer.on('connection', function(server) {
|
mockServer.on('connection', function(server) {
|
||||||
server.on('message', function(data) {
|
server.on('message', function(data) {
|
||||||
var message = textsecure.protobuf.WebSocketMessage.decode(data);
|
var message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||||
assert.strictEqual(message.type, textsecure.protobuf.WebSocketMessage.Type.REQUEST);
|
assert.strictEqual(
|
||||||
|
message.type,
|
||||||
|
textsecure.protobuf.WebSocketMessage.Type.REQUEST
|
||||||
|
);
|
||||||
assert.strictEqual(message.request.verb, 'GET');
|
assert.strictEqual(message.request.verb, 'GET');
|
||||||
assert.strictEqual(message.request.path, '/');
|
assert.strictEqual(message.request.path, '/');
|
||||||
server.close();
|
server.close();
|
||||||
|
@ -132,9 +164,8 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
new WebSocketResource(new WebSocket('ws://localhost:8081'), {
|
new WebSocketResource(new WebSocket('ws://localhost:8081'), {
|
||||||
keepalive: true
|
keepalive: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('optionally disconnects if no response', function(done) {
|
it('optionally disconnects if no response', function(done) {
|
||||||
|
@ -155,19 +186,25 @@
|
||||||
mockServer.on('connection', function(server) {
|
mockServer.on('connection', function(server) {
|
||||||
server.on('message', function(data) {
|
server.on('message', function(data) {
|
||||||
var message = textsecure.protobuf.WebSocketMessage.decode(data);
|
var message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||||
assert.strictEqual(message.type, textsecure.protobuf.WebSocketMessage.Type.REQUEST);
|
assert.strictEqual(
|
||||||
|
message.type,
|
||||||
|
textsecure.protobuf.WebSocketMessage.Type.REQUEST
|
||||||
|
);
|
||||||
assert.strictEqual(message.request.verb, 'GET');
|
assert.strictEqual(message.request.verb, 'GET');
|
||||||
assert.strictEqual(message.request.path, '/');
|
assert.strictEqual(message.request.path, '/');
|
||||||
assert(Date.now() > startTime + 60000, 'keepalive time should be longer than a minute');
|
assert(
|
||||||
|
Date.now() > startTime + 60000,
|
||||||
|
'keepalive time should be longer than a minute'
|
||||||
|
);
|
||||||
server.close();
|
server.close();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
var resource = new WebSocketResource(socket, { keepalive: true });
|
var resource = new WebSocketResource(socket, { keepalive: true });
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
resource.resetKeepAliveTimer()
|
resource.resetKeepAliveTimer();
|
||||||
}, 5000);
|
}, 5000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}());
|
})();
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
describe('TextSecureWebSocket', function() {
|
describe('TextSecureWebSocket', function() {
|
||||||
var RealWebSocket = window.WebSocket;
|
var RealWebSocket = window.WebSocket;
|
||||||
before(function() { window.WebSocket = MockSocket; });
|
before(function() {
|
||||||
after (function() { window.WebSocket = RealWebSocket; });
|
window.WebSocket = MockSocket;
|
||||||
|
});
|
||||||
|
after(function() {
|
||||||
|
window.WebSocket = RealWebSocket;
|
||||||
|
});
|
||||||
it('connects and disconnects', function(done) {
|
it('connects and disconnects', function(done) {
|
||||||
var mockServer = new MockServer('ws://localhost:8080');
|
var mockServer = new MockServer('ws://localhost:8080');
|
||||||
mockServer.on('connection', function(server) {
|
mockServer.on('connection', function(server) {
|
||||||
|
@ -27,7 +31,6 @@ describe('TextSecureWebSocket', function() {
|
||||||
done();
|
done();
|
||||||
};
|
};
|
||||||
socket.send('syn');
|
socket.send('syn');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('exposes the socket status', function(done) {
|
it('exposes the socket status', function(done) {
|
||||||
|
@ -58,5 +61,4 @@ describe('TextSecureWebSocket', function() {
|
||||||
};
|
};
|
||||||
mockServer.close();
|
mockServer.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
;(function(){
|
(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -54,8 +54,10 @@
|
||||||
socket.send(
|
socket.send(
|
||||||
new textsecure.protobuf.WebSocketMessage({
|
new textsecure.protobuf.WebSocketMessage({
|
||||||
type: textsecure.protobuf.WebSocketMessage.Type.RESPONSE,
|
type: textsecure.protobuf.WebSocketMessage.Type.RESPONSE,
|
||||||
response: { id: request.id, message: message, status: status }
|
response: { id: request.id, message: message, status: status },
|
||||||
}).encode().toArrayBuffer()
|
})
|
||||||
|
.encode()
|
||||||
|
.toArrayBuffer()
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -71,9 +73,11 @@
|
||||||
verb: request.verb,
|
verb: request.verb,
|
||||||
path: request.path,
|
path: request.path,
|
||||||
body: request.body,
|
body: request.body,
|
||||||
id : request.id
|
id: request.id,
|
||||||
}
|
},
|
||||||
}).encode().toArrayBuffer()
|
})
|
||||||
|
.encode()
|
||||||
|
.toArrayBuffer()
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -93,18 +97,21 @@
|
||||||
var blob = socketMessage.data;
|
var blob = socketMessage.data;
|
||||||
var handleArrayBuffer = function(buffer) {
|
var handleArrayBuffer = function(buffer) {
|
||||||
var message = textsecure.protobuf.WebSocketMessage.decode(buffer);
|
var message = textsecure.protobuf.WebSocketMessage.decode(buffer);
|
||||||
if (message.type === textsecure.protobuf.WebSocketMessage.Type.REQUEST ) {
|
if (
|
||||||
|
message.type === textsecure.protobuf.WebSocketMessage.Type.REQUEST
|
||||||
|
) {
|
||||||
handleRequest(
|
handleRequest(
|
||||||
new IncomingWebSocketRequest({
|
new IncomingWebSocketRequest({
|
||||||
verb: message.request.verb,
|
verb: message.request.verb,
|
||||||
path: message.request.path,
|
path: message.request.path,
|
||||||
body: message.request.body,
|
body: message.request.body,
|
||||||
id: message.request.id,
|
id: message.request.id,
|
||||||
socket : socket
|
socket: socket,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
} else if (
|
||||||
else if (message.type === textsecure.protobuf.WebSocketMessage.Type.RESPONSE ) {
|
message.type === textsecure.protobuf.WebSocketMessage.Type.RESPONSE
|
||||||
|
) {
|
||||||
var response = message.response;
|
var response = message.response;
|
||||||
var request = outgoing[response.id];
|
var request = outgoing[response.id];
|
||||||
if (request) {
|
if (request) {
|
||||||
|
@ -118,7 +125,8 @@
|
||||||
callback(response.message, response.status, request);
|
callback(response.message, response.status, request);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw 'Received response for unknown request ' + message.response.id;
|
throw 'Received response for unknown request ' +
|
||||||
|
message.response.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -137,17 +145,23 @@
|
||||||
if (opts.keepalive) {
|
if (opts.keepalive) {
|
||||||
this.keepalive = new KeepAlive(this, {
|
this.keepalive = new KeepAlive(this, {
|
||||||
path: opts.keepalive.path,
|
path: opts.keepalive.path,
|
||||||
disconnect : opts.keepalive.disconnect
|
disconnect: opts.keepalive.disconnect,
|
||||||
});
|
});
|
||||||
var resetKeepAliveTimer = this.keepalive.reset.bind(this.keepalive);
|
var resetKeepAliveTimer = this.keepalive.reset.bind(this.keepalive);
|
||||||
socket.addEventListener('open', resetKeepAliveTimer);
|
socket.addEventListener('open', resetKeepAliveTimer);
|
||||||
socket.addEventListener('message', resetKeepAliveTimer);
|
socket.addEventListener('message', resetKeepAliveTimer);
|
||||||
socket.addEventListener('close', this.keepalive.stop.bind(this.keepalive));
|
socket.addEventListener(
|
||||||
|
'close',
|
||||||
|
this.keepalive.stop.bind(this.keepalive)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.addEventListener('close', function() {
|
socket.addEventListener(
|
||||||
|
'close',
|
||||||
|
function() {
|
||||||
this.closed = true;
|
this.closed = true;
|
||||||
}.bind(this))
|
}.bind(this)
|
||||||
|
);
|
||||||
|
|
||||||
this.close = function(code, reason) {
|
this.close = function(code, reason) {
|
||||||
if (this.closed) {
|
if (this.closed) {
|
||||||
|
@ -168,7 +182,8 @@
|
||||||
// On linux the socket can wait a long time to emit its close event if we've
|
// On linux the socket can wait a long time to emit its close event if we've
|
||||||
// lost the internet connection. On the order of minutes. This speeds that
|
// lost the internet connection. On the order of minutes. This speeds that
|
||||||
// process up.
|
// process up.
|
||||||
setTimeout(function() {
|
setTimeout(
|
||||||
|
function() {
|
||||||
if (this.closed) {
|
if (this.closed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -179,12 +194,13 @@
|
||||||
ev.code = code;
|
ev.code = code;
|
||||||
ev.reason = reason;
|
ev.reason = reason;
|
||||||
this.dispatchEvent(ev);
|
this.dispatchEvent(ev);
|
||||||
}.bind(this), 1000);
|
}.bind(this),
|
||||||
|
1000
|
||||||
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
window.WebSocketResource.prototype = new textsecure.EventTarget();
|
window.WebSocketResource.prototype = new textsecure.EventTarget();
|
||||||
|
|
||||||
|
|
||||||
function KeepAlive(websocketResource, opts) {
|
function KeepAlive(websocketResource, opts) {
|
||||||
if (websocketResource instanceof WebSocketResource) {
|
if (websocketResource instanceof WebSocketResource) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
@ -211,13 +227,17 @@
|
||||||
reset: function() {
|
reset: function() {
|
||||||
clearTimeout(this.keepAliveTimer);
|
clearTimeout(this.keepAliveTimer);
|
||||||
clearTimeout(this.disconnectTimer);
|
clearTimeout(this.disconnectTimer);
|
||||||
this.keepAliveTimer = setTimeout(function() {
|
this.keepAliveTimer = setTimeout(
|
||||||
|
function() {
|
||||||
if (this.disconnect) {
|
if (this.disconnect) {
|
||||||
// automatically disconnect if server doesn't ack
|
// automatically disconnect if server doesn't ack
|
||||||
this.disconnectTimer = setTimeout(function() {
|
this.disconnectTimer = setTimeout(
|
||||||
|
function() {
|
||||||
clearTimeout(this.keepAliveTimer);
|
clearTimeout(this.keepAliveTimer);
|
||||||
this.wsr.close(3001, 'No response to keepalive request');
|
this.wsr.close(3001, 'No response to keepalive request');
|
||||||
}.bind(this), 1000);
|
}.bind(this),
|
||||||
|
1000
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.reset();
|
this.reset();
|
||||||
}
|
}
|
||||||
|
@ -225,10 +245,11 @@
|
||||||
this.wsr.sendRequest({
|
this.wsr.sendRequest({
|
||||||
verb: 'GET',
|
verb: 'GET',
|
||||||
path: this.path,
|
path: this.path,
|
||||||
success: this.reset.bind(this)
|
success: this.reset.bind(this),
|
||||||
});
|
});
|
||||||
}.bind(this), 55000);
|
}.bind(this),
|
||||||
|
55000
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
})();
|
||||||
}());
|
|
||||||
|
|
Loading…
Reference in a new issue