UNPKG

11.2 kBJavaScriptView Raw
1"use strict";
2
3/**
4 * Module dependencies.
5 */
6
7var _ = require("lodash")
8 , intercept = require("./intercept")
9 , Session = require("./session")
10 , Firewall = require("./firewall")
11 , VantageUtil = require("./util")
12 , stripAnsi = require("strip-ansi")
13 ; require("native-promise-only")
14 ;
15
16/**
17 * Initialize a new `VantageServer` instance.
18 *
19 * @param {Vantage} parent
20 * @return {VantageServer}
21 * @api public
22 */
23
24function VantageServer(parent) {
25 if (!(this instanceof VantageServer)) { return new VantageServer(); }
26 this._hooked = false;
27
28 // Sessions are created when you use
29 // vantage externally to remotely connect
30 // to this running instance of vantage.
31 // Every connection (a socket.io connection)
32 // is stored as a Session object.
33 this.sessions = [];
34
35 this.parent = parent;
36 return this;
37}
38
39/**
40 * VantageServer prototype.
41 */
42
43var vantageServer = VantageServer.prototype;
44
45/**
46 * Expose `VantageServer`.
47 */
48
49exports = module.exports = VantageServer;
50
51/**
52 * Gets vantage started as a server.
53 *
54 * @param {App} app
55 * @param {Object} options
56 * @return {VantageServer}
57 * @api public
58 */
59
60vantageServer.init = function(app, options, cb) {
61 var self = this;
62
63 // If options is a function, we think it's
64 // a callback.
65 cb = (_.isFunction(options)) ? options
66 : (cb || function(){});
67
68 // If a port is passed in instead of options,
69 // accept that by converting it to a filled object.
70 // Otherwise, default to an empty object.
71 options = (options && _.isFunction(options)) ? {}
72 : (options && !_.isObject(options) && !isNaN(options)) ? ({ port: options })
73 : (!options) ? {}
74 : options;
75
76 // If a port was passed as the 'app', assign it
77 // as the port instead.
78 options.port =
79 (app && !_.isObject(app) && !isNaN(app)) ? app : options.port;
80
81 // If only a port was passed as the app, make the app
82 // object a blank function.
83 app =
84 (app && !_.isObject(app) && !isNaN(app)) ? function(){} : app;
85
86 var appIs =
87 (_.isFunction(app)) ? "callback" :
88 (_.isObject(app) && _.isFunction(app.callback)) ? "koa" :
89 (_.isObject(app) && _.isFunction(app.connection)) ? "hapi" :
90 "";
91
92 options = _.defaults(options, {
93 port: 80,
94 ssl: false,
95 logActivity: false
96 });
97
98
99 var appCallback =
100 (appIs === "callback") ? app :
101 (appIs === "koa") ? app.callback() :
102 (appIs === "hapi") ? true :
103 void 0;
104
105 if (!appCallback) {
106 throw new Error("Unsupported HTTP Server passed into Vantage.");
107 }
108
109 if (appIs === "hapi") {
110 this.server = app;
111 this.server.connection({ port: options.port });
112 this.io = require("socket.io")(this.server.listener);
113 } else {
114 var type = (options.ssl) ? "https" : "http";
115 if (type === "http") {
116 this.server = require(type).createServer(appCallback);
117 } else {
118 this.server = require(type).createServer(options, appCallback);
119 }
120 this.io = require("socket.io")(this.server);
121 this.server.listen(options.port);
122 }
123
124 this._port = options.port;
125 this._logActivity = options.logActivity;
126
127 this.io.set("authorization", function(handshakeData, accept){
128
129 var address = handshakeData.connection._peername;
130 var valid = self.firewall.valid(address);
131 var query = handshakeData._query;
132 var id = query.id || void 0;
133 var ssnId = query.sessionId || void 0;
134
135 // If Vantage has the same unique ID, don't let
136 // it connect to itself.
137 if (id && (id === self.parent.session.id)) {
138 return accept("You can't connect to yourself.", false);
139 }
140 if (_.pluck(self.sessions, "id").indexOf(ssnId) > -1) {
141 return accept("You have already connected to this instance.", false);
142 }
143
144 if (!valid) {
145 return accept("IP Not Allowed: " + address, false);
146 } else {
147 return accept(void 0, true);
148 }
149
150 });
151
152 this.firewall = new Firewall();
153 this.parent.firewall = this.firewall;
154
155 this.listen(cb);
156
157 this.hook(function(txt) {
158 for (var i = 0; i < self.sessions.length; ++i) {
159 if (self.sessions[i].pipe) {
160 // Inquirer's prompt cleaning seems to randomly
161 // spit ANSI code, causing unwanted extra lines.
162 // So if we just get ANSI, ignore.
163 if (!(txt && txt.length > 1 && stripAnsi(txt).length === 0)) {
164 self.parent._send("vantage-stdout-downstream", "downstream", { value: txt, sessionId: self.sessions[i].id });
165 }
166 }
167 }
168 return txt;
169 });
170
171 return this;
172};
173
174/**
175 * Creates server socket connections and
176 * registers all events.
177 *
178 * @return {VantageServer}
179 * @api private
180 */
181
182vantageServer.listen = function(cb) {
183 var self = this;
184 cb = cb || function(){};
185
186 this.io.on("connection", function(socket) {
187
188 var query = socket.handshake.query;
189 var ssn = new Session({
190 local: false,
191 parent: self.parent,
192 id: query.sessionId,
193 authenticating: true,
194 authenticated: false,
195 host: socket.handshake.headers.host,
196 address: socket.handshake.address
197 });
198
199 ssn.server = socket;
200 self.sessions.push(ssn);
201
202 // Listens for an event, authenticating
203 // the session first.
204 function on(str, opts, cbk) {
205 cbk = (_.isFunction(opts)) ? opts : cbk;
206 cbk = cbk || function() {};
207 opts = opts || {};
208 ssn.server.on(str, function() {
209 if (!ssn.server || (!ssn.authenticating && !ssn.authenticated)) {
210 //console.log("Not Authenticated. Closing Session.", ssn.authenticating, ssn.authenticated);
211 self.parent._send("vantage-close-downstream", "downstream", { sessionId: ssn.id });
212 return;
213 }
214 cbk.apply(self, arguments);
215 });
216 }
217
218 on("vantage-keypress-upstream", function(data) {
219 self.parent._proxy("vantage-keypress-upstream", "upstream", data).then(function(){
220 if ((["up", "down", "tab"].indexOf(data.key) > -1)) {
221 var sessn = self.parent.getSessionById(data.sessionId);
222 sessn.getKeypressResult(data.key, data.value, function(err, response) {
223 self.parent._send("vantage-keypress-downstream", "downstream", { value: (err || response), sessionId: sessn.id });
224 });
225 } else {
226 self.parent._histCtr = 0;
227 }
228 });
229 });
230
231 on("vantage-command-upstream", function(data) {
232 self.parent._proxy("vantage-command-upstream", "upstream", data).then(function() {
233 if (data.command) {
234 var response = {
235 command: data.command,
236 completed: true,
237 error: void 0,
238 data: arguments,
239 sessionId: data.sessionId
240 };
241 var execute = function() {
242 return new Promise(function(resolve, reject){
243 var cmd = {
244 command: data.command,
245 args: data.args,
246 resolve: resolve,
247 reject: reject,
248 session: self.parent.getSessionById(data.sessionId),
249 callback: function() {
250 var args = VantageUtil.fixArgsForApply(arguments);
251 response.data = args;
252 if (args[0] !== undefined) {
253 response.error = args[0] || args[1];
254 }
255 self.parent.emit("server_command_executed", response);
256 self.parent._send("vantage-command-downstream", "downstream", response);
257 }
258 };
259 self.parent._exec(cmd);
260 });
261 };
262 self.parent.emit("server_command_received", {
263 command: data.command
264 });
265
266 try {
267 execute().then(function() {
268 response.data = arguments;
269 self.parent.emit("server_command_executed", response);
270 self.parent._send("vantage-command-downstream", "downstream", response);
271 }).catch(function(error){
272 response.error = error;
273 response.data = void 0;
274 self.parent.emit("server_command_error", response);
275 self.parent._send("vantage-command-downstream", "downstream", response);
276 });
277 } catch(e) {
278 console.log("Error executing remote command: ", e);
279 }
280 }
281 });
282 });
283
284 on("vantage-heartbeat-upstream", function(data) {
285 self.parent._proxy("vantage-heartbeat-upstream", "upstream", data).then(function() {
286 self.parent._send("vantage-heartbeat-downstream", "downstream", {
287 delimiter: self.parent._delimiter,
288 sessionId: ssn.id
289 });
290 });
291 });
292
293 on("vantage-close-upstream", function(data) {
294 self.parent._proxy("vantage-close-upstream", "upstream", data).then(function() {
295 self.parent._send("vantage-close-downstream", "downstream", { sessionId: ssn.id });
296 });
297 });
298
299 on("vantage-prompt-upstream", function(data) {
300 self.parent._proxy("vantage-prompt-upstream", "upstream", data).then(function() {
301 self.parent.emit("vantage-prompt-upstream", data);
302 });
303 });
304
305 on("vantage-auth-upstream", function(data) {
306 self.parent._proxy("vantage-auth-upstream", "upstream", data).then(function() {
307 self.parent._authenticate(data, function(err, authenticated) {
308 self.parent._send("vantage-auth-downstream", "downstream", { sessionId: ssn.id, error: err, authenticated: authenticated });
309 });
310 });
311 });
312
313 ssn.server.on("disconnect", function(data) {
314
315 self.parent.emit("server_disconnect", data);
316
317 var nw = [];
318 for (var i = 0; i < self.sessions.length; ++i) {
319 if (self.sessions[i].id === ssn.id) {
320 //
321 if (self.sessions[i].client !== undefined) {
322 self.sessions[i].client.close();
323 }
324 delete self.sessions[i];
325 } else {
326 nw.push(self.sessions[i]);
327 }
328 }
329 self.sessions = nw;
330 });
331
332 if (self.parent._banner) {
333 self.parent._send("vantage-banner-downstream", "downstream", { banner: self.parent._banner, sessionId: ssn.id });
334 }
335
336 self.parent._send("vantage-heartbeat-downstream", "downstream", { delimiter: self.parent._delimiter, sessionId: ssn.id });
337
338 self.parent.emit("server_connection", socket);
339 cb(socket);
340 });
341
342 return this;
343};
344
345/**
346 * Unhooks stdout capture.
347 *
348 * @return {VantageServer}
349 * @api public
350 */
351
352vantageServer.unhook = function() {
353 if (this._hooked && this._unhook !== undefined && this.sessions.length < 1) {
354 this._unhook();
355 this._hooked = false;
356 }
357 return this;
358};
359
360/**
361 * Hooks all stdout through a given function.
362 *
363 * @param {Function} fn
364 * @return {VantageServer}
365 * @api public
366 */
367
368vantageServer.hook = function(fn) {
369 if (this._hooked && this._unhook !== undefined) {
370 this.unhook();
371 }
372 this._unhook = intercept(fn);
373 this._hooked = true;
374 return this;
375};