4 * ws: a node.js websocket client
5 * Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
9 const EventEmitter = require('events');
10 const fs = require('fs');
11 const program = require('commander');
12 const read = require('read');
13 const readline = require('readline');
14 const tty = require('tty');
15 const WebSocket = require('ws');
18 * InputReader - processes console input.
20 * @extends EventEmitter
22 class Console extends EventEmitter {
26 this.stdin = process.stdin;
27 this.stdout = process.stdout;
29 this.readlineInterface = readline.createInterface(this.stdin, this.stdout);
31 this.readlineInterface
33 this.emit('line', data);
39 this._resetInput = () => {
63 this.readlineInterface.prompt();
66 print(type, msg, color) {
70 if (program.execute) color = type = '';
71 else if (!program.color) color = '';
73 this.stdout.write(color + type + msg + Console.Colors.Default + '\n');
75 } else if (type === Console.Types.Incoming) {
76 this.stdout.write(msg + '\n');
78 // is a control message and we're not in a tty... drop it.
84 this.stdout.write('\u001b[2K\u001b[3D');
89 this.stdin.on('keypress', this._resetInput);
93 this.stdin.removeListener('keypress', this._resetInput);
97 function collect(val, memo) {
105 * The actual application
107 const version = require('../package.json').version;
111 .usage('[options] (--listen <port> | --connect <url>)')
112 .option('-l, --listen <port>', 'listen on port')
113 .option('-c, --connect <url>', 'connect to a websocket server')
114 .option('-p, --protocol <version>', 'optional protocol version')
115 .option('-o, --origin <origin>', 'optional origin')
116 .option('-x, --execute <command>', 'execute command after connecting')
117 .option('-w, --wait <seconds>', 'wait given seconds after executing command')
118 .option('--host <host>', 'optional host')
119 .option('-s, --subprotocol <protocol>', 'optional subprotocol', collect, [])
120 .option('-n, --no-check', 'Do not check for unauthorized certificates')
122 '-H, --header <header:value>',
123 'Set an HTTP header. Repeat to set multiple. (--connect only)',
128 '--auth <username:password>',
129 'Add basic HTTP authentication header. (--connect only)'
131 .option('--ca <ca>', 'Specify a Certificate Authority (--connect only)')
132 .option('--cert <cert>', 'Specify a Client SSL Certificate (--connect only)')
135 "Specify a Client SSL Certificate's key (--connect only)"
138 '--passphrase [passphrase]',
139 "Specify a Client SSL Certificate Key's passphrase (--connect only). " +
140 "If you don't provide a value, it will be prompted for."
142 .option('--no-color', 'Run without color')
145 'Enable slash commands for control frames ' +
146 '(/ping, /pong, /close [code [, reason]])'
148 .parse(process.argv);
150 if (program.listen && program.connect) {
151 console.error('\u001b[33merror: use either --listen or --connect\u001b[39m');
155 if (program.listen) {
156 const wsConsole = new Console();
160 const wss = new WebSocket.Server({ port: program.listen }, () => {
162 Console.Types.Control,
163 `listening on port ${program.listen} (press CTRL+C to quit)`,
169 wsConsole.on('close', () => {
174 wsConsole.on('line', data => {
181 wss.on('connection', newClient => {
182 if (ws) return newClient.terminate();
188 Console.Types.Control,
193 ws.on('close', code => {
195 Console.Types.Control,
196 `disconnected (code: ${code})`,
204 ws.on('error', err => {
205 wsConsole.print(Console.Types.Error, err.message, Console.Colors.Yellow);
208 ws.on('message', data => {
209 wsConsole.print(Console.Types.Incoming, data, Console.Colors.Blue);
213 wss.on('error', err => {
214 wsConsole.print(Console.Types.Error, err.message, Console.Colors.Yellow);
217 } else if (program.connect) {
220 const wsConsole = new Console();
222 const headers = program.header.reduce((acc, cur) => {
223 const i = cur.indexOf(':');
224 const key = cur.slice(0, i);
225 const value = cur.slice(i + 1);
231 headers.Authorization =
232 'Basic ' + Buffer.from(program.auth).toString('base64');
234 if (program.host) headers.Host = program.host;
235 if (program.protocol) options.protocolVersion = +program.protocol;
236 if (program.origin) options.origin = program.origin;
237 if (!program.check) options.rejectUnauthorized = program.check;
238 if (program.ca) options.ca = fs.readFileSync(program.ca);
239 if (program.cert) options.cert = fs.readFileSync(program.cert);
240 if (program.key) options.key = fs.readFileSync(program.key);
242 let connectUrl = program.connect;
243 if (!connectUrl.match(/\w+:\/\/.*$/i)) {
244 connectUrl = `ws://${connectUrl}`;
247 options.headers = headers;
248 const ws = new WebSocket(connectUrl, program.subprotocol, options);
250 ws.on('open', () => {
251 if (program.execute) {
252 ws.send(program.execute);
255 }, program.wait ? program.wait * 1000 : 2000);
258 Console.Types.Control,
259 'connected (press CTRL+C to quit)',
263 wsConsole.on('line', data => {
264 if (program.slash && data[0] === '/') {
265 const toks = data.split(/\s+/);
266 switch (toks[0].substr(1)) {
274 let closeStatusCode = 1000;
275 let closeReason = '';
276 if (toks.length >= 2) {
277 closeStatusCode = parseInt(toks[1]);
279 if (toks.length >= 3) {
280 closeReason = toks.slice(2).join(' ');
282 if (closeReason.length > 0) {
283 ws.close(closeStatusCode, closeReason);
285 ws.close(closeStatusCode);
292 'Unrecognized slash command.',
293 Console.Colors.Yellow
304 ws.on('close', code => {
305 if (!program.execute) {
307 Console.Types.Control,
308 `disconnected (code: ${code})`,
316 ws.on('error', err => {
317 wsConsole.print(Console.Types.Error, err.message, Console.Colors.Yellow);
321 ws.on('message', data => {
322 wsConsole.print(Console.Types.Incoming, data, Console.Colors.Blue);
325 ws.on('ping', () => {
327 Console.Types.Incoming,
333 ws.on('pong', () => {
335 Console.Types.Incoming,
341 wsConsole.on('close', () => {
347 if (program.passphrase === true) {
350 prompt: 'Passphrase: ',
354 (err, passphrase) => {
355 options.passphrase = passphrase;
359 } else if (typeof program.passphrase === 'string') {
360 options.passphrase = program.passphrase;