Now reading messages and adding them to conversations. Some cleaning of unreachable code. Modified the message data to be encoded as base64 string before sending to server

Sending our public key in header of message

Now attaching our key to the source field when sending messages, allows messages to be decrypted with the fallback cypher

Now polling the server for messages every 5 seconds

Sending the source device with messages

Added mock respond function to request to leave it that same as the websocket stuff. RetrieveMessages now just returns the result

Polling now continues if the server responds with an error. Returning only the result from sendMessage and retrieveMessages

Revert commenting of unreachable code

Refactored http logic into own file

Revert a change to websocket-resources
pull/29/head
Beaudan 7 years ago
parent 1ccf3b6b95
commit c59b196487

@ -76,6 +76,7 @@ module.exports = grunt => {
'libtextsecure/event_target.js', 'libtextsecure/event_target.js',
'libtextsecure/account_manager.js', 'libtextsecure/account_manager.js',
'libtextsecure/websocket-resources.js', 'libtextsecure/websocket-resources.js',
'libtextsecure/http-resources.js',
'libtextsecure/message_receiver.js', 'libtextsecure/message_receiver.js',
'libtextsecure/outgoing_message.js', 'libtextsecure/outgoing_message.js',
'libtextsecure/sendmessage.js', 'libtextsecure/sendmessage.js',

@ -1,4 +1,4 @@
/* global log */ /* global log, dcodeIO */
const fetch = require('node-fetch'); const fetch = require('node-fetch');
const is = require('@sindresorhus/is'); const is = require('@sindresorhus/is');
@ -33,7 +33,7 @@ function initialize({ url }) {
timestamp, timestamp,
ttl, ttl,
pubKey, pubKey,
data: Array.from(data), data,
}); });
// Handle child process error (should never happen) // Handle child process error (should never happen)
@ -57,7 +57,7 @@ function initialize({ url }) {
const options = { const options = {
url: `${url}/retrieve`, url: `${url}/retrieve`,
type: 'GET', type: 'GET',
responseType: undefined, responseType: 'json',
timeout: undefined, timeout: undefined,
}; };
@ -93,18 +93,20 @@ function initialize({ url }) {
if (response.status >= 0 && response.status < 400) { if (response.status >= 0 && response.status < 400) {
log.info(options.type, options.url, response.status, 'Success'); log.info(options.type, options.url, response.status, 'Success');
return [result, response.status]; return result;
} }
log.error(options.type, options.url, response.status, 'Error'); log.error(options.type, options.url, response.status, 'Error');
throw HTTPError('retrieveMessages: error response', response.status, result); throw HTTPError('retrieveMessages: error response', response.status, result);
} }
async function sendMessage(pubKey, data, ttl) { async function sendMessage(pubKey, data, ttl) {
const data64 = dcodeIO.ByteBuffer.wrap(data).toString('base64');
const timestamp = Math.floor(Date.now() / 1000); const timestamp = Math.floor(Date.now() / 1000);
// Nonce is returned as a base64 string to include in header // Nonce is returned as a base64 string to include in header
let nonce; let nonce;
try { try {
nonce = await getPoWNonce(timestamp, ttl, pubKey, data); nonce = await getPoWNonce(timestamp, ttl, pubKey, data64);
} catch (err) { } catch (err) {
// Something went horribly wrong // Something went horribly wrong
// TODO: Handle gracefully // TODO: Handle gracefully
@ -122,13 +124,12 @@ function initialize({ url }) {
const fetchOptions = { const fetchOptions = {
method: options.type, method: options.type,
body: data, body: data64,
headers: { headers: {
'X-Loki-pow-nonce': nonce, 'X-Loki-pow-nonce': nonce,
'X-Loki-timestamp': timestamp.toString(), 'X-Loki-timestamp': timestamp.toString(),
'X-Loki-ttl': ttl.toString(), 'X-Loki-ttl': ttl.toString(),
'X-Loki-recipient': pubKey, 'X-Loki-recipient': pubKey,
'Content-Length': data.byteLength,
}, },
timeout: options.timeout, timeout: options.timeout,
}; };
@ -155,7 +156,7 @@ function initialize({ url }) {
if (response.status >= 0 && response.status < 400) { if (response.status >= 0 && response.status < 400) {
log.info(options.type, options.url, response.status, 'Success'); log.info(options.type, options.url, response.status, 'Success');
return [result, response.status]; return result;
} }
log.error(options.type, options.url, response.status, 'Error'); log.error(options.type, options.url, response.status, 'Error');
throw HTTPError('sendMessage: error response', response.status, result); throw HTTPError('sendMessage: error response', response.status, result);

@ -58,15 +58,9 @@ function greaterThan(arr1, arr2) {
// Return nonce that hashes together with payload lower than the target // Return nonce that hashes together with payload lower than the target
function calcPoW(timestamp, ttl, pubKey, data) { function calcPoW(timestamp, ttl, pubKey, data) {
const leadingString = timestamp.toString() + ttl.toString() + pubKey; const payload = new Uint8Array(
const leadingArray = new Uint8Array( bb.wrap(timestamp.toString() + ttl.toString() + pubKey + data, 'binary').toArrayBuffer()
bb.wrap(leadingString, 'binary').toArrayBuffer()
); );
// Payload constructed from concatenating timestamp, ttl and pubkey strings,
// converting to Uint8Array and then appending to the message data array
const payload = new Uint8Array(leadingArray.length + data.length);
payload.set(leadingArray);
payload.set(data, leadingArray.length);
// payloadLength + NONCE_LEN // payloadLength + NONCE_LEN
const totalLen = new BigInteger(payload.length.toString()).add( const totalLen = new BigInteger(payload.length.toString()).add(
@ -118,7 +112,7 @@ process.on('message', msg => {
msg.timestamp, msg.timestamp,
msg.ttl, msg.ttl,
msg.pubKey, msg.pubKey,
new Uint8Array(msg.data) msg.data
), ),
}); });
}); });

@ -0,0 +1,83 @@
/* global window, dcodeIO, textsecure, StringView */
// eslint-disable-next-line func-names
(function() {
let server;
function stringToArrayBufferBase64(string) {
return dcodeIO.ByteBuffer.wrap(string, 'base64').toArrayBuffer();
}
const Response = function Response(options) {
this.verb = options.verb || options.type;
this.path = options.path || options.url;
this.body = options.body || options.data;
this.success = options.success;
this.error = options.error;
this.id = options.id;
if (this.id === undefined) {
const bits = new Uint32Array(2);
window.crypto.getRandomValues(bits);
this.id = dcodeIO.Long.fromBits(bits[0], bits[1], true);
}
if (this.body === undefined) {
this.body = null;
}
};
const IncomingHttpResponse = function IncomingHttpResponse(options) {
const request = new Response(options);
this.verb = request.verb;
this.path = request.path;
this.body = request.body;
this.respond = (status, message) => {
// Mock websocket response
window.log.info(status, message);
};
};
window.HttpResource = function HttpResource(_server, opts = {}) {
server = _server;
let { handleRequest } = opts;
if (typeof handleRequest !== 'function') {
handleRequest = request => request.respond(404, 'Not found');
};
this.startPolling = async function pollServer() {
const myKeys = await textsecure.storage.protocol.getIdentityKeyPair();
const pubKey = StringView.arrayBufferToHex(myKeys.pubKey)
let result;
try {
result = await server.retrieveMessages(pubKey);
} catch(err) {
setTimeout(() => { pollServer(); }, 5000);
return;
}
if (!result.messages) {
setTimeout(() => { pollServer(); }, 5000);
return;
}
result.messages.forEach(async message => {
const { data } = message;
const dataPlaintext = stringToArrayBufferBase64(data);
const messageBuf = textsecure.protobuf.WebSocketMessage.decode(dataPlaintext);
if (messageBuf.type === textsecure.protobuf.WebSocketMessage.Type.REQUEST) {
handleRequest(
new IncomingHttpResponse({
verb: messageBuf.request.verb,
path: messageBuf.request.path,
body: messageBuf.request.body,
id: messageBuf.request.id,
})
);
}
});
setTimeout(() => { pollServer(); }, 5000);
};
};
})();

@ -1,12 +1,12 @@
/* global window: false */ /* global window: false */
/* global textsecure: false */ /* global textsecure: false */
/* global WebAPI: false */ /* global StringView: false */
/* global libsignal: false */ /* global libsignal: false */
/* global WebSocketResource: false */
/* global WebSocket: false */ /* global WebSocket: false */
/* global Event: false */ /* global Event: false */
/* global dcodeIO: false */ /* global dcodeIO: false */
/* global _: false */ /* global _: false */
/* global HttpResource: false */
/* global ContactBuffer: false */ /* global ContactBuffer: false */
/* global GroupBuffer: false */ /* global GroupBuffer: false */
/* global Worker: false */ /* global Worker: false */
@ -147,7 +147,7 @@ MessageReceiver.arrayBufferToStringBase64 = arrayBuffer =>
MessageReceiver.prototype = new textsecure.EventTarget(); MessageReceiver.prototype = new textsecure.EventTarget();
MessageReceiver.prototype.extend({ MessageReceiver.prototype.extend({
constructor: MessageReceiver, constructor: MessageReceiver,
async connect() { connect() {
if (this.calledClose) { if (this.calledClose) {
return; return;
} }
@ -159,12 +159,13 @@ MessageReceiver.prototype.extend({
} }
this.hasConnected = true; this.hasConnected = true;
const myKeys = await textsecure.storage.protocol.getIdentityKeyPair(); this.hr = new HttpResource(this.lokiserver, {
const result = await this.lokiserver.retrieveMessages(myKeys); handleRequest: this.handleRequest.bind(this),
});
this.hr.startPolling();
// TODO: Rework this socket stuff to work with online messaging
return; return;
// TODO: Rework this socket stuff to work with online messaging
if (this.socket && this.socket.readyState !== WebSocket.CLOSED) { if (this.socket && this.socket.readyState !== WebSocket.CLOSED) {
this.socket.close(); this.socket.close();
this.wsr.close(); this.wsr.close();
@ -234,7 +235,6 @@ MessageReceiver.prototype.extend({
); );
// TODO: handle properly // TODO: handle properly
return; return;
this.shutdown(); this.shutdown();
if (this.calledClose) { if (this.calledClose) {
@ -274,8 +274,8 @@ MessageReceiver.prototype.extend({
return; return;
} }
const promise = Promise.resolve(request.body.toArrayBuffer()) //textsecure.crypto const promise = Promise.resolve(request.body.toArrayBuffer()) // textsecure.crypto
//.decryptWebsocketMessage(request.body, this.signalingKey) // .decryptWebsocketMessage(request.body, 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

@ -166,8 +166,8 @@ OutgoingMessage.prototype = {
async transmitMessage(number, data, timestamp, ttl = 24 * 60 * 60) { async transmitMessage(number, data, timestamp, ttl = 24 * 60 * 60) {
const pubKey = number; const pubKey = number;
try { try {
const [response] = await this.lokiserver.sendMessage(pubKey, data, ttl); const result = await this.lokiserver.sendMessage(pubKey, data, ttl);
return response; return result;
} catch (e) { } catch (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
@ -209,8 +209,8 @@ OutgoingMessage.prototype = {
async wrapInWebsocketMessage(outgoingObject) { async wrapInWebsocketMessage(outgoingObject) {
const messageEnvelope = new textsecure.protobuf.Envelope({ const messageEnvelope = new textsecure.protobuf.Envelope({
type: outgoingObject.type, type: outgoingObject.type,
source: outgoingObject.address.getName(), source: outgoingObject.ourKey,
sourceDevice: outgoingObject.address.getDeviceId(), sourceDevice: outgoingObject.sourceDevice,
timestamp: this.timestamp, timestamp: this.timestamp,
content: outgoingObject.content, content: outgoingObject.content,
}); });
@ -236,11 +236,11 @@ OutgoingMessage.prototype = {
deviceIds.map(async deviceId => { deviceIds.map(async deviceId => {
const address = new libsignal.SignalProtocolAddress(number, deviceId); const address = new libsignal.SignalProtocolAddress(number, deviceId);
const ourNumber = textsecure.storage.user.getNumber(); const ourKey = textsecure.storage.user.getNumber();
const options = {}; const options = {};
// No limit on message keys if we're communicating with our other devices // No limit on message keys if we're communicating with our other devices
if (ourNumber === number) { if (ourKey === number) {
options.messageKeysLimit = false; options.messageKeysLimit = false;
} }
@ -270,8 +270,8 @@ OutgoingMessage.prototype = {
}) })
.then(ciphertext => ({ .then(ciphertext => ({
type: ciphertext.type, type: ciphertext.type,
address, ourKey,
destinationDeviceId: address.getDeviceId(), sourceDevice: 1,
destinationRegistrationId: ciphertext.registrationId, destinationRegistrationId: ciphertext.registrationId,
content: ciphertext.body, content: ciphertext.body,
})); }));

Loading…
Cancel
Save