UNPKG

@tak-ps/node-tak

Version:

Lightweight JavaScript library for communicating with TAK Server

220 lines (179 loc) 6.75 kB
#!/usr/bin/env tsx import fs from 'node:fs/promises' import os from 'node:os'; import path from 'node:path'; import minimist from 'minimist'; import { Static } from '@sinclair/typebox'; import { CoTParser } from '@tak-ps/node-cot' import TAK from './index.js'; import { getPemFromP12 } from 'p12-pem' import TAKAPI, { CommandList } from './lib/api.js'; import { APIAuthPassword, APIAuthCertificate } from './lib/auth.js'; import Commands, { CommandConfig } from './lib/commands.js'; import { select, confirm, number, input, password, Separator } from '@inquirer/prompts'; const args = minimist(process.argv, { string: ['profile', 'format', 'auth'], }); const configPath = path.resolve(os.homedir(), './.tak.json'); let config: Static<typeof CommandConfig>; try { config = JSON.parse(String(await fs.readFile(configPath))); } catch (err) { if (err instanceof Error && err.message.includes('no such file or directory')) { const answer = await confirm({ message: 'No TAK Config file Detected, create one?' }); if (!answer) { throw new Error('No config file and not creating one'); } config = { version: 1, profiles: {} } as Static<typeof CommandConfig>; await fs.writeFile( configPath, JSON.stringify(config, null, 4) ) } else { throw err; } } if (!args.profile && config.profiles && Object.keys(config.profiles).length) { args.profile = await select({ message: 'Choose a profile', choices: [ { name: 'New Profile', value: '' }, new Separator(), ].concat(Object.keys(config.profiles).map((profile) => { return { name: profile, value: profile }; }), ) }); } if (!args.profile) { console.error('Create new TAK Server Profile:'); args.profile = await input({ message: 'Enter a name for this profile' }); const host = await input({ message: 'Server hostname', default: 'ops.example.com' }); let marti: number | undefined = 8443; let webtak: number | undefined = 443; let stream: number | undefined = 8089; do { marti = await number({ message: 'Marti API Port', default: marti }); } while (!marti || isNaN(marti)) do { webtak = await number({ message: 'WebTAK API Port', default: webtak }); } while (!webtak || isNaN(webtak)) do { stream = await number({ message: 'Streaming Port', default: stream }); } while (!stream || isNaN(stream)) if (!config.profiles) config.profiles = {}; config.profiles[args.profile] = { host, ports: { webtak, marti, stream } }; } if (args.profile && !config.profiles[args.profile]) { throw new Error(`Profile "${args.profile}" is not defined in config file`); } else if (!args.auth && !config.profiles[args.profile].auth) { const user = await input({ message: 'TAK Username' }); const pass = await password({ message: 'TAK Password' }); const api = await TAKAPI.init( new URL('https://' + config.profiles[args.profile].host + ':' + config.profiles[args.profile].ports.webtak), new APIAuthPassword(user, pass) ); const auth = await api.Credentials.generate(); config.profiles[args.profile].auth = { cert: auth.cert, key: auth.key, ca: auth.ca[0] } } let command = args._[2]; if (!command) { command = await select({ message: 'Choose a Command Group', choices: [{ name: 'stream', value: 'stream' }].concat(Object.keys(CommandList).map((profile) => { return { name: profile, value: profile }; })) }); } await fs.writeFile( configPath, JSON.stringify(config, null, 4) ) let auth = config.profiles[args.profile].auth; if (args.auth) { const password = process.env.TAK_P12_PASSWORD || await input({ message: 'P12 Container Password' }); const certs = getPemFromP12(args.auth, password) const cert = certs.pemCertificate .split('-----BEGIN CERTIFICATE-----') .join('-----BEGIN CERTIFICATE-----\n') .split('-----END CERTIFICATE-----') .join('\n-----END CERTIFICATE-----'); const key = certs.pemKey .split('-----BEGIN RSA PRIVATE KEY-----') .join('-----BEGIN RSA PRIVATE KEY-----\n') .split('-----END RSA PRIVATE KEY-----') .join('\n-----END RSA PRIVATE KEY-----'); auth = { cert, key } } if (!auth) throw new Error(`No Auth in ${args.profile} profile`); if (command === 'stream') { const tak = await TAK.connect( new URL('ssl://' + config.profiles[args.profile].host + ':' + config.profiles[args.profile].ports.stream), auth ); tak.on('cot', (cot) => { console.log(JSON.stringify(CoTParser.to_geojson(cot))); }); } else { if (!config.profiles[args.profile].auth) { throw new Error(`No Auth in ${args.profile} profile`); } const tak = new TAKAPI( new URL('https://' + config.profiles[args.profile].host + ':' + config.profiles[args.profile].ports.marti), new APIAuthCertificate(auth.cert, auth.key) ); const invokeTest = tak[CommandList[command]]; if (!invokeTest || !(invokeTest instanceof Commands)) { throw new Error(`${command} not found`); } const invoke = invokeTest as Commands; if (!args._[3] || args._[3] === 'help') { const subcommands = <T extends object>(obj: T) => Object.keys(obj) as Array<keyof T>; console.log(( [ 'Command:', ` tak ${command} <subcommand>`, 'SubCommands:', ].concat(subcommands(invoke.schema).map((subcommand) => { return ` ${String(subcommand)} - ${invoke.schema[subcommand].description}` }))).join('\n') ) } else { if (args.format && !invoke.schema[args._[3]]) { throw new Error(`Unsupported Subcommand: ${args._[3]}`); } else if (args.format && !invoke.schema[args._[3]].formats.includes(args.format)) { throw new Error(`tak ${command} ${args._[3]} does not support --format ${args.format}. Supported formats are: ${invoke.schema[args._[3]].formats.join(",")}`); } try { const res = await invoke.cli(args); if (typeof res === 'string') { console.log(res); } else { console.log(JSON.stringify(res, null, 4)); } } catch (err) { console.error(err); } } }