UNPKG

contact

Version:

private, one-to-one or many-to-many command line chat

132 lines (117 loc) 3.92 kB
import Client from 'contact' import CliView from '../lib/cli-view.js' import Storage from './storage.js' import { streamReadText } from 'stream-read-all' import Transmission from 'contact-model/transmission' class CliApp { client storage view allCommands = [ '/help', '/users', '/rooms', '/clientSessions', '/roomSessions', '/members', '/nick', '/join', '/send' ] constructor (options = {}) { this.client = options.client || new Client({ Connection: options.Connection }) this.storage = options.storage || new Storage() this.view = options.view || new CliView() } async start (url, initialCommands) { const client = this.client const storage = this.storage const view = this.view try { this.connection = await client.connect(url) } catch (err) { if (err?.cause?.code === 'ABORT_ERR') { console.error('Connection attempt timed out') } else if (['ERR_INVALID_URL', 'ECONNREFUSED'].includes(err?.cause?.code || err?.code)) { console.error(`Connection failed, check the URL and that the server is online. [${err?.cause?.code || err?.code}]`) } else { console.error(err) } await this.connection?.close() await client.close() return } await storage.init() view.init({ input: process.stdin, output: process.stdout, history: await storage.readHistory(), completer: line => { const hits = this.allCommands.filter(c => c.startsWith(line)) return [hits.length ? hits : this.allCommands, line] } }) client.addEventListener('close', async e => { const cause = e.detail await view.displayMessage(`Connection closed, cause: ${cause}`, { skipPrompt: true }) view.close() // console.log('Connection to relay closed, shutting down.') }) client.addEventListener('msg', async e => { const t = e.detail if (t.headers.unread) { await storage.writeUnread(t) const nt = Transmission.notification('unread-message', t.headers['client-room-session-id'], t.headers['room-id'], t.headers.from) await view.displayNotification(nt.headers) } else { const msg = await streamReadText(t.readable) await view.displayMessage(`${t.headers.from}: ${msg}`) } }) client.addEventListener('notification', async e => { await view.displayNotification(e.detail) }) view.addEventListener('line', async e => { this.onUserInput(e.detail) }) view.addEventListener('close', async () => { await this.connection.close() await client.close() }) view.addEventListener('history', async e => { await storage.writeHistory(e.detail) }) for (const command of initialCommands) { const res = await this.connection.tui(command.join(' ')) view.displayMessage(res) } } async onUserInput (line) { if (line.startsWith('/')) { const res = await this.connection.tui(line) if (res.status === 200) { if (res.command.startsWith('/join')) { const roomId = res.command.split(/\s+/)[1] this.view.setRoomId(roomId) const unreadTransmissions = await this.storage.readUnread(roomId) for (const t of unreadTransmissions) { const msg = await streamReadText(t.readable) this.view.displayMessage(`${t.headers.from}: ${msg}`) } } this.view.displayMessage(res) } else { this.view.displayMessage('Command failed') this.view.displayMessage(res) } } else { const res = await this.connection.tui('/send', Transmission.msg(line)) if (res.status === 200) { this.view.displayMessage(`${res.body.clientSession.nick}: ${line}`) } else { this.view.displayMessage(res) } } } } export default CliApp