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,
 | 
						|
};
 |