speedtest-net
Version: 
Speedtest.net client
211 lines (187 loc) • 5.16 kB
JavaScript
;
require('draftlog').into(console).addLineListener(process.stdin);
const chalk = require('chalk');
const speedTest = require('../');
let header;
let speeds;
let step = 'Ping';
const statuses = {
  Ping: true,
  Download: false,
  Upload: false,
};
const width = 24;
function updateLines() {
  const spinner = makeSpinner();
  const headerTxt = renderHeader(statuses, step);
  const speedsTxt = renderStatus(statuses, step, spinner);
  header('│' + headerTxt + '│');
  speeds('│' + speedsTxt + '│');
}
function renderHeader(statuses) {
  let txt = '';
  for (const k of Object.keys(statuses)) {
    const status = statuses[k];
    let col = centerText(k, width);
    if (status === false) {
      col = chalk.dim(col);
    } else {
      col = chalk.white.bold(col);
    }
    txt += col;
  }
  return txt;
}
function renderStatus(statuses, step, spinner) {
  let txt = '';
  for (const k of Object.keys(statuses)) {
    let status = statuses[k];
    status = String(status === Boolean(status) ? '' : status);
    if (!status) {
      status = spinner + ' ';
    }
    status = centerText(status, width);
    status = status.replace(/([KMGT]bps|ms)/gi, chalk.dim('$1'));
    if (step === k) {
      status = chalk.yellow(status);
    } else {
      status = chalk.blue(status);
    }
    txt += status;
  }
  return txt;
}
function centerText(text, n, length) {
  // Account for text length first
  n -= length || text.length;
  // Pad to be even
  if (n % 2 === 1) {
    text = ' ' + text;
  }
  // Round n to lowest number
  n = Math.floor(n / 2);
  // Make spacer
  const spacer = ' '.repeat(n);
  // Fill in text
  return spacer + text + spacer;
}
function speedText(speed) {
  let bits = speed * 8;
  const units = ['', 'K', 'M', 'G', 'T'];
  const places = [0, 1, 2, 3, 3];
  let unit = 0;
  while (bits >= 2000 && unit < 4) {
    unit++;
    bits /= 1000;
  }
  return `${bits.toFixed(places[unit])} ${units[unit]}bps`;
}
const frames = [
  '+---',
  '-+--',
  '--+-',
  '---+',
  '--+-',
  '-+--',
];
let lastChange = 0;
function makeSpinner() {
  if (Date.now() > lastChange + 30) {
    frames.unshift(frames.pop());
    lastChange = Date.now();
  }
  return frames[0];
}
const empty = () => console.log('│' + ' '.repeat(width * 3) + '│');
console.log();
console.log('┌' + '─'.repeat(width * 3) + '┐');
empty();
console.draft('│' + ' '.repeat(width * 3) + '│');
empty();
header = console.draft();
speeds = console.draft();
empty();
empty();
console.log('└' + '─'.repeat(width * 3) + '┘');
console.log();
console.log();
updateLines();
const options = {};
let paramError = null;
for (let i = 2; i < process.argv.length; i++) {
  const arg = process.argv[i];
  const next = process.argv[i + 1];
  if (i >= 2) {
    if (arg === '--help' || arg === '-h') {
      console.log(`Usage: ${process.argv[1]} [-h|--help] [--accept-license] [--accept-gdpr] [--server-id <id>] [--source-ip <ip>]`);
      console.log('-h  --help            Help');
      console.log('    --accept-license  Accept the Ookla EULA, TOS and Privacy policy. ');
      console.log('    --accept-gdpr     Accepts the Ookla GDPR terms. ');
      console.log('                      The terms only need to be accepted once.');
      console.log('    --server-id <id>  Test using a specific server by Ookla server ID');
      console.log('    --source-ip <ip>  Test a specific network interface identified by local IP');
      process.exit(0);
    } else if (arg === '--accept-license') {
      options.acceptLicense = true;
    } else if (arg === '--accept-gdpr') {
      options.acceptGdpr = true;
    } else if (arg === '--server-id') {
      if (next !== undefined) {
        i++;
        options.serverId = next;
      } else {
        paramError = 'Error: bad parameters';
      }
    } else if (arg === '--source-ip') {
      if (next !== undefined) {
        i++;
        options.sourceIp = next;
      } else {
        paramError = 'Error: bad parameters';
      }
    }
  }
}
if (paramError) {
  console.error();
  console.error(chalk.red(paramError));
  console.error();
  process.exit(1);
}
(async () => {
  try {
    setInterval(updateLines, 100);
    await speedTest({
      ...options,
      progress: event => {
        const content = event[event.type] || {};
        switch (event.type) {
          case 'ping':
            step = 'Ping';
            statuses.Ping = content.latency.toFixed(1) + ' ms';
            break;
          case 'download':
            statuses.Download = speedText(content.bandwidth);
            step = 'Download';
            break;
          case 'upload':
            statuses.Upload = speedText(content.bandwidth);
            step = 'Upload';
            break;
        }
      }
    });
    step = 'Finished';
    updateLines();
  } catch (err) {
    if (err.message.test(/acceptLicense/)) {
      console.error(chalk.red(err.message.replace('acceptLicense: true', '--accept-license')));
    } else {
      console.error(chalk.red(err.message.replace('acceptGdpr: true', '--accept-gdpr')));
    }
    process.exit(1);
  } finally {
    process.exit(0);
  }
})();