You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			191 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			JavaScript
		
	
			
		
		
	
	
			191 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			JavaScript
		
	
| /* global log, dcodeIO, window */
 | |
| 
 | |
| const fetch = require('node-fetch');
 | |
| const is = require('@sindresorhus/is');
 | |
| const { fork } = require('child_process');
 | |
| 
 | |
| const development = (window.getEnvironment() !== 'production');
 | |
| 
 | |
| function getPoWNonce(timestamp, ttl, pubKey, data) {
 | |
|   return new Promise((resolve, reject) => {
 | |
|     // Create forked node process to calculate PoW without blocking main process
 | |
|     const child = fork('./libloki/proof-of-work.js');
 | |
| 
 | |
|     // Send data required for PoW to child process
 | |
|     child.send({
 | |
|       timestamp,
 | |
|       ttl,
 | |
|       pubKey,
 | |
|       data,
 | |
|       development,
 | |
|     });
 | |
| 
 | |
|     // Handle child process error (should never happen)
 | |
|     child.on('error', err => {
 | |
|       reject(err);
 | |
|     });
 | |
| 
 | |
|     // Callback to receive PoW result
 | |
|     child.on('message', msg => {
 | |
|       if (msg.err) {
 | |
|         reject(msg.err);
 | |
|       } else {
 | |
|         child.kill();
 | |
|         resolve(msg.nonce);
 | |
|       }
 | |
|     });
 | |
|   });
 | |
| }
 | |
| 
 | |
| class LokiServer {
 | |
| 
 | |
|   constructor({ urls }) {
 | |
|     this.nodes = [];
 | |
|     urls.forEach(url => {
 | |
|       if (!is.string(url)) {
 | |
|         throw new Error('WebAPI.initialize: Invalid server url');
 | |
|       }
 | |
|       this.nodes.push({ url });
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   async sendMessage(pubKey, data, messageTimeStamp, ttl) {
 | |
|     const data64 = dcodeIO.ByteBuffer.wrap(data).toString('base64');
 | |
|     // Hardcoded to use a single node/server for now
 | |
|     const currentNode = this.nodes[0];
 | |
| 
 | |
|     const timestamp = Math.floor(Date.now() / 1000);
 | |
|     // Nonce is returned as a base64 string to include in header
 | |
|     let nonce;
 | |
|     try {
 | |
|       window.Whisper.events.trigger('calculatingPoW', {
 | |
|         pubKey,
 | |
|         timestamp: messageTimeStamp,
 | |
|       });
 | |
|       nonce = await getPoWNonce(timestamp, ttl, pubKey, data64);
 | |
|     } catch (err) {
 | |
|       // Something went horribly wrong
 | |
|       // TODO: Handle gracefully
 | |
|       log.error('Error computing PoW');
 | |
|     }
 | |
| 
 | |
|     const options = {
 | |
|       url: `${currentNode.url}/store`,
 | |
|       type: 'POST',
 | |
|       responseType: undefined,
 | |
|       timeout: undefined,
 | |
|     };
 | |
| 
 | |
|     const fetchOptions = {
 | |
|       method: options.type,
 | |
|       body: data64,
 | |
|       headers: {
 | |
|         'X-Loki-pow-nonce': nonce,
 | |
|         'X-Loki-timestamp': timestamp.toString(),
 | |
|         'X-Loki-ttl': ttl.toString(),
 | |
|         'X-Loki-recipient': pubKey,
 | |
|       },
 | |
|       timeout: options.timeout,
 | |
|     };
 | |
| 
 | |
|     let response;
 | |
|     try {
 | |
|       response = await fetch(options.url, fetchOptions);
 | |
|     } catch (e) {
 | |
|       log.error(options.type, options.url, 0, 'Error');
 | |
|       throw HTTPError('fetch error', 0, e.toString());
 | |
|     }
 | |
| 
 | |
|     let result;
 | |
|     if (
 | |
|       options.responseType === 'json' &&
 | |
|       response.headers.get('Content-Type') === 'application/json'
 | |
|     ) {
 | |
|       result = await response.json();
 | |
|     } else if (options.responseType === 'arraybuffer') {
 | |
|       result = await response.buffer();
 | |
|     } else {
 | |
|       result = await response.text();
 | |
|     }
 | |
| 
 | |
|     if (response.status >= 0 && response.status < 400) {
 | |
|       return result;
 | |
|     }
 | |
|     log.error(options.type, options.url, response.status, 'Error');
 | |
|     throw HTTPError('sendMessage: error response', response.status, result);
 | |
|   }
 | |
| 
 | |
|   async retrieveMessages(pubKey) {
 | |
|     // Hardcoded to use a single node/server for now
 | |
|     const currentNode = this.nodes[0];
 | |
| 
 | |
|     const options = {
 | |
|       url: `${currentNode.url}/retrieve`,
 | |
|       type: 'GET',
 | |
|       responseType: 'json',
 | |
|       timeout: undefined,
 | |
|     };
 | |
| 
 | |
|     const headers = {
 | |
|       'X-Loki-recipient': pubKey,
 | |
|     };
 | |
| 
 | |
|     if (currentNode.lastHash) {
 | |
|       headers['X-Loki-last-hash'] = currentNode.lastHash;
 | |
|     }
 | |
| 
 | |
|     const fetchOptions = {
 | |
|       method: options.type,
 | |
|       headers,
 | |
|       timeout: options.timeout,
 | |
|     };
 | |
| 
 | |
|     let response;
 | |
|     try {
 | |
|       response = await fetch(options.url, fetchOptions);
 | |
|     } catch (e) {
 | |
|       log.error(options.type, options.url, 0, 'Error');
 | |
|       throw HTTPError('fetch error', 0, e.toString());
 | |
|     }
 | |
| 
 | |
|     let result;
 | |
|     if (
 | |
|       options.responseType === 'json' &&
 | |
|       response.headers.get('Content-Type') === 'application/json'
 | |
|     ) {
 | |
|       result = await response.json();
 | |
|     } else if (options.responseType === 'arraybuffer') {
 | |
|       result = await response.buffer();
 | |
|     } else {
 | |
|       result = await response.text();
 | |
|     }
 | |
| 
 | |
|     if (response.status >= 0 && response.status < 400) {
 | |
|       if (result.lastHash) {
 | |
|         currentNode.lastHash = result.lastHash;
 | |
|       }
 | |
|       return result;
 | |
|     }
 | |
|     log.error(options.type, options.url, response.status, 'Error');
 | |
|     throw HTTPError('retrieveMessages: error response', response.status, result);
 | |
|   }
 | |
| }
 | |
| 
 | |
| function HTTPError(message, providedCode, response, stack) {
 | |
|   const code = providedCode > 999 || providedCode < 100 ? -1 : providedCode;
 | |
|   const e = new Error(`${message}; code: ${code}`);
 | |
|   e.name = 'HTTPError';
 | |
|   e.code = code;
 | |
|   if (stack) {
 | |
|     e.stack += `\nOriginal stack:\n${stack}`;
 | |
|   }
 | |
|   if (response) {
 | |
|     e.response = response;
 | |
|   }
 | |
|   return e;
 | |
| }
 | |
| 
 | |
| module.exports = {
 | |
|   LokiServer,
 | |
| };
 |