48f0889f3af31dce5fcf7e824e0e8ac3dc7163bf
[VSoRC/.git] / node_modules / .bin / wscat
1 #!/usr/bin/env node
2
3 /*!
4  * ws: a node.js websocket client
5  * Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
6  * MIT Licensed
7  */
8
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');
16
17 /**
18  * InputReader - processes console input.
19  *
20  * @extends EventEmitter
21  */
22 class Console extends EventEmitter {
23   constructor() {
24     super();
25
26     this.stdin = process.stdin;
27     this.stdout = process.stdout;
28
29     this.readlineInterface = readline.createInterface(this.stdin, this.stdout);
30
31     this.readlineInterface
32       .on('line', data => {
33         this.emit('line', data);
34       })
35       .on('close', () => {
36         this.emit('close');
37       });
38
39     this._resetInput = () => {
40       this.clear();
41     };
42   }
43
44   static get Colors() {
45     return {
46       Red: '\u001b[31m',
47       Green: '\u001b[32m',
48       Yellow: '\u001b[33m',
49       Blue: '\u001b[34m',
50       Default: '\u001b[39m'
51     };
52   }
53
54   static get Types() {
55     return {
56       Incoming: '< ',
57       Control: '',
58       Error: 'error: '
59     };
60   }
61
62   prompt() {
63     this.readlineInterface.prompt();
64   }
65
66   print(type, msg, color) {
67     if (tty.isatty(1)) {
68       this.clear();
69
70       if (program.execute) color = type = '';
71       else if (!program.color) color = '';
72
73       this.stdout.write(color + type + msg + Console.Colors.Default + '\n');
74       this.prompt();
75     } else if (type === Console.Types.Incoming) {
76       this.stdout.write(msg + '\n');
77     } else {
78       // is a control message and we're not in a tty... drop it.
79     }
80   }
81
82   clear() {
83     if (tty.isatty(1)) {
84       this.stdout.write('\u001b[2K\u001b[3D');
85     }
86   }
87
88   pause() {
89     this.stdin.on('keypress', this._resetInput);
90   }
91
92   resume() {
93     this.stdin.removeListener('keypress', this._resetInput);
94   }
95 }
96
97 function collect(val, memo) {
98   memo.push(val);
99   return memo;
100 }
101
102 function noop() {}
103
104 /**
105  * The actual application
106  */
107 const version = require('../package.json').version;
108
109 program
110   .version(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')
121   .option(
122     '-H, --header <header:value>',
123     'Set an HTTP header. Repeat to set multiple. (--connect only)',
124     collect,
125     []
126   )
127   .option(
128     '--auth <username:password>',
129     'Add basic HTTP authentication header. (--connect only)'
130   )
131   .option('--ca <ca>', 'Specify a Certificate Authority (--connect only)')
132   .option('--cert <cert>', 'Specify a Client SSL Certificate (--connect only)')
133   .option(
134     '--key <key>',
135     "Specify a Client SSL Certificate's key (--connect only)"
136   )
137   .option(
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."
141   )
142   .option('--no-color', 'Run without color')
143   .option(
144     '--slash',
145     'Enable slash commands for control frames ' +
146       '(/ping, /pong, /close [code [, reason]])'
147   )
148   .parse(process.argv);
149
150 if (program.listen && program.connect) {
151   console.error('\u001b[33merror: use either --listen or --connect\u001b[39m');
152   process.exit(-1);
153 }
154
155 if (program.listen) {
156   const wsConsole = new Console();
157   wsConsole.pause();
158
159   let ws = null;
160   const wss = new WebSocket.Server({ port: program.listen }, () => {
161     wsConsole.print(
162       Console.Types.Control,
163       `listening on port ${program.listen} (press CTRL+C to quit)`,
164       Console.Colors.Green
165     );
166     wsConsole.clear();
167   });
168
169   wsConsole.on('close', () => {
170     if (ws) ws.close();
171     process.exit(0);
172   });
173
174   wsConsole.on('line', data => {
175     if (ws) {
176       ws.send(data);
177       wsConsole.prompt();
178     }
179   });
180
181   wss.on('connection', newClient => {
182     if (ws) return newClient.terminate();
183
184     ws = newClient;
185     wsConsole.resume();
186     wsConsole.prompt();
187     wsConsole.print(
188       Console.Types.Control,
189       'client connected',
190       Console.Colors.Green
191     );
192
193     ws.on('close', code => {
194       wsConsole.print(
195         Console.Types.Control,
196         `disconnected (code: ${code})`,
197         Console.Colors.Green
198       );
199       wsConsole.clear();
200       wsConsole.pause();
201       ws = null;
202     });
203
204     ws.on('error', err => {
205       wsConsole.print(Console.Types.Error, err.message, Console.Colors.Yellow);
206     });
207
208     ws.on('message', data => {
209       wsConsole.print(Console.Types.Incoming, data, Console.Colors.Blue);
210     });
211   });
212
213   wss.on('error', err => {
214     wsConsole.print(Console.Types.Error, err.message, Console.Colors.Yellow);
215     process.exit(-1);
216   });
217 } else if (program.connect) {
218   const options = {};
219   const cont = () => {
220     const wsConsole = new Console();
221
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);
226       acc[key] = value;
227       return acc;
228     }, {});
229
230     if (program.auth) {
231       headers.Authorization =
232         'Basic ' + Buffer.from(program.auth).toString('base64');
233     }
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);
241
242     let connectUrl = program.connect;
243     if (!connectUrl.match(/\w+:\/\/.*$/i)) {
244       connectUrl = `ws://${connectUrl}`;
245     }
246
247     options.headers = headers;
248     const ws = new WebSocket(connectUrl, program.subprotocol, options);
249
250     ws.on('open', () => {
251       if (program.execute) {
252         ws.send(program.execute);
253         setTimeout(() => {
254           ws.close();
255         }, program.wait ? program.wait * 1000 : 2000);
256       } else {
257         wsConsole.print(
258           Console.Types.Control,
259           'connected (press CTRL+C to quit)',
260           Console.Colors.Green
261         );
262
263         wsConsole.on('line', data => {
264           if (program.slash && data[0] === '/') {
265             const toks = data.split(/\s+/);
266             switch (toks[0].substr(1)) {
267               case 'ping':
268                 ws.ping(noop);
269                 break;
270               case 'pong':
271                 ws.pong(noop);
272                 break;
273               case 'close': {
274                 let closeStatusCode = 1000;
275                 let closeReason = '';
276                 if (toks.length >= 2) {
277                   closeStatusCode = parseInt(toks[1]);
278                 }
279                 if (toks.length >= 3) {
280                   closeReason = toks.slice(2).join(' ');
281                 }
282                 if (closeReason.length > 0) {
283                   ws.close(closeStatusCode, closeReason);
284                 } else {
285                   ws.close(closeStatusCode);
286                 }
287                 break;
288               }
289               default:
290                 wsConsole.print(
291                   Console.Types.Error,
292                   'Unrecognized slash command.',
293                   Console.Colors.Yellow
294                 );
295             }
296           } else {
297             ws.send(data);
298           }
299           wsConsole.prompt();
300         });
301       }
302     });
303
304     ws.on('close', code => {
305       if (!program.execute) {
306         wsConsole.print(
307           Console.Types.Control,
308           `disconnected (code: ${code})`,
309           Console.Colors.Green
310         );
311       }
312       wsConsole.clear();
313       process.exit();
314     });
315
316     ws.on('error', err => {
317       wsConsole.print(Console.Types.Error, err.message, Console.Colors.Yellow);
318       process.exit(-1);
319     });
320
321     ws.on('message', data => {
322       wsConsole.print(Console.Types.Incoming, data, Console.Colors.Blue);
323     });
324
325     ws.on('ping', () => {
326       wsConsole.print(
327         Console.Types.Incoming,
328         'Received ping',
329         Console.Colors.Blue
330       );
331     });
332
333     ws.on('pong', () => {
334       wsConsole.print(
335         Console.Types.Incoming,
336         'Received pong',
337         Console.Colors.Blue
338       );
339     });
340
341     wsConsole.on('close', () => {
342       ws.close();
343       process.exit();
344     });
345   };
346
347   if (program.passphrase === true) {
348     read(
349       {
350         prompt: 'Passphrase: ',
351         silent: true,
352         replace: '*'
353       },
354       (err, passphrase) => {
355         options.passphrase = passphrase;
356         cont();
357       }
358     );
359   } else if (typeof program.passphrase === 'string') {
360     options.passphrase = program.passphrase;
361     cont();
362   } else {
363     cont();
364   }
365 } else {
366   program.help();
367 }