chattervox
Version:
An AX.25 packet radio chat protocol with support for digital signatures and binary compression. Like IRC over radio waves 📡〰.
138 lines (124 loc) • 4.42 kB
text/typescript
import * as fs from 'fs'
import * as path from 'path'
import * as os from 'os'
import { isCallsignSSID, isCallsign, isSSID } from './utils'
import { Keystore } from './Keystore'
export interface Config {
readonly version: number,
callsign: string,
ssid: number,
keystoreFile: string,
kissPort: string,
kissBaud: number,
feedbackDebounce: number,
signingKey?: string,
}
export const defaultChattervoxDir = path.join(os.homedir(), '.chattervox')
export const defaultConfigPath = path.join(defaultChattervoxDir, 'config.json')
export const defaultKeystorePath = path.join(defaultChattervoxDir, 'keystore.json')
export const defaultConfig: Config = {
version: 3,
callsign: 'N0CALL',
ssid: 0,
keystoreFile: defaultKeystorePath,
kissPort: 'kiss://localhost:8001',
kissBaud: 9600,
feedbackDebounce: 20 * 1000,
}
/** Save a config file as JSON
* @function save
* @param {Config} config
* @param {string} configPath?
* @returns void
*/
export function save(config: Config, configPath?: string): void {
validate(config)
const path = typeof configPath === 'string' ? configPath : defaultConfigPath
fs.writeFileSync(path, JSON.stringify(config, null, 4))
}
/** Load a config file
* @function load
* @param {string} configPath?
* @returns Config
*/
export function load(configPath?: string): Config {
const path = typeof configPath === 'string' ? configPath : defaultConfigPath
const conf: Config = JSON.parse(fs.readFileSync(path).toString('utf8'))
if (migrate(conf)) save(conf, configPath)
validate(conf)
return conf
}
/**
* Update an older config to a newer one. Transforms the config object in place.
* @function migrate
* @param {Config} config
* @returns boolean True if the config was changed
*/
export function migrate(config: Config): boolean {
let changed = false
// this change was made in config v3
if (typeof config.feedbackDebounce === 'undefined') {
config.feedbackDebounce = defaultConfig.feedbackDebounce
changed = true
}
return changed
}
/** Check if the config file (or any file) exists
* @function exists
* @param {string} configPath?
* @returns boolean
*/
export function exists(configPath?: string): boolean {
const path = typeof configPath === 'string' ? configPath : defaultConfigPath
return fs.existsSync(path)
}
/**
* @function validate
* @param config
* @throws TypeError
*/
export function validate(config: Config): void {
if (typeof config !== 'object') {
throw TypeError('config is not an object')
} else if (typeof config.version !== 'number') {
throw TypeError('version must be a number type')
} else if (typeof config.callsign !== 'string') {
throw TypeError('callsign must be a string type')
} else if (isCallsignSSID(config.callsign)) {
throw TypeError('callsign must be a valid callsign excluding an SSID')
} else if (!isCallsign(config.callsign)) {
throw TypeError('callsign must be a valid callsign')
} else if (!isSSID(config.ssid)) {
throw TypeError('ssid must be a number between 0 and 15')
} else if (typeof config.kissPort !== 'string') {
throw TypeError('kissPort must be a string type')
} else if (typeof config.kissBaud !== 'number') {
throw TypeError('kissBaud must be a number type')
} else if (typeof config.keystoreFile !== 'string') {
throw TypeError('keystoreFile must be a string type')
} else if (typeof config.signingKey !== 'undefined'
&& config.signingKey !== null
&& typeof config.signingKey !== 'string') {
throw TypeError('signingKey must be a string or null if it is defined')
} else if (config.feedbackDebounce !== null
&& typeof config.feedbackDebounce !== 'number') {
throw TypeError('feedbackDebounce must be a number or null')
}
}
/**
* Create new chattervox directory, config file, and keystore ONLY if they do
* not already exist.
* @function init
*/
export function init(): void {
if (!fs.existsSync(defaultChattervoxDir)) {
fs.mkdirSync(defaultChattervoxDir)
}
if (!exists(defaultConfigPath)) {
save(defaultConfig)
}
if (!exists(defaultKeystorePath)) {
// simply creating a new keystore object with save the store
new Keystore(defaultKeystorePath)
}
}