From 8c9b1a7a7ade8f87abebb2d8eb133a9222797301 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 21 Jan 2019 10:46:47 +1100 Subject: [PATCH] Added loki server. Added test. --- .prettierignore | 3 + libloki/local_loki_server.js | 75 ++++++++++++++++++++ libloki/test/node/.eslintrc.js | 31 +++++++++ libloki/test/node/local_loki_server_test.js | 77 +++++++++++++++++++++ libtextsecure/http-resources.js | 36 +++++----- libtextsecure/message_receiver.js | 15 ++++ package.json | 5 +- preload.js | 4 ++ yarn.lock | 17 ++++- 9 files changed, 244 insertions(+), 19 deletions(-) create mode 100644 libloki/local_loki_server.js create mode 100644 libloki/test/node/.eslintrc.js create mode 100644 libloki/test/node/local_loki_server_test.js diff --git a/.prettierignore b/.prettierignore index 50862cc2d..2dbd12114 100644 --- a/.prettierignore +++ b/.prettierignore @@ -50,3 +50,6 @@ _locales/**/*.json # Symlink into third-party `components`: stylesheets/_intlTelInput.scss +# Coverage +coverage/** +.nyc_output/** \ No newline at end of file diff --git a/libloki/local_loki_server.js b/libloki/local_loki_server.js new file mode 100644 index 000000000..b7b5c0fc1 --- /dev/null +++ b/libloki/local_loki_server.js @@ -0,0 +1,75 @@ +const http = require('http'); +const EventEmitter = require('events'); + +class LocalLokiServer extends EventEmitter { + /** + * Creates an instance of LocalLokiServer. + * Sends out a `message` event when a new message is received. + */ + constructor() { + super(); + this.server = http.createServer((req, res) => { + let body = []; + + // Check endpoints + if (req.method === 'POST') { + req + .on('error', () => { + // Internal server error + res.statusCode = 500; + res.end(); + }) + .on('data', chunk => { + body.push(chunk); + }) + .on('end', () => { + body = Buffer.concat(body).toString(); + + // Check endpoints here + if (req.url === '/store') { + // body is a base64 encoded string + this.emit('message', body); + res.statusCode = 200; + res.end(); + } else { + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('Invalid endpoint!'); + } + }); + } else { + // Method Not Allowed + res.statusCode = 405; + res.end(); + } + }); + } + + async start(port) { + // Close the old server + await this.close(); + + // Start a listening on new server + return new Promise((res, rej) => { + this.server.listen(port, err => { + if (err) { + rej(err); + } else { + res(port); + } + }); + }); + } + + // Async wrapper for http server close + close() { + if (this.server) { + return new Promise(res => { + this.server.close(() => res()); + }); + } + + return Promise.resolve(); + } +} + +exports.LocalLokiServer = LocalLokiServer; diff --git a/libloki/test/node/.eslintrc.js b/libloki/test/node/.eslintrc.js new file mode 100644 index 000000000..acedc200c --- /dev/null +++ b/libloki/test/node/.eslintrc.js @@ -0,0 +1,31 @@ +// For reference: https://github.com/airbnb/javascript + +module.exports = { + env: { + node: true, + mocha: true, + browser: true, + }, + + globals: { + check: true, + gen: true, + }, + + parserOptions: { + sourceType: 'module', + }, + + rules: { + // We still get the value of this rule, it just allows for dev deps + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: true, + }, + ], + + // We want to keep each test structured the same, even if its contents are tiny + 'arrow-body-style': 'off', + }, +}; diff --git a/libloki/test/node/local_loki_server_test.js b/libloki/test/node/local_loki_server_test.js new file mode 100644 index 000000000..ec75a72f0 --- /dev/null +++ b/libloki/test/node/local_loki_server_test.js @@ -0,0 +1,77 @@ +const axios = require('axios'); +const { assert } = require('chai'); +const { LocalLokiServer } = require('../../local_loki_server'); + +describe('LocalLokiServer', () => { + before(async () => { + this.server = new LocalLokiServer(); + await this.server.start(8000); + }); + + after(() => { + this.server.close(); + }); + + it('should return 405 if not a POST request', async () => { + try { + await axios.get('http://localhost:8000'); + assert.fail('Got a successful response'); + } catch (error) { + if (error.response) { + assert.equal(405, error.response.status); + return; + } + assert.isNotOk(error, 'Another error was receieved'); + } + }); + + it('should return 404 if no endpoint provided', async () => { + try { + await axios.post('http://localhost:8000', { name: 'Test' }); + assert.fail('Got a successful response'); + } catch (error) { + if (error.response) { + assert.equal(404, error.response.status); + return; + } + assert.isNotOk(error, 'Another error was receieved'); + } + }); + + it('should return 404 and a string if invalid enpoint is provided', async () => { + try { + await axios.post('http://localhost:8000/invalid', { name: 'Test' }); + assert.fail('Got a successful response'); + } catch (error) { + if (error.response) { + assert.equal(404, error.response.status); + assert.equal('Invalid endpoint!', error.response.data); + return; + } + assert.isNotOk(error, 'Another error was receieved'); + } + }); + + describe('/store', async () => { + it('should pass the POSTed data to the callback', async () => { + const server = new LocalLokiServer(); + await server.start(8001); + + const promise = new Promise(res => { + server.on('message', message => { + assert.equal(message, 'This is data'); + server.close(); + res(); + }); + }); + + try { + await axios.post('http://localhost:8001/store', 'This is data'); + } catch (error) { + assert.isNotOk(error, 'Error occured'); + } + + return promise; + }); + }); +}); diff --git a/libtextsecure/http-resources.js b/libtextsecure/http-resources.js index 071d0ad32..12e736cbd 100644 --- a/libtextsecure/http-resources.js +++ b/libtextsecure/http-resources.js @@ -70,25 +70,29 @@ const newMessages = await filterIncomingMessages(messages); newMessages.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, - }) - ); - } + this.handleMessage(data); }); }; + this.handleMessage = message => { + const dataPlaintext = stringToArrayBufferBase64(message); + 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, + }) + ); + } + }; + this.startPolling = async function pollServer(callback) { try { await server.retrieveMessages(processMessages); diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index c9325707f..d42a04a64 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -23,6 +23,7 @@ function MessageReceiver(username, password, signalingKey, options = {}) { this.username = username; this.password = password; this.lokiMessageAPI = window.LokiMessageAPI; + this.localServer = new window.LocalLokiServer(); if (!options.serverTrustRoot) { throw new Error('Server trust root is required!'); @@ -80,6 +81,11 @@ MessageReceiver.prototype.extend({ this.onEmpty(); } }); + + this.localServer.removeAllListeners(); + this.localServer.on('message', this.httpPollingResource.handleMessage); + this.localServer.start(8000); + // TODO: Rework this socket stuff to work with online messaging const useWebSocket = false; if (useWebSocket) { @@ -121,6 +127,11 @@ MessageReceiver.prototype.extend({ this.wsr.removeEventListener('close', this._onClose); this.wsr = null; } + + if (this.localServer) { + this.localServer.removeAllListeners(); + this.localServer = null; + } }, close() { window.log.info('MessageReceiver.close()'); @@ -132,6 +143,10 @@ MessageReceiver.prototype.extend({ this.wsr.close(3000, 'called close'); } + if (this.localServer) { + this.localServer.close(); + } + return this.drain(); }, onopen() { diff --git a/package.json b/package.json index f4e4e55e5..41fa5f4af 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,8 @@ "test-lib-view": "NODE_ENV=test-lib yarn run start", "test-loki-view": "NODE_ENV=test-loki yarn run start", "test-electron": "yarn grunt test", - "test-node": "mocha --recursive test/app test/modules ts/test", - "test-node-coverage": "nyc --reporter=lcov --reporter=text mocha --recursive test/app test/modules ts/test", + "test-node": "mocha --recursive test/app test/modules ts/test libloki/test/node", + "test-node-coverage": "nyc --reporter=lcov --reporter=text mocha --recursive test/app test/modules ts/test libloki/test/node", "eslint": "eslint .", "lint": "yarn format --list-different && yarn lint-windows", "lint-windows": "yarn eslint && yarn tslint", @@ -118,6 +118,7 @@ "@types/sinon": "4.3.1", "arraybuffer-loader": "1.0.3", "asar": "0.14.0", + "axios": "0.18.0", "bower": "1.8.2", "chai": "4.1.2", "electron": "3.0.9", diff --git a/preload.js b/preload.js index 11a951517..9defb1067 100644 --- a/preload.js +++ b/preload.js @@ -283,6 +283,10 @@ window.LokiMessageAPI = new LokiMessageAPI({ messageServerPort: config.messageServerPort, }); +const { LocalLokiServer } = require('./libloki/local_loki_server'); + +window.LocalLokiServer = LocalLokiServer; + window.mnemonic = require('./libloki/mnemonic'); const { WorkerInterface } = require('./js/modules/util_worker_interface'); diff --git a/yarn.lock b/yarn.lock index 922dbb1ee..644fa9d38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -666,6 +666,14 @@ aws4@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" +axios@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102" + integrity sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI= + dependencies: + follow-redirects "^1.3.0" + is-buffer "^1.1.5" + babel-code-frame@6.26.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -2028,7 +2036,7 @@ debug@2, debug@2.6.9, debug@^2.1.2, debug@^2.3.3, debug@^2.6.0, debug@^2.6.6: dependencies: ms "2.0.0" -debug@3.1.0, debug@^3.1.0: +debug@3.1.0, debug@=3.1.0, debug@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" dependencies: @@ -3225,6 +3233,13 @@ flush-write-stream@^1.0.0: inherits "^2.0.1" readable-stream "^2.0.4" +follow-redirects@^1.3.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.6.1.tgz#514973c44b5757368bad8bddfe52f81f015c94cb" + integrity sha512-t2JCjbzxQpWvbhts3l6SH1DKzSrx8a+SsaVf4h6bG4kOXUuPYS/kg2Lr4gQSb7eemaHqJkOThF1BGyjlUkO1GQ== + dependencies: + debug "=3.1.0" + for-each@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.2.tgz#2c40450b9348e97f281322593ba96704b9abd4d4"