isaacgr/jaysonic

View on GitHub
bin/client

Summary

Maintainability
Test Coverage
#!/usr/bin/env node

const package = require("../package.json");
const Jaysonic = require("../lib");
const program = require("commander");
const fs = require("fs");

const zip = (rows) => rows[0].map((_, c) => rows.map((row) => row[c]));
const commaSeparated = (value) => {
  return value.split(",");
};
const formatParams = (value) => {
  return JSON.parse(`[${value.replace(/'/g, '"')}]`);
};

const formatJson = (value) => {
  return JSON.parse(value.replace(/'/g, '"'));
};

const usageString = `--host 192.168.2.1 --port 8555 --method hello --params '["foo"]'

Making requests with multiple methods is supported with comma separated values, i.e.

--method hello,hi
or
--notification goodbye,seeya
or
--subscribe update,anotherupdate

Params can also be comma separated, these should be one-to-one lined up with the method or notification, i.e.

--method request1,request2 --params '["params1"],["params2"]'

Request methods cannot be sent with notifications, however subscriptions can be made along side either, i.e.

--method hello --subscribe some.update
or
--notification bye --subscribe someother.update
`;

program
  .version(package.version)
  .name("jaysonic-client")
  .usage(usageString)
  .option(
    "-c, --client-type <string>",
    "Type of client (tcp, ws, http, https)",
    "tcp",
    String
  )
  .option(
    "-m, --method <string>",
    "Method name for request. Comma separate method names to do more than one at a time.",
    commaSeparated
  )
  .option(
    "-s, --subscribe <string>",
    "Method name to subscribe to. Comma separate method names to do more than one at a time.",
    commaSeparated
  )
  .option(
    "-n, --notify <string>",
    "Method name for notification. Comma separate method names to do more than one at a time.",
    commaSeparated
  )
  .option(
    "--params <object>",
    "Array or object to use as parameters. Comma separate params list or object to use with multiple request or notification methods.",
    formatParams
  )
  .option("-i, --host <string>", "Host IP of the server", "127.0.0.1", String)
  .option("-p, --port <number>", "Port to connect to", Number)
  .option("-u, --path <string>", "Path for ws or http client", "/", String)
  .option("--headers <string>", "Headers for http request.", formatJson)
  .option(
    "-d, --delimiter <string>",
    "Delimiter to use for the request. For example: Use $'\\n' as cli syntax for escape characters.",
    "\n"
  )
  .option("-t, --timeout <number>", "Response timeout in seconds", 30, Number)
  .option(
    "--connection-timeout <number>",
    "Connection timeout in milliseconds",
    5000,
    Number
  )
  .option(
    "-r, --retries <number>",
    "Number of connection retry attempts",
    2,
    Number
  )
  .option("-v, --jsonrpc-version <number>", "JSON-RPC version (1 or 2)", 2)
  .option("-w, --write <string>", "Write output to file", String)
  .parse(process.argv);

const getClient = () => {
  const programOptions = {
    host: program.host,
    port: program.port,
    path: program.path,
    headers: program.headers,
    delimiter: program.delimiter,
    timeout: program.timeout,
    connectionTimeout: program.connectionTimeout,
    retries: program.retries,
    version: program.jsonrpcVersion
  };
  let client;
  if (program.clientType === "ws") {
    const options = {
      url: `ws://${program.host}:${program.port}${program.path}`
    };
    client = new Jaysonic.client.ws({
      ...programOptions,
      ...options
    });
  } else if (program.clientType === "http" || program.clientType === "https") {
    const options = {
      host: program.host,
      port: program.port,
      path: program.path,
      scheme: program.clientType
    };
    client = new Jaysonic.client.http({
      ...programOptions,
      ...options
    });
  } else if (program.clientType === "tcp") {
    const options = { host: program.host, port: program.port };
    client = new Jaysonic.client.tcp({
      ...programOptions,
      ...options
    });
  }
  return client;
};

const makeRequests = async () => {
  if (program.clientType === "http" && program.subscribe) {
    process.stderr.write("HTTP client does not support subscriptions." + "\n");
    process.exit(-1);
  }
  if (!program.method && !program.notify && !program.subscribe) {
    process.stderr.write(
      "No method, notification or subscription request provided." + "\n"
    );
    process.exit(-1);
  }
  if (program.method) {
    const requests = zip([program.method, program.params || []]);
    for (let request of requests) {
      await sendRequest(request[0], request[1]);
    }
  } else if (program.notify) {
    const requests = zip([program.notify, program.params || []]);
    for (let request of requests) {
      await sendNotification(request[0], request[1]);
    }
  }
  if (program.subscribe) {
    for (let subscription of program.subscribe) {
      client.subscribe(subscription, handleSubscription);
    }
  } else {
    process.exit(0);
  }
};

const handleSubscription = (result) => {
  if (program.write) {
    fs.appendFileSync(program.write, JSON.stringify(result) + "\n");
  } else {
    process.stdout.write(JSON.stringify(result) + "\n");
  }
};

const sendRequest = async (method, params) => {
  try {
    const result = await client.request().send(method, params);
    if (program.write) {
      fs.writeFileSync(program.write, JSON.stringify(result));
    } else {
      process.stdout.write(JSON.stringify(result, null, 2) + "\n");
    }
  } catch (error) {
    if (program.write) {
      fs.writeFileSync(program.write, JSON.stringify(error));
    } else {
      process.stderr.write(JSON.stringify(error, null, 2) + "\n");
    }
  }
};

const sendNotification = async (method, params) => {
  if (program.clientType === "http" || program.clientType === "https") {
    try {
      const result = await client.request().notify(method, params);
      if (program.write) {
        fs.writeFileSync(program.write, JSON.stringify(result.body));
      } else {
        process.stdout.write(JSON.stringify(result.body, null, 2) + "\n");
      }
    } catch (error) {
      if (program.write) {
        fs.writeFileSync(program.write, error.message);
      } else {
        process.stderr.write(error.message + "\n");
      }
    }
  } else {
    return client.request().notify(method, params);
  }
};

const client = getClient();
if (program.clientType === "http" || program.clientType === "https") {
  makeRequests();
} else {
  client
    .connect()
    .then(() => {
      makeRequests();
    })
    .catch((error) => {
      process.stderr.write(JSON.stringify(error, null, 2) + "\n");
      process.exit(-1);
    });
}