UNPKG

5.34 kBJavaScriptView Raw
1/**
2 * Relays log messages from a Windows Phone emulator or device.
3 *
4 * @module logrelay
5 *
6 * @copyright
7 * Copyright (c) 2014 by Appcelerator, Inc. All Rights Reserved.
8 *
9 * @license
10 * Licensed under the terms of the Apache Public License.
11 * Please see the LICENSE included with this distribution for details.
12 */
13
14const
15 appc = require('node-appc'),
16 EventEmitter = require('events').EventEmitter,
17 net = require('net'),
18 util = require('util'),
19 uuid = require('uuid'),
20 __ = appc.i18n(__dirname).__;
21
22module.exports = LogRelay;
23
24/**
25 * Creates LogRelay object.
26 *
27 * @class
28 * @classdesc Hook registry and dispatcher.
29 * @extends EventEmitter
30 * @constructor
31 *
32 * @param {Object} [opts] - An object containing various settings.
33 * @param {Boolean} [opts.includeInternalIPAddresses=true] - When true, detects internal IP addresses including the local loopback interface.
34 * @param {String} [opts.serverToken] - A unique token that describes the computer where the LogRelay is running.
35 * @param {Number} [opts.tcpPort=8666] - The port to listen for the app to connect and received log messages.
36 */
37function LogRelay(opts) {
38 opts || (opts = {});
39
40 // the token is used by the app to try and find the correct log relay server
41 this.serverToken = opts.serverToken || uuid.v4();
42
43 this.includeInternalIPAddresses = opts.includeInternalIPAddresses !== void 0 ? opts.includeInternalIPAddresses : true;
44
45 this.ipAddressList = [];
46 this.tcpPort = opts.tcpPort || 8666;
47 this.tcpServer = null;
48}
49
50util.inherits(LogRelay, EventEmitter);
51
52/**
53 * Starts the TCP log relay server.
54 *
55 * @param {Object} [options] - An object containing various settings.
56 * @param {Function} [callback(err)] - A function that is called after the log relay server starts.
57 *
58 * @emits module:logrelay#connection
59 * @emits module:logrelay#disconnect
60 * @emits module:logrelay#error
61 * @emits module:logrelay#message
62 * @emits module:logrelay#started
63 */
64LogRelay.prototype.start = function start(options, callback) {
65 if (typeof options === 'function') {
66 callback = options;
67 options = {};
68 } else if (!options) {
69 options = {};
70 }
71 typeof callback === 'function' || (callback = function () {});
72
73 var self = this;
74
75 appc.net.interfaces(function (ifaces) {
76 Object.keys(ifaces).forEach(function (name) {
77 ifaces[name].ipAddresses.forEach(function (ip) {
78 if (/IPv4/i.test(ip.family) && (!ip.internal || self.includeInternalIPAddresses === void 0 || self.includeInternalIPAddresses)) {
79 self.ipAddressList.push(ip.address);
80 }
81 });
82 });
83
84 // because the tcp server is sensitive to port collisions, we basically create this loop
85 // that will continue to try ports until it finds an open port
86 (function startTCPServer() {
87 // create and start the tcp server
88 if (self.tcpServer) {
89 try {
90 self.tcpServer.close();
91 } catch (e) {}
92 self.tcpServer = null;
93 }
94
95 var tcpServer = self.tcpServer = net.createServer(handleLogConnection);
96
97 tcpServer.on('error', function (e) {
98 if (e.code == 'EADDRINUSE') {
99 self.emit('log-debug', __('Log relay failed to bind to port %s, trying port %s', self.tcpPort, self.tcpPort + 1));
100 self.tcpPort++;
101 startTCPServer();
102 } else {
103 self.stop();
104 var ex = new Error(__('TCP server error: %s', e.toString()));
105 self.emit('error', ex);
106 callback(ex);
107 }
108 });
109
110 tcpServer.listen(self.tcpPort, function () {
111 self.emit('log-debug', __('Log relay listening on port %s on %s', self.tcpPort, self.ipAddressList.join(', ')));
112 self.emit('started', this);
113 callback(null, this);
114 });
115 }());
116
117 function emitLines(lines) {
118 var len = lines.length;
119
120 // if the last line is empty, then it's probably not wanted
121 // note that we only want to strip the last empty line, so we can't use trim()
122 if (len > 0 && lines[len - 1] === '') {
123 len--;
124 }
125
126 for (var i = 0; i < len; i++) {
127 self.emit('message', lines[i]);
128 }
129 }
130
131 function handleLogConnection(conn) {
132 self.emit('connection', conn);
133
134 var initialized = false,
135 buffer = '',
136 timer;
137
138 conn.on('data', function (data) {
139 clearTimeout(timer);
140
141 var lines = (buffer + data.toString()).split('\n');
142
143 if (!initialized) {
144 if (lines[0] !== self.serverToken) {
145 // bad request, hang up
146 conn.destroy();
147 return;
148 }
149
150 lines.splice(0, 1);
151 initialized = true;
152 }
153
154 // safe the last line in case it's incomplete
155 buffer = lines.pop();
156
157 // output what we have so far
158 emitLines(lines);
159
160 // we wait 2 seconds for more data before we just flush the buffer
161 timer = setTimeout(function () {
162 emitLines(buffer.split('\n'));
163 buffer = '';
164 }, 2000);
165 });
166
167 conn.on('close', function () {
168 // flush buffer
169 emitLines(buffer.split('\n'));
170 buffer = '';
171
172 self.emit('disconnect', conn);
173 });
174
175 conn.on('error', function () {});
176 };
177 });
178};
179
180/**
181 * Stops the UDP beacon and TCP log relay servers.
182 *
183 * @emits module:logrelay#stopped
184 */
185LogRelay.prototype.stop = function stop() {
186 if (this.tcpServer) {
187 this.tcpServer.close();
188 this.tcpServer = null;
189 }
190
191 this.emit('stopped');
192};