|
|
|
@ -38,7 +38,7 @@ function MessageReceiver(username, password, signalingKey) {
|
|
|
|
|
// bind events
|
|
|
|
|
lokiPublicChatAPI.on(
|
|
|
|
|
'publicMessage',
|
|
|
|
|
this.handleUnencryptedMessage.bind(this)
|
|
|
|
|
window.NewReceiver.handleUnencryptedMessage
|
|
|
|
|
);
|
|
|
|
|
openGroupBound = true;
|
|
|
|
|
}
|
|
|
|
@ -80,48 +80,13 @@ MessageReceiver.prototype.extend({
|
|
|
|
|
}
|
|
|
|
|
// set up pollers for any RSS feeds
|
|
|
|
|
feeds.forEach(feed => {
|
|
|
|
|
feed.on('rssMessage', this.handleUnencryptedMessage.bind(this));
|
|
|
|
|
feed.on('rssMessage', window.NewReceiver.handleUnencryptedMessage);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Ensures that an immediate 'empty' event from the websocket will fire only after
|
|
|
|
|
// all cached envelopes are processed.
|
|
|
|
|
this.incoming = [this.pending];
|
|
|
|
|
},
|
|
|
|
|
async handleUnencryptedMessage({ message }) {
|
|
|
|
|
const isMe = message.source === textsecure.storage.user.getNumber();
|
|
|
|
|
if (!isMe && message.message.profile) {
|
|
|
|
|
const conversation = await window.ConversationController.getOrCreateAndWait(
|
|
|
|
|
message.source,
|
|
|
|
|
'private'
|
|
|
|
|
);
|
|
|
|
|
await window.NewReceiver.updateProfile(
|
|
|
|
|
conversation,
|
|
|
|
|
message.message.profile,
|
|
|
|
|
message.message.profileKey
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ourNumber = textsecure.storage.user.getNumber();
|
|
|
|
|
const primaryDevice = window.storage.get('primaryDevicePubKey');
|
|
|
|
|
const isOurDevice =
|
|
|
|
|
message.source &&
|
|
|
|
|
(message.source === ourNumber || message.source === primaryDevice);
|
|
|
|
|
const isPublicChatMessage =
|
|
|
|
|
message.message.group &&
|
|
|
|
|
message.message.group.id &&
|
|
|
|
|
!!message.message.group.id.match(/^publicChat:/);
|
|
|
|
|
let ev;
|
|
|
|
|
|
|
|
|
|
if (isPublicChatMessage && isOurDevice) {
|
|
|
|
|
// Public chat messages from ourselves should be outgoing
|
|
|
|
|
ev = new Event('sent');
|
|
|
|
|
} else {
|
|
|
|
|
ev = new Event('message');
|
|
|
|
|
}
|
|
|
|
|
ev.confirm = function confirmTerm() {};
|
|
|
|
|
ev.data = message;
|
|
|
|
|
this.dispatchAndWait(ev);
|
|
|
|
|
},
|
|
|
|
|
stopProcessing() {
|
|
|
|
|
window.log.info('MessageReceiver: stopProcessing requested');
|
|
|
|
|
this.stoppingProcessing = true;
|
|
|
|
@ -145,15 +110,6 @@ MessageReceiver.prototype.extend({
|
|
|
|
|
onerror() {
|
|
|
|
|
window.log.error('websocket error');
|
|
|
|
|
},
|
|
|
|
|
dispatchAndWait(event) {
|
|
|
|
|
const promise = this.appPromise || Promise.resolve();
|
|
|
|
|
const appJobPromise = Promise.all(this.dispatchEvent(event));
|
|
|
|
|
const job = () => appJobPromise;
|
|
|
|
|
|
|
|
|
|
this.appPromise = promise.then(job, job);
|
|
|
|
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
},
|
|
|
|
|
onclose(ev) {
|
|
|
|
|
window.log.info(
|
|
|
|
|
'websocket closed',
|
|
|
|
@ -163,40 +119,6 @@ MessageReceiver.prototype.extend({
|
|
|
|
|
this.calledClose
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onEmpty() {
|
|
|
|
|
const { incoming } = this;
|
|
|
|
|
this.incoming = [];
|
|
|
|
|
|
|
|
|
|
const emitEmpty = () => {
|
|
|
|
|
window.log.info("MessageReceiver: emitting 'empty' event");
|
|
|
|
|
const ev = new Event('empty');
|
|
|
|
|
this.dispatchAndWait(ev);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const waitForApplication = async () => {
|
|
|
|
|
window.log.info(
|
|
|
|
|
"MessageReceiver: finished processing messages after 'empty', now waiting for application"
|
|
|
|
|
);
|
|
|
|
|
const promise = this.appPromise || Promise.resolve();
|
|
|
|
|
this.appPromise = Promise.resolve();
|
|
|
|
|
|
|
|
|
|
// We don't await here because we don't this to gate future message processing
|
|
|
|
|
promise.then(emitEmpty, emitEmpty);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const waitForEmptyQueue = () => {
|
|
|
|
|
// resetting count to zero so everything queued after this starts over again
|
|
|
|
|
this.count = 0;
|
|
|
|
|
|
|
|
|
|
this.addToQueue(waitForApplication);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// We first wait for all recently-received messages (this.incoming) to be queued,
|
|
|
|
|
// then we queue a task to wait for the application to finish its processing, then
|
|
|
|
|
// finally we emit the 'empty' event to the queue.
|
|
|
|
|
Promise.all(incoming).then(waitForEmptyQueue, waitForEmptyQueue);
|
|
|
|
|
},
|
|
|
|
|
drain() {
|
|
|
|
|
const { incoming } = this;
|
|
|
|
|
this.incoming = [];
|
|
|
|
@ -219,42 +141,6 @@ MessageReceiver.prototype.extend({
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
},
|
|
|
|
|
unpad(paddedData) {
|
|
|
|
|
const paddedPlaintext = new Uint8Array(paddedData);
|
|
|
|
|
let plaintext;
|
|
|
|
|
|
|
|
|
|
for (let i = paddedPlaintext.length - 1; i >= 0; i -= 1) {
|
|
|
|
|
if (paddedPlaintext[i] === 0x80) {
|
|
|
|
|
plaintext = new Uint8Array(i);
|
|
|
|
|
plaintext.set(paddedPlaintext.subarray(0, i));
|
|
|
|
|
plaintext = plaintext.buffer;
|
|
|
|
|
break;
|
|
|
|
|
} else if (paddedPlaintext[i] !== 0x00) {
|
|
|
|
|
throw new Error('Invalid padding');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return plaintext;
|
|
|
|
|
},
|
|
|
|
|
async decryptPreKeyWhisperMessage(ciphertext, sessionCipher, address) {
|
|
|
|
|
const padded = await sessionCipher.decryptPreKeyWhisperMessage(ciphertext);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
return this.unpad(padded);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
if (e.message === 'Unknown identity key') {
|
|
|
|
|
// create an error that the UI will pick up and ask the
|
|
|
|
|
// user if they want to re-negotiate
|
|
|
|
|
const buffer = dcodeIO.ByteBuffer.wrap(ciphertext);
|
|
|
|
|
throw new textsecure.IncomingIdentityKeyError(
|
|
|
|
|
address.toString(),
|
|
|
|
|
buffer.toArrayBuffer(),
|
|
|
|
|
e.identityKey
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
window.textsecure = window.textsecure || {};
|
|
|
|
|