@webos-tools/cli 3.0.4 → 3.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/.eslintignore +1 -1
  2. package/.eslintrc.js +52 -52
  3. package/APIs.js +79 -79
  4. package/CHANGELOG.md +128 -128
  5. package/LICENSE +201 -201
  6. package/README.md +218 -218
  7. package/bin/ares-config.js +199 -199
  8. package/bin/ares-device-info.js +30 -30
  9. package/bin/ares-device.js +219 -219
  10. package/bin/ares-generate.js +270 -270
  11. package/bin/ares-inspect.js +179 -179
  12. package/bin/ares-install.js +223 -223
  13. package/bin/ares-launch.js +318 -318
  14. package/bin/ares-log.js +255 -255
  15. package/bin/ares-novacom.js +223 -223
  16. package/bin/ares-package.js +336 -336
  17. package/bin/ares-pull.js +156 -156
  18. package/bin/ares-push.js +155 -155
  19. package/bin/ares-server.js +174 -174
  20. package/bin/ares-setup-device.js +520 -520
  21. package/bin/ares-shell.js +132 -132
  22. package/bin/ares.js +166 -166
  23. package/files/conf/ares.json +49 -49
  24. package/files/conf/command-service.json +73 -73
  25. package/files/conf/config.json +22 -22
  26. package/files/conf/ipk.json +30 -30
  27. package/files/conf/novacom-devices.json +35 -35
  28. package/files/conf/query/query-app.json +14 -14
  29. package/files/conf/query/query-hosted.json +18 -18
  30. package/files/conf/query/query-package.json +10 -10
  31. package/files/conf/query/query-service.json +6 -6
  32. package/files/conf/sdk.json +8 -8
  33. package/files/conf/template.json +57 -57
  34. package/files/conf/webos_emul +27 -27
  35. package/files/conf-base/env/sdk-ose.json +8 -8
  36. package/files/conf-base/env/sdk-tv.json +8 -8
  37. package/files/conf-base/profile/config-ose.json +21 -21
  38. package/files/conf-base/profile/config-tv.json +22 -22
  39. package/files/conf-base/query/query-app.json +14 -14
  40. package/files/conf-base/query/query-hosted.json +18 -18
  41. package/files/conf-base/query/query-package.json +10 -10
  42. package/files/conf-base/query/query-service.json +6 -6
  43. package/files/conf-base/template-conf/ose-templates.json +67 -67
  44. package/files/conf-base/template-conf/tv-sdk-templates.json +57 -57
  45. package/files/help/ares-config.help +43 -43
  46. package/files/help/ares-device.help +94 -94
  47. package/files/help/ares-generate.help +65 -65
  48. package/files/help/ares-inspect.help +70 -70
  49. package/files/help/ares-install.help +90 -90
  50. package/files/help/ares-launch.help +100 -100
  51. package/files/help/ares-log-pmlogd.help +84 -84
  52. package/files/help/ares-log.help +101 -101
  53. package/files/help/ares-novacom.help +68 -68
  54. package/files/help/ares-package.help +101 -101
  55. package/files/help/ares-pull.help +38 -38
  56. package/files/help/ares-push.help +38 -38
  57. package/files/help/ares-server.help +39 -39
  58. package/files/help/ares-setup-device.help +75 -75
  59. package/files/help/ares-shell.help +42 -42
  60. package/files/help/ares.help +47 -47
  61. package/files/help/readme.help +23 -23
  62. package/files/schema/ApplicationDescription.schema +319 -319
  63. package/files/schema/NovacomDevices.schema +61 -61
  64. package/files/templates/ose-sdk-templates/appinfo/appinfo.json +10 -10
  65. package/files/templates/ose-sdk-templates/bootplate-web/index.html +88 -88
  66. package/files/templates/ose-sdk-templates/hosted-webapp/index.html +13 -13
  67. package/files/templates/ose-sdk-templates/icon/icon.png +0 -0
  68. package/files/templates/ose-sdk-templates/js-service/helloclient.js +31 -31
  69. package/files/templates/ose-sdk-templates/js-service/helloworld_webos_service.js +188 -188
  70. package/files/templates/ose-sdk-templates/qml-app/main.qml +68 -68
  71. package/files/templates/ose-sdk-templates/qmlappinfo/appinfo.json +10 -10
  72. package/files/templates/ose-sdk-templates/serviceinfo/package.json +11 -11
  73. package/files/templates/ose-sdk-templates/serviceinfo/services.json +8 -8
  74. package/files/templates/tv-sdk-templates/appinfo/appinfo.json +10 -10
  75. package/files/templates/tv-sdk-templates/bootplate-web/index.html +58 -58
  76. package/files/templates/tv-sdk-templates/bootplate-web/webOSTVjs-1.2.10/LICENSE-2.0.txt +202 -202
  77. package/files/templates/tv-sdk-templates/hosted-webapp/index.html +14 -14
  78. package/files/templates/tv-sdk-templates/js-service/helloworld_service.js +39 -39
  79. package/files/templates/tv-sdk-templates/packageinfo/packageinfo.json +3 -3
  80. package/files/templates/tv-sdk-templates/serviceinfo/package.json +11 -11
  81. package/files/templates/tv-sdk-templates/serviceinfo/services.json +8 -8
  82. package/files/templates/tv-sdk-templates/webicon/icon.png +0 -0
  83. package/files/templates/tv-sdk-templates/webicon/largeIcon.png +0 -0
  84. package/lib/base/ares.html +40 -40
  85. package/lib/base/cli-appdata.js +290 -290
  86. package/lib/base/cli-control.js +44 -44
  87. package/lib/base/common-tools.js +29 -29
  88. package/lib/base/error-handler.js +265 -265
  89. package/lib/base/file-watcher.js +155 -155
  90. package/lib/base/help-format.js +147 -147
  91. package/lib/base/luna.js +178 -178
  92. package/lib/base/novacom.js +1191 -1191
  93. package/lib/base/sdkenv.js +59 -59
  94. package/lib/base/server.js +137 -137
  95. package/lib/base/setup-device.js +328 -328
  96. package/lib/base/version-tools.js +79 -79
  97. package/lib/device.js +1419 -1419
  98. package/lib/generator.js +377 -377
  99. package/lib/inspect.js +494 -494
  100. package/lib/install.js +463 -463
  101. package/lib/launch.js +605 -605
  102. package/lib/log.js +584 -584
  103. package/lib/package.js +2129 -2129
  104. package/lib/pull.js +231 -231
  105. package/lib/pusher.js +210 -210
  106. package/lib/session.js +74 -74
  107. package/lib/shell.js +193 -193
  108. package/lib/tar-filter-pack.js +62 -62
  109. package/lib/util/copy.js +31 -31
  110. package/lib/util/createFileName.js +40 -40
  111. package/lib/util/eof.js +30 -30
  112. package/lib/util/json.js +63 -63
  113. package/lib/util/merge.js +14 -14
  114. package/lib/util/objclone.js +40 -40
  115. package/lib/util/spinner.js +37 -37
  116. package/npm-shrinkwrap.json +9115 -9115
  117. package/package.json +100 -100
  118. package/scripts/postinstall.js +24 -24
  119. package/spec/helpers/reporter.js +65 -65
  120. package/spec/jsSpecs/apiTest/generator.spec.js +372 -372
  121. package/spec/jsSpecs/apiTest/inspector.spec.js +89 -89
  122. package/spec/jsSpecs/apiTest/installer.spec.js +67 -67
  123. package/spec/jsSpecs/apiTest/launcher.spec.js +150 -150
  124. package/spec/jsSpecs/apiTest/packager.spec.js +194 -194
  125. package/spec/jsSpecs/apiTest/puller.spec.js +101 -101
  126. package/spec/jsSpecs/apiTest/pusher.spec.js +103 -103
  127. package/spec/jsSpecs/apiTest/server.spec.js +115 -115
  128. package/spec/jsSpecs/apiTest/setupDevice.spec.js +93 -93
  129. package/spec/jsSpecs/apiTest/shell.spec.js +49 -49
  130. package/spec/jsSpecs/ares-config.spec.js +78 -78
  131. package/spec/jsSpecs/ares-device.spec.js +443 -443
  132. package/spec/jsSpecs/ares-generate.spec.js +397 -397
  133. package/spec/jsSpecs/ares-inspect.spec.js +252 -252
  134. package/spec/jsSpecs/ares-install.spec.js +150 -150
  135. package/spec/jsSpecs/ares-launch.spec.js +301 -301
  136. package/spec/jsSpecs/ares-log.spec.js +824 -824
  137. package/spec/jsSpecs/ares-novacom.spec.js +149 -149
  138. package/spec/jsSpecs/ares-package.spec.js +1211 -1211
  139. package/spec/jsSpecs/ares-pull.spec.js +157 -157
  140. package/spec/jsSpecs/ares-push.spec.js +146 -146
  141. package/spec/jsSpecs/ares-server.spec.js +160 -160
  142. package/spec/jsSpecs/ares-setup-device.spec.js +281 -281
  143. package/spec/jsSpecs/ares-shell.spec.js +220 -220
  144. package/spec/jsSpecs/ares.spec.js +83 -83
  145. package/spec/jsSpecs/common-spec.js +169 -169
  146. package/spec/support/jasmine.json +22 -22
  147. package/spec/tempFiles/nativeApp/auto/pkg_arm64/GLES2 +0 -0
  148. package/spec/tempFiles/nativeApp/auto/pkg_arm64/appinfo.json +9 -9
  149. package/spec/tempFiles/nativeApp/ose/pkg_arm/Hello +0 -0
  150. package/spec/tempFiles/nativeApp/ose/pkg_arm/appinfo.json +8 -8
  151. package/spec/tempFiles/nativeApp/ose/pkg_arm/package.properties +2 -2
  152. package/spec/tempFiles/nativeApp/oseEmul/pkg_x86/Hello +0 -0
  153. package/spec/tempFiles/nativeApp/oseEmul/pkg_x86/appinfo.json +9 -9
  154. package/spec/tempFiles/nativeApp/rsi/pkg_x86/GLES2 +0 -0
  155. package/spec/tempFiles/nativeApp/rsi/pkg_x86/appinfo.json +9 -9
  156. package/spec/tempFiles/sign/sign.crt +32 -32
  157. package/spec/tempFiles/sign/signPriv.key +52 -52
  158. package/spec/test_data/ares-generate.json +41 -41
  159. package/spec/test_data/ares.json +33 -33
@@ -1,1191 +1,1191 @@
1
- /*
2
- * Copyright (c) 2020-2024 LG Electronics Inc.
3
- *
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
-
7
- const async = require('async'),
8
- chalk = require('chalk'),
9
- fs = require('fs'),
10
- mkdirp = require('mkdirp'),
11
- net = require('net'),
12
- log = require('npmlog'),
13
- path = require('path'),
14
- request = require('request'),
15
- shelljs = require('shelljs'),
16
- Ssh2 = require('ssh2'),
17
- stream = require('stream'),
18
- util = require('util'),
19
- Appdata = require('./cli-appdata'),
20
- errHndl = require('./error-handler'),
21
- server = require('./server');
22
-
23
- // novacom emulation layer, on top of ssh
24
- (function() {
25
- const novacom = {};
26
-
27
- log.heading = 'novacom';
28
- log.level = 'warn';
29
- novacom.log = log;
30
-
31
- const keydir = path.resolve(process.env.HOME || process.env.USERPROFILE, '.ssh');
32
- let cliData;
33
-
34
- function makeExecError(cmd, code, signal, orgErrMsg) {
35
- let err = null; // null:success, undefined:did-not-run, Error:failure
36
-
37
- if (orgErrMsg) {
38
- orgErrMsg = "\n(Original Message: " + orgErrMsg.replace(/\u001b[^m]*?m/g,"").trim() + ")";
39
- } else {
40
- orgErrMsg = "";
41
- }
42
-
43
- if (signal) {
44
- signal = " (signal: " + signal + ")";
45
- } else {
46
- signal = "";
47
- }
48
-
49
- if (code !== 0 || signal) {
50
- err = new Error();
51
- err.message = "Command '" + cmd + "' exited with code=" + code + signal + orgErrMsg;
52
- err.code = code;
53
-
54
- return errHndl.getErrMsg(err);
55
- }
56
- return err;
57
- }
58
-
59
- novacom.Resolver = Resolver;
60
-
61
- /**
62
- * @constructor
63
- */
64
- function Resolver() {
65
- /**
66
- * @property devices
67
- * This list use to be maintained by novacomd
68
- */
69
- this.devices = [];
70
- this.deviceFileContent = null;
71
- cliData = new Appdata();
72
- }
73
-
74
- novacom.Resolver.prototype = {
75
- /**
76
- * Load the resolver DB from the filesystem
77
- * @param {Function} next a common-JS callback invoked when the DB is ready to use.
78
- */
79
- load: function(next) {
80
- log.info("novacom#Resolver()#load()");
81
- const resolver = this;
82
-
83
- async.waterfall([
84
- _replaceBuiltinSshKey.bind(resolver),
85
- _adjustList.bind(resolver),
86
- _loadString.bind(resolver)
87
- ], function(err) {
88
- if (err) {
89
- setImmediate(next, err);
90
- } else {
91
- log.silly("novacom#Resolver()#load()", "devices:", resolver.devices);
92
- setImmediate(next);
93
- }
94
- });
95
-
96
- function _replaceBuiltinSshKey(next) {
97
- log.silly("novacom#Resolver()#load()#_replaceBuiltinSshKey()");
98
- const builtinPrvKeyForEmul = path.join(__dirname, "../../files/conf/", 'webos_emul'),
99
- userHomePrvKeyForEmul = path.join(keydir, 'webos_emul');
100
-
101
- fs.stat(builtinPrvKeyForEmul, function(err, builtinKeyStat) {
102
- if (err) {
103
- if (err.code === 'ENOENT') {
104
- setImmediate(next);
105
- } else {
106
- setImmediate(next, err);
107
- }
108
- } else {
109
- fs.stat(userHomePrvKeyForEmul, function(error, userKeyStat) {
110
- if (error) {
111
- if (error.code === 'ENOENT') {
112
- mkdirp(keydir, function() {
113
- shelljs.cp('-rf', builtinPrvKeyForEmul, keydir);
114
- fs.chmodSync(userHomePrvKeyForEmul, '0600');
115
- setImmediate(next);
116
- });
117
- } else {
118
- setImmediate(next, error);
119
- }
120
- } else {
121
- if (builtinKeyStat.mtime.getTime() > userKeyStat.mtime.getTime()) {
122
- shelljs.cp('-rf', builtinPrvKeyForEmul, keydir);
123
- fs.chmodSync(userHomePrvKeyForEmul, '0600');
124
- }
125
- setImmediate(next);
126
- }
127
- });
128
- }
129
- });
130
- }
131
-
132
- /*
133
- * Add "default" field to list files
134
- * Set "emulator" to default target
135
- */
136
- function _adjustList(next) {
137
- const defaultTarget = "emulator";
138
- let inDevices = cliData.getDeviceList(true);
139
-
140
- // count targes does not have "default field"
141
- const defaultFields = inDevices.filter(function(device) {
142
- return (device.default !== undefined);
143
- });
144
- if (defaultFields.length < 1) {
145
- log.silly("novacom#Resolver()#load()#_adjustList()", "rewrite default field");
146
- inDevices = inDevices.map(function(dev) {
147
- if (defaultTarget === dev.name) {
148
- dev.default = true;
149
- } else {
150
- dev.default = false;
151
- }
152
- return dev;
153
- });
154
- this.save(inDevices);
155
- }
156
- setImmediate(next);
157
- }
158
-
159
- /*
160
- * Load devices described in the given string
161
- * (supposed to be a JSON Array).
162
- */
163
- function _loadString(next) {
164
- const inDevices = cliData.getDeviceList(true);
165
-
166
- if (!Array.isArray(inDevices)) {
167
- setImmediate(next, errHndl.getErrMsg("INVALID_FILE"));
168
- return;
169
- }
170
-
171
- async.forEach(inDevices, function(inDevice, next) {
172
- async.series([
173
- resolver._loadOne.bind(resolver, inDevice),
174
- resolver._addOne.bind(resolver, inDevice)
175
- ], next);
176
- }, function(err) {
177
- if (err) {
178
- setImmediate(next, err);
179
- } else {
180
- setImmediate(next);
181
- }
182
- });
183
- }
184
- },
185
-
186
- /*
187
- * Resolve the SSH private key of the given device
188
- * into a usable string. Prefer already fetch keys,
189
- * then manually-configured OpenSSH one & finally
190
- * fetch it from a distant device (webOS Pro only).
191
- */
192
- _loadOne: function(inDevice, next) {
193
- if (typeof inDevice.privateKey === 'string') {
194
- inDevice.privateKeyName = inDevice.privateKey;
195
- inDevice.privateKey = Buffer.from(inDevice.privateKey, 'base64');
196
- setImmediate(next);
197
- } else if (typeof inDevice.privateKey === 'object' && typeof inDevice.privateKey.openSsh === 'string') {
198
- inDevice.privateKeyName = inDevice.privateKey.openSsh;
199
- async.waterfall([
200
- fs.readFile.bind(this, path.join(keydir, inDevice.privateKey.openSsh), next),
201
- function(privateKey, next) {
202
- inDevice.privateKey = privateKey;
203
- setImmediate(next);
204
- }
205
- ], function(err) {
206
- // do not load non-existing OpenSSH private key files
207
- if (err) {
208
- log.verbose("novacom#Resolver()#_loadOne()", "Unable to find SSH private key named '" +
209
- inDevice.privateKey.openSsh + "' from '" + keydir + " for '" + inDevice.name + "'");
210
- inDevice.privateKey = undefined;
211
- }
212
- setImmediate(next);
213
- });
214
- } else if (inDevice.type !== undefined && inDevice.type === 'webospro') {
215
- // FIXME: here is the place to stream-down the SSH private key from the device
216
- setImmediate(next, errHndl.getErrMsg("NOT_IMPLEMENTED", "webOS Pro device type handling"));
217
- } else { // private Key is not defined in novacom-device.json
218
- if (!inDevice.password) {
219
- log.silly("novacom#Resolver()#_loadOne()", "Regist privateKey : need to set a SSH private key in " +
220
- keydir + " for'" + inDevice.name + "'");
221
- }
222
- inDevice.privateKeyName = undefined;
223
- inDevice.privateKey = undefined;
224
- setImmediate(next);
225
- }
226
- },
227
-
228
- /*
229
- * Add given inDevice to the Resolver DB, overwritting
230
- * any existing one with the same "name:" is needed.
231
- */
232
- _addOne: function(inDevice, next) {
233
- // add the current profile device only
234
- if (!inDevice.profile || !cliData.compareProfileSync(inDevice.profile)) {
235
- return setImmediate(next);
236
- }
237
-
238
- inDevice.display = {
239
- name: inDevice.name,
240
- type: inDevice.type,
241
- privateKeyName: inDevice.privateKeyName,
242
- passphrase: inDevice.passphrase,
243
- description: inDevice.description,
244
- conn: inDevice.conn || ['ssh'],
245
- devId: inDevice.id || null
246
- };
247
-
248
- if (inDevice.username && inDevice.host && inDevice.port) {
249
- inDevice.display.addr = "ssh://" + inDevice.username + "@" + inDevice.host + ":" + inDevice.port;
250
- }
251
-
252
- for (const n in inDevice) {
253
- if (n !== "display") {
254
- inDevice.display[n] = inDevice[n];
255
- }
256
- }
257
-
258
- // filter-out `this.devices` from the one having the same name as `inDevice`...
259
- this.devices = this.devices.filter(function(device) {
260
- return device.name !== inDevice.name;
261
- });
262
-
263
- // ...hook proper luna interface
264
- const systemCmd = cliData.getCommandService();
265
- inDevice.lunaSend = systemCmd.lunaSend;
266
- inDevice.lunaAddr = systemCmd.lunaAddr;
267
-
268
- // ...and then append `inDevice`
269
- this.devices.push(inDevice);
270
- setImmediate(next);
271
- },
272
-
273
-
274
- /**
275
- * @public
276
- */
277
- save: function(devicesData, next) {
278
- log.info("novacom#Resolver()#save()");
279
- return cliData.setDeviceList(devicesData, next);
280
- },
281
-
282
- /**
283
- * @public
284
- */
285
- list: function(next) {
286
- log.info("novacom#Resolver()#list()");
287
- setImmediate(next, null, this.devices.map(function(device) {
288
- return device.display;
289
- }));
290
- },
291
-
292
- setTargetName: function(target, printTarget, next) {
293
- let name = (typeof target === 'string' ? target : target && target.name);
294
- if (!name) {
295
- const usbDevices = this.devices.filter(function(device) {
296
- return (device.conn && device.conn.indexOf("novacom") !== -1);
297
- });
298
-
299
- if (usbDevices.length > 1) {
300
- return next(errHndl.getErrMsg("CONNECTED_MULTI_DEVICE"));
301
- }
302
-
303
- // default target priority
304
- // specified target name > target connected by novacom > user setting > emulator
305
- if (usbDevices.length > 0 && usbDevices[0].name) {
306
- name = usbDevices[0].name;
307
- } else {
308
- const defaultTargets = this.devices.filter(function(device) {
309
- return (device.default && device.default === true);
310
- });
311
-
312
- if (defaultTargets.length > 1) {
313
- return next(errHndl.getErrMsg("SET_DEFAULT_MULTI_DEVICE"));
314
- } else if (defaultTargets.length === 1) {
315
- name = defaultTargets[0].name;
316
- } else {
317
- // if cannot find default target in list, set to "emulator"
318
- name = "emulator";
319
- }
320
- }
321
- }
322
- if (printTarget) {
323
- console.log(chalk.green("[Info] Set target device : " + name));
324
- }
325
- next(null, 'name', name);
326
- },
327
-
328
- getDeviceBy: function(key, value, next) {
329
- log.info("novacom#Resolver()#getDeviceBy()", "key:", key, ", value:", value);
330
- const devices = this.devices.filter(function(device) {
331
- return device[key] && device[key] === value;
332
- });
333
-
334
- if (devices.length < 1) {
335
- setImmediate(next, errHndl.getErrMsg("UNMATCHED_DEVICE", key, value));
336
- } else if (typeof next === 'function') {
337
- setImmediate(next, null, devices[0]);
338
- } else {
339
- return devices[0];
340
- }
341
- },
342
-
343
- getSshPrvKey: function(target, next) {
344
- let name = (typeof target === 'string' ? target : target && target.name);
345
- if (!name) {
346
- // if name is not specified, find default target
347
- const defaultTargets = this.devices.filter(function(device) {
348
- return (device.default && device.default === true);
349
- });
350
- if (defaultTargets.length > 1) {
351
- return next(errHndl.getErrMsg("SET_DEFAULT_MULTI_DEVICE"));
352
- } else if (defaultTargets.length === 1) {
353
- name = defaultTargets[0].name;
354
- } else {
355
- // if cannot find default target in list, set to "emulator"
356
- name = "emulator";
357
- }
358
- // assign target name
359
- if (typeof target === 'string') {
360
- target = name;
361
- } else if (typeof target === 'object') {
362
- target.name = name;
363
- }
364
- }
365
-
366
- async.waterfall([
367
- this.getDeviceBy.bind(this, 'name', name),
368
- function(targetDevice, next) {
369
- log.info("Resolver#getSshPrvKey()", "targetDevice.host:", targetDevice.host);
370
- const url = 'http://' + targetDevice.host + ':9991' + '/webos_rsa';
371
- const keyFileNamePrefix = targetDevice.name.replace(/(\s+)/gi, '_');
372
- const keyFileName = keyFileNamePrefix + "_webos";
373
- const keySavePath = path.join(keydir, keyFileName);
374
- request.head(url, function(err, res) {
375
- if (err || (res && res.statusCode !== 200)) {
376
- return setImmediate(next, errHndl.getErrMsg("FAILED_GET_SSHKEY"));
377
- }
378
- log.info("Resolver#getSshPrvKey()#head", "content-type:", res.headers['content-type']);
379
- log.info("Resolver#getSshPrvKey()#head", "content-length:", res.headers['content-length']);
380
- request(url).pipe(fs.createWriteStream(keySavePath)).on('close', function(error) {
381
- if (error) {
382
- return setImmediate(next, errHndl.getErrMsg("FAILED_GET_SSHKEY"));
383
- } else {
384
- setImmediate(next, error, keySavePath, keyFileName);
385
- }
386
- });
387
- });
388
- },
389
- function(keyFilePath, keyFileName, next) {
390
- log.info("Resolver#getSshPrvKey()", "SSH Private Key:", keyFilePath);
391
- console.log("SSH Private Key:", keyFilePath);
392
- fs.chmodSync(keyFilePath, '0600');
393
- setImmediate(next, null, keyFileName);
394
- }
395
- ], next);
396
- },
397
-
398
- modifyDeviceFile: function(op, target, next) {
399
- log.info("novacom#Resolver()#modifyDeviceFile()", "op:", op);
400
- let defaultTarget = "emulator",
401
- inDevices = cliData.getDeviceList(true);
402
-
403
- if (!target.name) {
404
- return setImmediate(next, errHndl.getErrMsg("EMPTY_VALUE", "target"));
405
- }
406
-
407
- if (!Array.isArray(inDevices)) {
408
- return setImmediate(next, errHndl.getErrMsg("INVALID_FILE"));
409
- }
410
-
411
- const matchedDevices = inDevices.filter(function(dev) {
412
- let match = false;
413
- if (target.name === dev.name) {
414
- if (target.profile) {
415
- if (target.profile === dev.profile) {
416
- match = true;
417
- }
418
- } else {
419
- match = true;
420
- }
421
- }
422
- return match;
423
- });
424
-
425
- if (op === 'add') {
426
- if (matchedDevices.length > 0) {
427
- return setImmediate(next, errHndl.getErrMsg("EXISTING_VALUE", "DEVICE_NAME", target.name));
428
- }
429
-
430
- for (const key in target) {
431
- if (target[key] === "@DELETE@") {
432
- delete target[key];
433
- }
434
- }
435
-
436
- if (target.default === true) {
437
- defaultTarget = target.name;
438
- } else if (target.default === false) {
439
- // new device is not a default target, keep current default target device.
440
- defaultTarget = null;
441
- }
442
-
443
- inDevices = inDevices.concat(target);
444
- } else if (op === 'remove' || op === 'modify' || op === 'default') {
445
- if (matchedDevices.length === 0) {
446
- return setImmediate(next, errHndl.getErrMsg("INVALID_VALUE", "DEVICE_NAME", target.name));
447
- }
448
-
449
- if (op === 'remove') {
450
- inDevices = inDevices.filter(function(dev) {
451
- if (target.name === dev.name) {
452
- if (dev.indelible === true) {
453
- return setImmediate(next, errHndl.getErrMsg("CANNOT_REMOVE_DEVICE", dev.name));
454
- } else {
455
- // removed target is not default target, do not set defalut to others
456
- if (dev.default === false) {
457
- defaultTarget = null;
458
- }
459
- return false;
460
- }
461
- } else {
462
- return true;
463
- }
464
- });
465
- } else if (op === 'modify') {
466
- inDevices = inDevices.map(function(dev) {
467
- if (target.name === dev.name) {
468
- if (dev.default === true) {
469
- // keep current default device as modified target
470
- defaultTarget = dev.name;
471
- } else {
472
- // do not change default device
473
- defaultTarget = null;
474
- }
475
-
476
- for (const prop in target) {
477
- if (Object.prototype.hasOwnProperty.call(target, prop)) {
478
- dev[prop] = target[prop];
479
- if (dev[prop] === "@DELETE@") {
480
- delete dev[prop];
481
- }
482
- }
483
- }
484
- }
485
- return dev;
486
- });
487
- } else if (op === 'default') {
488
- inDevices.map(function(dev) {
489
- if (target.name === dev.name) {
490
- if (target.default === true) {
491
- defaultTarget = target.name;
492
- }
493
- }
494
- });
495
- }
496
- } else {
497
- return setImmediate(next, errHndl.getErrMsg("UNKNOWN_OPERATOR", op));
498
- }
499
-
500
- // Set default target & others to no default target(false)
501
- if (defaultTarget != null) {
502
- inDevices = inDevices.map(function(dev) {
503
- if (defaultTarget === dev.name) {
504
- dev.default = true;
505
- } else {
506
- dev.default = false;
507
- }
508
- return dev;
509
- });
510
- }
511
- this.save(inDevices, next);
512
- }
513
- };
514
-
515
- /**
516
- * @constructor
517
- * @param {String} target the name of the target device to connect to. "default"
518
- * @param {Function} next common-js callback, invoked when the Session becomes usable or definitively unusable (failed)
519
- */
520
- function Session(target, printTarget, next) {
521
- if (typeof next !== 'function') {
522
- throw errHndl.getErrMsg("MISSING_CALLBACK", "next", util.inspect(next));
523
- }
524
-
525
- const name = (typeof target === 'string' ? target : target && target.name);
526
- log.info("novacom#Session()", "opening session to '" + (name ? name : "default device") + "'");
527
- this.resolver = new Resolver();
528
-
529
- async.waterfall([
530
- this.resolver.load.bind(this.resolver),
531
- this.resolver.setTargetName.bind(this.resolver, target, printTarget),
532
- this.resolver.getDeviceBy.bind(this.resolver),
533
- this.checkConnection.bind(this),
534
- this.begin.bind(this)
535
- ], next);
536
- }
537
-
538
- novacom.Session = Session;
539
-
540
- novacom.Session.prototype = {
541
- /**
542
- * Check if socket can be connected
543
- * This method can be called multiple times.
544
- * @param {Function} next common-js callback
545
- */
546
- checkConnection: function(target, next) {
547
- let alive = false;
548
- if (target && target.host && target.port) {
549
- const socket = new net.Socket();
550
- socket.setTimeout(2000);
551
- const client = socket.connect({
552
- host: target.host,
553
- port: target.port
554
- });
555
- client.on('connect', function() {
556
- alive = true;
557
- client.end();
558
- setImmediate(next, null, target);
559
- });
560
- client.on('error', function(err) {
561
- client.destroy();
562
- setImmediate(next, errHndl.getErrMsg(err));
563
- });
564
- client.on('timeout', function() {
565
- client.destroy();
566
- if (!alive) {
567
- setImmediate(next, errHndl.getErrMsg("TIME_OUT"));
568
- }
569
- });
570
- } else {
571
- setImmediate(next, null, target);
572
- }
573
- },
574
-
575
- /**
576
- * Begin a novacom session with the current target
577
- * This method can be called multiple times.
578
- * @param {Function} next common-js callback
579
- */
580
- begin: function(target, next) {
581
- log.verbose("novacom#Session()#begin()", "target:", target.display);
582
- const self = this;
583
- this.target = target || this.target;
584
-
585
- if (this.target.conn && (this.target.conn.indexOf('ssh') === -1)) {
586
- setImmediate(next, null, this);
587
- return this;
588
- }
589
-
590
- if (this.target.privateKey === undefined && this.target.password === undefined) {
591
- return setImmediate(next, errHndl.getErrMsg("NOT_EXIST_SSHKEY_PASSWD"));
592
- }
593
-
594
- if (!this.ssh) {
595
- this.forwardedPorts = [];
596
- this.ssh = new Ssh2();
597
- this.ssh.on('connect', function() {
598
- log.info("novacom#Session()#begin()", "ssh session event: connected");
599
- });
600
- this.ssh.on('ready', _next.bind(this));
601
- this.ssh.on('error', _next.bind(this));
602
- this.ssh.on('end', function() {
603
- log.info("novacom#Session()#begin()", "ssh session event: end");
604
- });
605
- this.ssh.on('close', function(had_error) {
606
- log.info("novacom#Session()#begin()", "ssh session event: close (had_error:", had_error, ")");
607
- });
608
- this.target.readyTimeout = 30000;
609
- this.ssh.connect(this.target);
610
-
611
- process.on("SIGHUP", _clearSession);
612
- process.on("SIGINT", _clearSession);
613
- process.on("SIGQUIT", _clearSession);
614
- process.on("SIGTERM", _clearSession);
615
- process.on("exit", function() {
616
- _clearSession();
617
- });
618
- // Node.js cannot handle SIGKILL, SIGSTOP
619
- // process.on("SIGKILL", _clearSession);
620
- // process.on("SIGSTOP", _clearSession);
621
- }
622
- return this;
623
-
624
- function _next(err) {
625
- setImmediate(next, (err ? errHndl.getErrMsg(err) : err), this);
626
- }
627
-
628
- function _clearSession() {
629
- log.verbose("novacom#Session()#begin()", "clear Session");
630
- self.end();
631
- setTimeout(function() {
632
- process.exit();
633
- }, 500);
634
- }
635
- },
636
-
637
- /**
638
- * @return the resolved device actually in use for this session
639
- */
640
- getDevice: function() {
641
- return this.target;
642
- },
643
-
644
- /**
645
- * Suspend the novacom session. Underlying resources
646
- * are released (eg. SSH connections are closed).
647
- */
648
- end: function() {
649
- log.info('novacom#Session()#end()', "user-requested termination");
650
- if (this.ssh) {
651
- this.ssh.end();
652
- }
653
- return this;
654
- },
655
-
656
- _checkSftp: function(next) {
657
- // FIXME: This is workaround to prevent hang from ssh2.sftp()
658
- // - issue in ssh2: https://github.com/mscdex/ssh2/issues/240
659
- // This way only works with ssh2@0.2.x, not working with ssh2@0.4.x, ssh2@0.3.x.
660
- const self = this;
661
- self.ssh.subsys('sftp', function(err, _stream) {
662
- if (err) {
663
- return setImmediate(next, err);
664
- }
665
-
666
- _stream.once('data', function(data) {
667
- const regex = new RegExp("sftp-server(.| )+not found", "gi");
668
- if (data.toString().match(regex)) {
669
- const sftpError = errHndl.getErrMsg("UNABLE_USE_SFTP");
670
- sftpError.code = 4;
671
- return setImmediate(next, sftpError);
672
- }
673
- });
674
- });
675
- },
676
-
677
- /**
678
- * Upload a file on the device
679
- * @param {String} inPath location on the host
680
- * @param {String} outPath location on the device
681
- * @param {Function} next common-js callback
682
- */
683
- put: function(inPath, outPath, next) {
684
- log.info("novacom#Session()#put()", "uploding into device:", outPath, "from host:", inPath);
685
- const self = this;
686
- let inStream;
687
-
688
- log.verbose("novacom#Session()#put()", "sftpPut()::start");
689
- self.sftpPut(inPath, outPath, function(err) {
690
- if (err) {
691
- log.verbose(err);
692
- if (4 === err.code || 127 === err.code) {
693
- log.info("novacom#Session()#put()", "sftp is not available, attempt transfering file via streamPut");
694
- inStream = fs.createReadStream(inPath);
695
- self.streamPut(outPath, inStream, next);
696
- } else if (14 === err.code) {
697
- const detailMsg = errHndl.getErrMsg("NO_FREE_SPACE");
698
- setImmediate(next, detailMsg);
699
- } else {
700
- setImmediate(next, err);
701
- }
702
- } else {
703
- log.info("novacom#Session()#put()", "sftpPut()::done");
704
- setImmediate(next);
705
- }
706
- });
707
- },
708
-
709
- /**
710
- * Upload a file on the device via ssh stream
711
- * @param {String} outPath location on the device
712
- * @param {ReadableStream} inStream paused host-side source
713
- * @param {Function} next common-js callback
714
- */
715
- streamPut: function(outPath, inStream, next) {
716
- log.info("novacom#Session()#streamPut()", "streaming into device:" + outPath);
717
- const cmd = '/bin/cat > "' + outPath + '"';
718
- this.run(cmd, inStream /* stdin*/ , null /* stdout*/ , process.stderr /* stderr*/ , next);
719
- },
720
-
721
- /**
722
- * Upload a file on the device via sftp
723
- * @param {String} inPath location on the host
724
- * @param {String} outPath location on the device
725
- * @param {Function} next common-js callback
726
- */
727
- sftpPut: function(inPath, outPath, next) {
728
- log.verbose('novacom#Session()#sftpPut()', 'host:' + inPath + ' => ' + 'device:' + outPath);
729
- const self = this;
730
- self._checkSftp(next);
731
-
732
- async.series({
733
- transfer: function(next) {
734
- self.ssh.sftp(function(err, sftp) {
735
- if (err) {
736
- return setImmediate(next, err);
737
- }
738
-
739
- const readStream = fs.createReadStream(inPath),
740
- writeStream = sftp.createWriteStream(outPath);
741
-
742
- writeStream.on('close', function() {
743
- sftp.end();
744
- setImmediate(next);
745
- });
746
-
747
- // Exit when the remote process has terminated
748
- writeStream.on('exit', function(code, signal) {
749
- err = makeExecError('sftpPut', code, signal);
750
- setImmediate(next, err);
751
- });
752
-
753
- writeStream.on('error', function(error) {
754
- log.verbose('novacom#Session()#sftpPut()', "error:", error);
755
- setImmediate(next, error);
756
- });
757
-
758
- readStream.pipe(writeStream);
759
- });
760
- }
761
- }, function(err) {
762
- setImmediate(next, err);
763
- });
764
- },
765
-
766
- /**
767
- * Download file on the device
768
- * @param {String} inPath location on the device
769
- * @param {String} outPath location on the host
770
- * @param {Function} next common-js callback
771
- */
772
- get: function(inPath, outPath, next) {
773
- log.verbose("novacom#Session()#get()", "downloading into host:", outPath, "from target:", inPath);
774
- const self = this;
775
-
776
- log.verbose("novacom#Session()#get()", "sftpGet()::start");
777
- self.sftpGet(inPath, outPath, function(err) {
778
- if (err) {
779
- log.verbose(err);
780
- if (4 === err.code || 127 === err.code) {
781
- log.info("novacom#Session()#get()", "sftp is not available, attempt transfering file via streamPut");
782
- const os = fs.createWriteStream(outPath);
783
- os.on('error', function(error) {
784
- setImmediate(next, errHndl.getErrMsg(error));
785
- });
786
- self.streamGet(inPath, os, next);
787
- } else {
788
- setImmediate(next, err);
789
- }
790
- } else {
791
- log.verbose("novacom#Session()#get()", "sftpGet()::done");
792
- setImmediate(next);
793
- }
794
- });
795
- },
796
-
797
- /**
798
- * Read a file from the device via ssh stream
799
- * @param {String} inPath the device file path to be read
800
- * @param {WritableStream} outStream host-side destination to copy the file into
801
- * @param {Function} next commonJS callback invoked upon completion or failure
802
- */
803
- streamGet: function(inPath, outStream, next) {
804
- log.verbose('novacom#Session()#streamGet()', "streaming from device:" + inPath);
805
- const cmd = '/bin/cat ' + inPath;
806
- this.run(cmd, null /* stdin*/ , outStream /* stdout*/ , process.stderr /* stderr*/ , next);
807
- },
808
-
809
- /**
810
- * Download file on the device via sftp
811
- * @param {String} inPath location on the device
812
- * @param {String} outPath location on the host
813
- * @param {Function} next common-js callback
814
- */
815
- sftpGet: function(inPath, outPath, next) {
816
- log.verbose("novacom#Session()#sftpGet()", "target:" + inPath + " => " + "host:" + outPath);
817
- const self = this;
818
- self._checkSftp(next);
819
- async.series({
820
- transfer: function(next) {
821
- self.ssh.sftp(function(err, sftp) {
822
- if (err) {
823
- setImmediate(next, err);
824
- return;
825
- }
826
-
827
- const readStream = sftp.createReadStream(inPath),
828
- writeStream = fs.createWriteStream(outPath);
829
-
830
- readStream.on('close', function() {
831
- sftp.end();
832
- setImmediate(next);
833
- });
834
-
835
- // Exit when the remote process has terminated
836
- readStream.on('exit', function(code, signal) {
837
- err = makeExecError('sftpGet', code, signal);
838
- setImmediate(next, err);
839
- });
840
-
841
- readStream.on('error', function(error) {
842
- log.verbose("novacom#Session()#sftpGet()", "error:", error);
843
- setImmediate(next, error);
844
- });
845
-
846
- readStream.pipe(writeStream);
847
- });
848
- }
849
- }, function(err) {
850
- setImmediate(next, err);
851
- });
852
- },
853
-
854
- /**
855
- * Run a command on the device
856
- * @param {String} cmd the device command to run
857
- * @param {stream.ReadableStream} stdin given as novacom process stdin
858
- * @param {stream.WritableStream} stdout given as novacom process stdout
859
- * @param {stream.WritableStream} stderr given as novacom process stderr
860
- * @param {Function} next commonJS callback invoked upon completion or failure
861
- */
862
- run: function(cmd, stdin, stdout, stderr, next) {
863
- this.run_ssh(cmd, {}, stdin, stdout, stderr, next);
864
- },
865
-
866
- /**
867
- * Run a command with exec option on the device
868
- * @param {String} cmd the device command to run
869
- * @param {Object} opt given as exec option
870
- * @param {stream.ReadableStream} stdin given as novacom process stdin
871
- * @param {stream.WritableStream} stdout given as novacom process stdout
872
- * @param {stream.WritableStream} stderr given as novacom process stderr
873
- * @param {Function} next commonJS callback invoked upon completion or failure
874
- */
875
- runWithOption: function(cmd, opt, stdin, stdout, stderr, next) {
876
- this.run_ssh(cmd, opt, stdin, stdout, stderr, next);
877
- },
878
-
879
- /**
880
- * Run a command on the device
881
- * @param {String} cmd the device command to run
882
- * @param {Object} opt given as exec option
883
- * @param {stream.ReadableStream} stdin given as novacom process stdin
884
- * @param {stream.WritableStream} stdout given as novacom process stdout
885
- * @param {stream.WritableStream} stderr given as novacom process stderr
886
- * @param {Function} next commonJS callback invoked upon completion or failure
887
- */
888
- run_ssh: function(cmd, opt, stdin, stdout, stderr, next) {
889
- log.info("novacom#Session()#run()", "cmd:" + cmd + ", opt:" + JSON.stringify(opt));
890
- if (typeof next !== 'function') {
891
- throw errHndl.getErrMsg("MISSING_CALLBACK", "next", util.inspect(next));
892
- }
893
-
894
- // plumb output
895
- const write = {},
896
- obj = {};
897
- let orgErrMsg;
898
-
899
- if (!stdout) {
900
- log.silly("novacom#Session()#run()", "stdout: none");
901
- write.stdout = function() {};
902
- } else if (stdout instanceof stream.Stream) {
903
- log.silly("novacom#Session()#run()", "stdout: stream");
904
- write.stdout = stdout.write;
905
- obj.stdout = stdout;
906
- } else if (stdout instanceof Function) {
907
- log.silly("novacom#Session()#run()", "stdout: function");
908
- write.stdout = stdout;
909
- } else {
910
- setImmediate(next, errHndl.getErrMsg("INVALID_VALUE", "stdout", util.inspect(stdout)));
911
- }
912
-
913
- if (!stderr) {
914
- log.silly("novacom#Session()#run()", "stderr: none");
915
- write.stderr = function() {};
916
- } else if (stderr instanceof stream.Stream) {
917
- log.silly("novacom#Session()#run()", "stderr: stream");
918
- write.stderr = stderr.write;
919
- obj.stderr = stderr;
920
- } else if (stderr instanceof Function) {
921
- log.silly("novacom#Session()#run()", "stderr: function");
922
- write.stderr = stderr;
923
- } else {
924
- setImmediate(next, errHndl.getErrMsg("INVALID_VALUE", "stderr", util.inspect(stderr)));
925
- }
926
-
927
- // execute command
928
- this.ssh.exec(cmd, opt, (function(err, chStream) {
929
- log.silly("novacom#Session()#run()", "exec cmd:" + cmd + ", opt:" + JSON.stringify(opt) + ", err:" + err);
930
- if (err) {
931
- return setImmediate(next, err);
932
- }
933
-
934
- // manual pipe(): handle & divert data chunks
935
- chStream.on('data', function(data, extended) {
936
- extended = extended || 'stdout';
937
- log.verbose("novacom#Session()#run()", "on data (" + extended + ")");
938
- write[extended].bind(obj[extended])(data);
939
- }).stderr.on('data', function(data) {
940
- log.verbose("novacom#Session()#run()", "on data (stderr)");
941
- orgErrMsg = data.toString();
942
- });
943
-
944
- // manual pipe(): handle EOF
945
- chStream.on('end', function() {
946
- log.silly("novacom#Session()#run()", "event EOF from (cmd:" + cmd + ")");
947
- if ((stdout !== process.stdout) && (stdout instanceof stream.Stream)) {
948
- stdout.end();
949
- }
950
- if ((stderr !== process.stderr) && (stderr instanceof stream.Stream)) {
951
- stderr.end();
952
- }
953
- });
954
-
955
- // Exit when the remote process has terminated
956
- chStream.on('exit', function(code, signal) {
957
- log.silly("novacom#Session()#run()", "event exit code:" + code + ', signal:' + signal + " (cmd:" + cmd + ")");
958
- err = makeExecError(cmd, code, signal, orgErrMsg);
959
- setImmediate(next, err);
960
- });
961
-
962
- // Exit if the 'exit' event was not
963
- // received (dropbear <= 0.51)
964
- chStream.on('close', function() {
965
- log.silly("novacom#Session()#run()", "event close (cmd:" + cmd + ")");
966
- if (err === undefined) {
967
- setImmediate(next);
968
- }
969
- });
970
-
971
- if (stdin) {
972
- stdin.pipe(chStream);
973
- log.verbose("novacom#Session()#run()", "resuming input");
974
- }
975
- }));
976
- },
977
-
978
- /**
979
- * Run a command on the device considerless return stdout
980
- * @param {String} cmd the device command to run
981
- * @param {Function} callback invoked upon exit event
982
- * @param {Function} next commonJS callback invoked upon completion or failure
983
- */
984
- runNoHangup: function(cmd, cbData, cbExit, next) {
985
- this.runNoHangup_ssh(cmd, cbData, cbExit, next);
986
- },
987
-
988
- /**
989
- * Run a command on the device considerless return stdout
990
- * @param {String} cmd the device command to run
991
- * @param {Function} callback invoked upon exit event
992
- * @param {Function} next commonJS callback invoked upon completion or failure
993
- */
994
- runNoHangup_ssh: function(cmd, cbData, cbExit, next) {
995
- log.info("novacom#Session()#runNoHangup()", "cmd=" + cmd);
996
- if (arguments.length < 2) {
997
- throw errHndl.getErrMsg("MISSING_CALLBACK", "next");
998
- }
999
-
1000
- for (const arg in arguments) {
1001
- if (typeof arguments[arg] === 'undefined') {
1002
- delete arguments[arg];
1003
- arguments.length--;
1004
- }
1005
- }
1006
-
1007
- switch (arguments.length) {
1008
- case 2:
1009
- next = cbData;
1010
- cbData = cbExit = null;
1011
- break;
1012
- case 3:
1013
- next = cbExit;
1014
- cbExit = cbData;
1015
- cbData = null;
1016
- break;
1017
- default:
1018
- break;
1019
- }
1020
-
1021
- if (typeof next !== 'function') {
1022
- throw errHndl.getErrMsg("MISSING_CALLBACK", "next", util.inspect(next));
1023
- }
1024
-
1025
- // execute command
1026
- this.ssh.exec(cmd, (function(err, _stream) {
1027
- log.verbose("novacom#Session()#run()", "exec cmd:" + cmd + ", err:" + err);
1028
- if (err) {
1029
- return setImmediate(next, err);
1030
- }
1031
-
1032
- _stream.on('data', function(data) {
1033
- const str = (Buffer.isBuffer(data)) ? data.toString() : data;
1034
- log.verbose("novacom#Session()#runNoHangup()#onData", str);
1035
- if (cbData) cbData(data);
1036
- }).stderr.on('data', function(data) {
1037
- const str = (Buffer.isBuffer(data)) ? data.toString() : data;
1038
- log.verbose("novacom#Session()#runNoHangup()#onData#stderr#", str);
1039
- if (cbData) cbData(data);
1040
- });
1041
-
1042
- // Exit when the remote process has terminated
1043
- if (cbExit) {
1044
- _stream.on('exit', function(code, signal) {
1045
- log.verbose("novacom#Session()#runNoHangup()", "event exit code=" + code + ', signal=' + signal + " (cmd: " + cmd + ")");
1046
- err = makeExecError(cmd, code, signal);
1047
- cbExit(err);
1048
- });
1049
- }
1050
- setImmediate(next);
1051
- }));
1052
- },
1053
-
1054
- /**
1055
- * Forward the given device port on the host.
1056
- * As any other public method, this one can be called
1057
- * only once the ssh session has emitted the 'ready'
1058
- * event, so as part of the Session()#next callback.
1059
- * @public
1060
- * @param {Function} next commonJS callback invoked upon completion or failure
1061
- */
1062
- forward: function(devicePort, localPort, forwardName, next) {
1063
- log.info("novacom#Session()#forward()", "devicePort:", devicePort, ", localPort:", localPort);
1064
- const session = this;
1065
- let forwardInUse = false,
1066
- registerName = null;
1067
-
1068
- if (typeof forwardName === 'function') {
1069
- next = forwardName;
1070
- } else if (forwardName) {
1071
- registerName = forwardName;
1072
- }
1073
-
1074
- if (localPort !== 0) {
1075
- if (session.forwardedPorts.indexOf({
1076
- name: registerName,
1077
- local: localPort,
1078
- device: devicePort
1079
- }) > 0) {
1080
- forwardInUse = true;
1081
- }
1082
- } else if (session.forwardedPorts.filter(function(forwardItem) {
1083
- return (forwardItem.device === devicePort && forwardItem.name === registerName);
1084
- }).length > 0) {
1085
- forwardInUse = true;
1086
- }
1087
-
1088
- if (forwardInUse) {
1089
- return setImmediate(next);
1090
- }
1091
-
1092
- const localServer = net.createServer(function(inCnx) {
1093
- log.info("novacom#Session()#forward()", "new client, localPort:", localPort);
1094
- log.verbose("novacom#Session()#forward()", "new client, from: " + inCnx.remoteAddress + ':' + inCnx.remotePort);
1095
-
1096
- inCnx.on('error', function(err) {
1097
- log.verbose("novacom#Session()#forward()", "inCnx::error, err::" + err);
1098
- });
1099
-
1100
- // Open the outbound connection on the device to match the incoming client.
1101
- session.ssh.forwardOut("127.0.0.1" /* srcAddr*/ , inCnx.remotePort /* srcPort*/ , "127.0.0.1" /* dstAddr*/ , devicePort /* dstPort*/ , function(err, outCnx) {
1102
- if (err) {
1103
- console.log("novacom#Session()#forward()", "failed forwarding client localPort:",
1104
- localPort, "(inCnx.remotePort:", inCnx.remotePort, ")=> devicePort:", devicePort);
1105
- log.warn("novacom#Session()#forward()", "failed forwarding client localPort:",
1106
- localPort, "=> devicePort:", devicePort);
1107
- inCnx.destroy();
1108
- return;
1109
- }
1110
-
1111
- log.info("novacom#Session()#forward()", "connected, devicePort:", devicePort);
1112
- inCnx.on('data', function(data) {
1113
- if (outCnx.writable && outCnx.writable === true) {
1114
- if (outCnx.write(data) === false) {
1115
- inCnx.pause();
1116
- }
1117
- }
1118
- });
1119
-
1120
- inCnx.on('close', function(had_err) {
1121
- log.verbose("novacom#Session()#forward()", "inCnx::close, had_err:", had_err);
1122
- outCnx.destroy();
1123
- });
1124
-
1125
- outCnx.on('drain', function() {
1126
- inCnx.resume();
1127
- });
1128
-
1129
- outCnx.on('data', function(data) {
1130
- inCnx.write(data);
1131
- });
1132
-
1133
- outCnx.on('close', function(had_err) {
1134
- log.verbose("novacom#Session()#forward()", "outCnx::close, had_err:", had_err);
1135
- });
1136
- });
1137
- });
1138
-
1139
- session.ssh.on('close', function() {
1140
- localServer.close();
1141
- });
1142
-
1143
- try {
1144
- localServer.listen(localPort, null, (function() {
1145
- const localServerPort = localServer.address().port;
1146
- session.forwardedPorts.push({
1147
- name: registerName,
1148
- local: localServerPort,
1149
- device: devicePort
1150
- });
1151
- setImmediate(next);
1152
- }));
1153
- } catch (err) {
1154
- setImmediate(next, err);
1155
- }
1156
- },
1157
-
1158
- getLocalPortByName: function(queryName) {
1159
- const session = this;
1160
- let found = null;
1161
- session.forwardedPorts.forEach(function(portItem) {
1162
- if (portItem.name === queryName) {
1163
- found = portItem.local;
1164
- return;
1165
- }
1166
- });
1167
- return found;
1168
- },
1169
-
1170
- runHostedAppServer: function(url, next) {
1171
- server.runServer(url, 0, function(err, serverInfo) {
1172
- if (serverInfo && serverInfo.port) {
1173
- this.setHostedAppServerPort(serverInfo.port);
1174
- }
1175
- next(err);
1176
- }.bind(this));
1177
- },
1178
-
1179
- setHostedAppServerPort: function(port) {
1180
- this.hostedAppServerPort = port;
1181
- },
1182
-
1183
- getHostedAppServerPort: function() {
1184
- return this.hostedAppServerPort;
1185
- }
1186
- };
1187
-
1188
- if (typeof module !== 'undefined' && module.exports) {
1189
- module.exports = novacom;
1190
- }
1191
- }());
1
+ /*
2
+ * Copyright (c) 2020-2024 LG Electronics Inc.
3
+ *
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ const async = require('async'),
8
+ chalk = require('chalk'),
9
+ fs = require('fs'),
10
+ mkdirp = require('mkdirp'),
11
+ net = require('net'),
12
+ log = require('npmlog'),
13
+ path = require('path'),
14
+ request = require('request'),
15
+ shelljs = require('shelljs'),
16
+ Ssh2 = require('ssh2'),
17
+ stream = require('stream'),
18
+ util = require('util'),
19
+ Appdata = require('./cli-appdata'),
20
+ errHndl = require('./error-handler'),
21
+ server = require('./server');
22
+
23
+ // novacom emulation layer, on top of ssh
24
+ (function() {
25
+ const novacom = {};
26
+
27
+ log.heading = 'novacom';
28
+ log.level = 'warn';
29
+ novacom.log = log;
30
+
31
+ const keydir = path.resolve(process.env.HOME || process.env.USERPROFILE, '.ssh');
32
+ let cliData;
33
+
34
+ function makeExecError(cmd, code, signal, orgErrMsg) {
35
+ let err = null; // null:success, undefined:did-not-run, Error:failure
36
+
37
+ if (orgErrMsg) {
38
+ orgErrMsg = "\n(Original Message: " + orgErrMsg.replace(/\u001b[^m]*?m/g,"").trim() + ")";
39
+ } else {
40
+ orgErrMsg = "";
41
+ }
42
+
43
+ if (signal) {
44
+ signal = " (signal: " + signal + ")";
45
+ } else {
46
+ signal = "";
47
+ }
48
+
49
+ if (code !== 0 || signal) {
50
+ err = new Error();
51
+ err.message = "Command '" + cmd + "' exited with code=" + code + signal + orgErrMsg;
52
+ err.code = code;
53
+
54
+ return errHndl.getErrMsg(err);
55
+ }
56
+ return err;
57
+ }
58
+
59
+ novacom.Resolver = Resolver;
60
+
61
+ /**
62
+ * @constructor
63
+ */
64
+ function Resolver() {
65
+ /**
66
+ * @property devices
67
+ * This list use to be maintained by novacomd
68
+ */
69
+ this.devices = [];
70
+ this.deviceFileContent = null;
71
+ cliData = new Appdata();
72
+ }
73
+
74
+ novacom.Resolver.prototype = {
75
+ /**
76
+ * Load the resolver DB from the filesystem
77
+ * @param {Function} next a common-JS callback invoked when the DB is ready to use.
78
+ */
79
+ load: function(next) {
80
+ log.info("novacom#Resolver()#load()");
81
+ const resolver = this;
82
+
83
+ async.waterfall([
84
+ _replaceBuiltinSshKey.bind(resolver),
85
+ _adjustList.bind(resolver),
86
+ _loadString.bind(resolver)
87
+ ], function(err) {
88
+ if (err) {
89
+ setImmediate(next, err);
90
+ } else {
91
+ log.silly("novacom#Resolver()#load()", "devices:", resolver.devices);
92
+ setImmediate(next);
93
+ }
94
+ });
95
+
96
+ function _replaceBuiltinSshKey(next) {
97
+ log.silly("novacom#Resolver()#load()#_replaceBuiltinSshKey()");
98
+ const builtinPrvKeyForEmul = path.join(__dirname, "../../files/conf/", 'webos_emul'),
99
+ userHomePrvKeyForEmul = path.join(keydir, 'webos_emul');
100
+
101
+ fs.stat(builtinPrvKeyForEmul, function(err, builtinKeyStat) {
102
+ if (err) {
103
+ if (err.code === 'ENOENT') {
104
+ setImmediate(next);
105
+ } else {
106
+ setImmediate(next, err);
107
+ }
108
+ } else {
109
+ fs.stat(userHomePrvKeyForEmul, function(error, userKeyStat) {
110
+ if (error) {
111
+ if (error.code === 'ENOENT') {
112
+ mkdirp(keydir, function() {
113
+ shelljs.cp('-rf', builtinPrvKeyForEmul, keydir);
114
+ fs.chmodSync(userHomePrvKeyForEmul, '0600');
115
+ setImmediate(next);
116
+ });
117
+ } else {
118
+ setImmediate(next, error);
119
+ }
120
+ } else {
121
+ if (builtinKeyStat.mtime.getTime() > userKeyStat.mtime.getTime()) {
122
+ shelljs.cp('-rf', builtinPrvKeyForEmul, keydir);
123
+ fs.chmodSync(userHomePrvKeyForEmul, '0600');
124
+ }
125
+ setImmediate(next);
126
+ }
127
+ });
128
+ }
129
+ });
130
+ }
131
+
132
+ /*
133
+ * Add "default" field to list files
134
+ * Set "emulator" to default target
135
+ */
136
+ function _adjustList(next) {
137
+ const defaultTarget = "emulator";
138
+ let inDevices = cliData.getDeviceList(true);
139
+
140
+ // count targes does not have "default field"
141
+ const defaultFields = inDevices.filter(function(device) {
142
+ return (device.default !== undefined);
143
+ });
144
+ if (defaultFields.length < 1) {
145
+ log.silly("novacom#Resolver()#load()#_adjustList()", "rewrite default field");
146
+ inDevices = inDevices.map(function(dev) {
147
+ if (defaultTarget === dev.name) {
148
+ dev.default = true;
149
+ } else {
150
+ dev.default = false;
151
+ }
152
+ return dev;
153
+ });
154
+ this.save(inDevices);
155
+ }
156
+ setImmediate(next);
157
+ }
158
+
159
+ /*
160
+ * Load devices described in the given string
161
+ * (supposed to be a JSON Array).
162
+ */
163
+ function _loadString(next) {
164
+ const inDevices = cliData.getDeviceList(true);
165
+
166
+ if (!Array.isArray(inDevices)) {
167
+ setImmediate(next, errHndl.getErrMsg("INVALID_FILE"));
168
+ return;
169
+ }
170
+
171
+ async.forEach(inDevices, function(inDevice, next) {
172
+ async.series([
173
+ resolver._loadOne.bind(resolver, inDevice),
174
+ resolver._addOne.bind(resolver, inDevice)
175
+ ], next);
176
+ }, function(err) {
177
+ if (err) {
178
+ setImmediate(next, err);
179
+ } else {
180
+ setImmediate(next);
181
+ }
182
+ });
183
+ }
184
+ },
185
+
186
+ /*
187
+ * Resolve the SSH private key of the given device
188
+ * into a usable string. Prefer already fetch keys,
189
+ * then manually-configured OpenSSH one & finally
190
+ * fetch it from a distant device (webOS Pro only).
191
+ */
192
+ _loadOne: function(inDevice, next) {
193
+ if (typeof inDevice.privateKey === 'string') {
194
+ inDevice.privateKeyName = inDevice.privateKey;
195
+ inDevice.privateKey = Buffer.from(inDevice.privateKey, 'base64');
196
+ setImmediate(next);
197
+ } else if (typeof inDevice.privateKey === 'object' && typeof inDevice.privateKey.openSsh === 'string') {
198
+ inDevice.privateKeyName = inDevice.privateKey.openSsh;
199
+ async.waterfall([
200
+ fs.readFile.bind(this, path.join(keydir, inDevice.privateKey.openSsh), next),
201
+ function(privateKey, next) {
202
+ inDevice.privateKey = privateKey;
203
+ setImmediate(next);
204
+ }
205
+ ], function(err) {
206
+ // do not load non-existing OpenSSH private key files
207
+ if (err) {
208
+ log.verbose("novacom#Resolver()#_loadOne()", "Unable to find SSH private key named '" +
209
+ inDevice.privateKey.openSsh + "' from '" + keydir + " for '" + inDevice.name + "'");
210
+ inDevice.privateKey = undefined;
211
+ }
212
+ setImmediate(next);
213
+ });
214
+ } else if (inDevice.type !== undefined && inDevice.type === 'webospro') {
215
+ // FIXME: here is the place to stream-down the SSH private key from the device
216
+ setImmediate(next, errHndl.getErrMsg("NOT_IMPLEMENTED", "webOS Pro device type handling"));
217
+ } else { // private Key is not defined in novacom-device.json
218
+ if (!inDevice.password) {
219
+ log.silly("novacom#Resolver()#_loadOne()", "Regist privateKey : need to set a SSH private key in " +
220
+ keydir + " for'" + inDevice.name + "'");
221
+ }
222
+ inDevice.privateKeyName = undefined;
223
+ inDevice.privateKey = undefined;
224
+ setImmediate(next);
225
+ }
226
+ },
227
+
228
+ /*
229
+ * Add given inDevice to the Resolver DB, overwritting
230
+ * any existing one with the same "name:" is needed.
231
+ */
232
+ _addOne: function(inDevice, next) {
233
+ // add the current profile device only
234
+ if (!inDevice.profile || !cliData.compareProfileSync(inDevice.profile)) {
235
+ return setImmediate(next);
236
+ }
237
+
238
+ inDevice.display = {
239
+ name: inDevice.name,
240
+ type: inDevice.type,
241
+ privateKeyName: inDevice.privateKeyName,
242
+ passphrase: inDevice.passphrase,
243
+ description: inDevice.description,
244
+ conn: inDevice.conn || ['ssh'],
245
+ devId: inDevice.id || null
246
+ };
247
+
248
+ if (inDevice.username && inDevice.host && inDevice.port) {
249
+ inDevice.display.addr = "ssh://" + inDevice.username + "@" + inDevice.host + ":" + inDevice.port;
250
+ }
251
+
252
+ for (const n in inDevice) {
253
+ if (n !== "display") {
254
+ inDevice.display[n] = inDevice[n];
255
+ }
256
+ }
257
+
258
+ // filter-out `this.devices` from the one having the same name as `inDevice`...
259
+ this.devices = this.devices.filter(function(device) {
260
+ return device.name !== inDevice.name;
261
+ });
262
+
263
+ // ...hook proper luna interface
264
+ const systemCmd = cliData.getCommandService();
265
+ inDevice.lunaSend = systemCmd.lunaSend;
266
+ inDevice.lunaAddr = systemCmd.lunaAddr;
267
+
268
+ // ...and then append `inDevice`
269
+ this.devices.push(inDevice);
270
+ setImmediate(next);
271
+ },
272
+
273
+
274
+ /**
275
+ * @public
276
+ */
277
+ save: function(devicesData, next) {
278
+ log.info("novacom#Resolver()#save()");
279
+ return cliData.setDeviceList(devicesData, next);
280
+ },
281
+
282
+ /**
283
+ * @public
284
+ */
285
+ list: function(next) {
286
+ log.info("novacom#Resolver()#list()");
287
+ setImmediate(next, null, this.devices.map(function(device) {
288
+ return device.display;
289
+ }));
290
+ },
291
+
292
+ setTargetName: function(target, printTarget, next) {
293
+ let name = (typeof target === 'string' ? target : target && target.name);
294
+ if (!name) {
295
+ const usbDevices = this.devices.filter(function(device) {
296
+ return (device.conn && device.conn.indexOf("novacom") !== -1);
297
+ });
298
+
299
+ if (usbDevices.length > 1) {
300
+ return next(errHndl.getErrMsg("CONNECTED_MULTI_DEVICE"));
301
+ }
302
+
303
+ // default target priority
304
+ // specified target name > target connected by novacom > user setting > emulator
305
+ if (usbDevices.length > 0 && usbDevices[0].name) {
306
+ name = usbDevices[0].name;
307
+ } else {
308
+ const defaultTargets = this.devices.filter(function(device) {
309
+ return (device.default && device.default === true);
310
+ });
311
+
312
+ if (defaultTargets.length > 1) {
313
+ return next(errHndl.getErrMsg("SET_DEFAULT_MULTI_DEVICE"));
314
+ } else if (defaultTargets.length === 1) {
315
+ name = defaultTargets[0].name;
316
+ } else {
317
+ // if cannot find default target in list, set to "emulator"
318
+ name = "emulator";
319
+ }
320
+ }
321
+ }
322
+ if (printTarget) {
323
+ console.log(chalk.green("[Info] Set target device : " + name));
324
+ }
325
+ next(null, 'name', name);
326
+ },
327
+
328
+ getDeviceBy: function(key, value, next) {
329
+ log.info("novacom#Resolver()#getDeviceBy()", "key:", key, ", value:", value);
330
+ const devices = this.devices.filter(function(device) {
331
+ return device[key] && device[key] === value;
332
+ });
333
+
334
+ if (devices.length < 1) {
335
+ setImmediate(next, errHndl.getErrMsg("UNMATCHED_DEVICE", key, value));
336
+ } else if (typeof next === 'function') {
337
+ setImmediate(next, null, devices[0]);
338
+ } else {
339
+ return devices[0];
340
+ }
341
+ },
342
+
343
+ getSshPrvKey: function(target, next) {
344
+ let name = (typeof target === 'string' ? target : target && target.name);
345
+ if (!name) {
346
+ // if name is not specified, find default target
347
+ const defaultTargets = this.devices.filter(function(device) {
348
+ return (device.default && device.default === true);
349
+ });
350
+ if (defaultTargets.length > 1) {
351
+ return next(errHndl.getErrMsg("SET_DEFAULT_MULTI_DEVICE"));
352
+ } else if (defaultTargets.length === 1) {
353
+ name = defaultTargets[0].name;
354
+ } else {
355
+ // if cannot find default target in list, set to "emulator"
356
+ name = "emulator";
357
+ }
358
+ // assign target name
359
+ if (typeof target === 'string') {
360
+ target = name;
361
+ } else if (typeof target === 'object') {
362
+ target.name = name;
363
+ }
364
+ }
365
+
366
+ async.waterfall([
367
+ this.getDeviceBy.bind(this, 'name', name),
368
+ function(targetDevice, next) {
369
+ log.info("Resolver#getSshPrvKey()", "targetDevice.host:", targetDevice.host);
370
+ const url = 'http://' + targetDevice.host + ':9991' + '/webos_rsa';
371
+ const keyFileNamePrefix = targetDevice.name.replace(/(\s+)/gi, '_');
372
+ const keyFileName = keyFileNamePrefix + "_webos";
373
+ const keySavePath = path.join(keydir, keyFileName);
374
+ request.head(url, function(err, res) {
375
+ if (err || (res && res.statusCode !== 200)) {
376
+ return setImmediate(next, errHndl.getErrMsg("FAILED_GET_SSHKEY"));
377
+ }
378
+ log.info("Resolver#getSshPrvKey()#head", "content-type:", res.headers['content-type']);
379
+ log.info("Resolver#getSshPrvKey()#head", "content-length:", res.headers['content-length']);
380
+ request(url).pipe(fs.createWriteStream(keySavePath)).on('close', function(error) {
381
+ if (error) {
382
+ return setImmediate(next, errHndl.getErrMsg("FAILED_GET_SSHKEY"));
383
+ } else {
384
+ setImmediate(next, error, keySavePath, keyFileName);
385
+ }
386
+ });
387
+ });
388
+ },
389
+ function(keyFilePath, keyFileName, next) {
390
+ log.info("Resolver#getSshPrvKey()", "SSH Private Key:", keyFilePath);
391
+ console.log("SSH Private Key:", keyFilePath);
392
+ fs.chmodSync(keyFilePath, '0600');
393
+ setImmediate(next, null, keyFileName);
394
+ }
395
+ ], next);
396
+ },
397
+
398
+ modifyDeviceFile: function(op, target, next) {
399
+ log.info("novacom#Resolver()#modifyDeviceFile()", "op:", op);
400
+ let defaultTarget = "emulator",
401
+ inDevices = cliData.getDeviceList(true);
402
+
403
+ if (!target.name) {
404
+ return setImmediate(next, errHndl.getErrMsg("EMPTY_VALUE", "target"));
405
+ }
406
+
407
+ if (!Array.isArray(inDevices)) {
408
+ return setImmediate(next, errHndl.getErrMsg("INVALID_FILE"));
409
+ }
410
+
411
+ const matchedDevices = inDevices.filter(function(dev) {
412
+ let match = false;
413
+ if (target.name === dev.name) {
414
+ if (target.profile) {
415
+ if (target.profile === dev.profile) {
416
+ match = true;
417
+ }
418
+ } else {
419
+ match = true;
420
+ }
421
+ }
422
+ return match;
423
+ });
424
+
425
+ if (op === 'add') {
426
+ if (matchedDevices.length > 0) {
427
+ return setImmediate(next, errHndl.getErrMsg("EXISTING_VALUE", "DEVICE_NAME", target.name));
428
+ }
429
+
430
+ for (const key in target) {
431
+ if (target[key] === "@DELETE@") {
432
+ delete target[key];
433
+ }
434
+ }
435
+
436
+ if (target.default === true) {
437
+ defaultTarget = target.name;
438
+ } else if (target.default === false) {
439
+ // new device is not a default target, keep current default target device.
440
+ defaultTarget = null;
441
+ }
442
+
443
+ inDevices = inDevices.concat(target);
444
+ } else if (op === 'remove' || op === 'modify' || op === 'default') {
445
+ if (matchedDevices.length === 0) {
446
+ return setImmediate(next, errHndl.getErrMsg("INVALID_VALUE", "DEVICE_NAME", target.name));
447
+ }
448
+
449
+ if (op === 'remove') {
450
+ inDevices = inDevices.filter(function(dev) {
451
+ if (target.name === dev.name) {
452
+ if (dev.indelible === true) {
453
+ return setImmediate(next, errHndl.getErrMsg("CANNOT_REMOVE_DEVICE", dev.name));
454
+ } else {
455
+ // removed target is not default target, do not set defalut to others
456
+ if (dev.default === false) {
457
+ defaultTarget = null;
458
+ }
459
+ return false;
460
+ }
461
+ } else {
462
+ return true;
463
+ }
464
+ });
465
+ } else if (op === 'modify') {
466
+ inDevices = inDevices.map(function(dev) {
467
+ if (target.name === dev.name) {
468
+ if (dev.default === true) {
469
+ // keep current default device as modified target
470
+ defaultTarget = dev.name;
471
+ } else {
472
+ // do not change default device
473
+ defaultTarget = null;
474
+ }
475
+
476
+ for (const prop in target) {
477
+ if (Object.prototype.hasOwnProperty.call(target, prop)) {
478
+ dev[prop] = target[prop];
479
+ if (dev[prop] === "@DELETE@") {
480
+ delete dev[prop];
481
+ }
482
+ }
483
+ }
484
+ }
485
+ return dev;
486
+ });
487
+ } else if (op === 'default') {
488
+ inDevices.map(function(dev) {
489
+ if (target.name === dev.name) {
490
+ if (target.default === true) {
491
+ defaultTarget = target.name;
492
+ }
493
+ }
494
+ });
495
+ }
496
+ } else {
497
+ return setImmediate(next, errHndl.getErrMsg("UNKNOWN_OPERATOR", op));
498
+ }
499
+
500
+ // Set default target & others to no default target(false)
501
+ if (defaultTarget != null) {
502
+ inDevices = inDevices.map(function(dev) {
503
+ if (defaultTarget === dev.name) {
504
+ dev.default = true;
505
+ } else {
506
+ dev.default = false;
507
+ }
508
+ return dev;
509
+ });
510
+ }
511
+ this.save(inDevices, next);
512
+ }
513
+ };
514
+
515
+ /**
516
+ * @constructor
517
+ * @param {String} target the name of the target device to connect to. "default"
518
+ * @param {Function} next common-js callback, invoked when the Session becomes usable or definitively unusable (failed)
519
+ */
520
+ function Session(target, printTarget, next) {
521
+ if (typeof next !== 'function') {
522
+ throw errHndl.getErrMsg("MISSING_CALLBACK", "next", util.inspect(next));
523
+ }
524
+
525
+ const name = (typeof target === 'string' ? target : target && target.name);
526
+ log.info("novacom#Session()", "opening session to '" + (name ? name : "default device") + "'");
527
+ this.resolver = new Resolver();
528
+
529
+ async.waterfall([
530
+ this.resolver.load.bind(this.resolver),
531
+ this.resolver.setTargetName.bind(this.resolver, target, printTarget),
532
+ this.resolver.getDeviceBy.bind(this.resolver),
533
+ this.checkConnection.bind(this),
534
+ this.begin.bind(this)
535
+ ], next);
536
+ }
537
+
538
+ novacom.Session = Session;
539
+
540
+ novacom.Session.prototype = {
541
+ /**
542
+ * Check if socket can be connected
543
+ * This method can be called multiple times.
544
+ * @param {Function} next common-js callback
545
+ */
546
+ checkConnection: function(target, next) {
547
+ let alive = false;
548
+ if (target && target.host && target.port) {
549
+ const socket = new net.Socket();
550
+ socket.setTimeout(2000);
551
+ const client = socket.connect({
552
+ host: target.host,
553
+ port: target.port
554
+ });
555
+ client.on('connect', function() {
556
+ alive = true;
557
+ client.end();
558
+ setImmediate(next, null, target);
559
+ });
560
+ client.on('error', function(err) {
561
+ client.destroy();
562
+ setImmediate(next, errHndl.getErrMsg(err));
563
+ });
564
+ client.on('timeout', function() {
565
+ client.destroy();
566
+ if (!alive) {
567
+ setImmediate(next, errHndl.getErrMsg("TIME_OUT"));
568
+ }
569
+ });
570
+ } else {
571
+ setImmediate(next, null, target);
572
+ }
573
+ },
574
+
575
+ /**
576
+ * Begin a novacom session with the current target
577
+ * This method can be called multiple times.
578
+ * @param {Function} next common-js callback
579
+ */
580
+ begin: function(target, next) {
581
+ log.verbose("novacom#Session()#begin()", "target:", target.display);
582
+ const self = this;
583
+ this.target = target || this.target;
584
+
585
+ if (this.target.conn && (this.target.conn.indexOf('ssh') === -1)) {
586
+ setImmediate(next, null, this);
587
+ return this;
588
+ }
589
+
590
+ if (this.target.privateKey === undefined && this.target.password === undefined) {
591
+ return setImmediate(next, errHndl.getErrMsg("NOT_EXIST_SSHKEY_PASSWD"));
592
+ }
593
+
594
+ if (!this.ssh) {
595
+ this.forwardedPorts = [];
596
+ this.ssh = new Ssh2();
597
+ this.ssh.on('connect', function() {
598
+ log.info("novacom#Session()#begin()", "ssh session event: connected");
599
+ });
600
+ this.ssh.on('ready', _next.bind(this));
601
+ this.ssh.on('error', _next.bind(this));
602
+ this.ssh.on('end', function() {
603
+ log.info("novacom#Session()#begin()", "ssh session event: end");
604
+ });
605
+ this.ssh.on('close', function(had_error) {
606
+ log.info("novacom#Session()#begin()", "ssh session event: close (had_error:", had_error, ")");
607
+ });
608
+ this.target.readyTimeout = 30000;
609
+ this.ssh.connect(this.target);
610
+
611
+ process.on("SIGHUP", _clearSession);
612
+ process.on("SIGINT", _clearSession);
613
+ process.on("SIGQUIT", _clearSession);
614
+ process.on("SIGTERM", _clearSession);
615
+ process.on("exit", function() {
616
+ _clearSession();
617
+ });
618
+ // Node.js cannot handle SIGKILL, SIGSTOP
619
+ // process.on("SIGKILL", _clearSession);
620
+ // process.on("SIGSTOP", _clearSession);
621
+ }
622
+ return this;
623
+
624
+ function _next(err) {
625
+ setImmediate(next, (err ? errHndl.getErrMsg(err) : err), this);
626
+ }
627
+
628
+ function _clearSession() {
629
+ log.verbose("novacom#Session()#begin()", "clear Session");
630
+ self.end();
631
+ setTimeout(function() {
632
+ process.exit();
633
+ }, 500);
634
+ }
635
+ },
636
+
637
+ /**
638
+ * @return the resolved device actually in use for this session
639
+ */
640
+ getDevice: function() {
641
+ return this.target;
642
+ },
643
+
644
+ /**
645
+ * Suspend the novacom session. Underlying resources
646
+ * are released (eg. SSH connections are closed).
647
+ */
648
+ end: function() {
649
+ log.info('novacom#Session()#end()', "user-requested termination");
650
+ if (this.ssh) {
651
+ this.ssh.end();
652
+ }
653
+ return this;
654
+ },
655
+
656
+ _checkSftp: function(next) {
657
+ // FIXME: This is workaround to prevent hang from ssh2.sftp()
658
+ // - issue in ssh2: https://github.com/mscdex/ssh2/issues/240
659
+ // This way only works with ssh2@0.2.x, not working with ssh2@0.4.x, ssh2@0.3.x.
660
+ const self = this;
661
+ self.ssh.subsys('sftp', function(err, _stream) {
662
+ if (err) {
663
+ return setImmediate(next, err);
664
+ }
665
+
666
+ _stream.once('data', function(data) {
667
+ const regex = new RegExp("sftp-server(.| )+not found", "gi");
668
+ if (data.toString().match(regex)) {
669
+ const sftpError = errHndl.getErrMsg("UNABLE_USE_SFTP");
670
+ sftpError.code = 4;
671
+ return setImmediate(next, sftpError);
672
+ }
673
+ });
674
+ });
675
+ },
676
+
677
+ /**
678
+ * Upload a file on the device
679
+ * @param {String} inPath location on the host
680
+ * @param {String} outPath location on the device
681
+ * @param {Function} next common-js callback
682
+ */
683
+ put: function(inPath, outPath, next) {
684
+ log.info("novacom#Session()#put()", "uploding into device:", outPath, "from host:", inPath);
685
+ const self = this;
686
+ let inStream;
687
+
688
+ log.verbose("novacom#Session()#put()", "sftpPut()::start");
689
+ self.sftpPut(inPath, outPath, function(err) {
690
+ if (err) {
691
+ log.verbose(err);
692
+ if (4 === err.code || 127 === err.code) {
693
+ log.info("novacom#Session()#put()", "sftp is not available, attempt transfering file via streamPut");
694
+ inStream = fs.createReadStream(inPath);
695
+ self.streamPut(outPath, inStream, next);
696
+ } else if (14 === err.code) {
697
+ const detailMsg = errHndl.getErrMsg("NO_FREE_SPACE");
698
+ setImmediate(next, detailMsg);
699
+ } else {
700
+ setImmediate(next, err);
701
+ }
702
+ } else {
703
+ log.info("novacom#Session()#put()", "sftpPut()::done");
704
+ setImmediate(next);
705
+ }
706
+ });
707
+ },
708
+
709
+ /**
710
+ * Upload a file on the device via ssh stream
711
+ * @param {String} outPath location on the device
712
+ * @param {ReadableStream} inStream paused host-side source
713
+ * @param {Function} next common-js callback
714
+ */
715
+ streamPut: function(outPath, inStream, next) {
716
+ log.info("novacom#Session()#streamPut()", "streaming into device:" + outPath);
717
+ const cmd = '/bin/cat > "' + outPath + '"';
718
+ this.run(cmd, inStream /* stdin*/ , null /* stdout*/ , process.stderr /* stderr*/ , next);
719
+ },
720
+
721
+ /**
722
+ * Upload a file on the device via sftp
723
+ * @param {String} inPath location on the host
724
+ * @param {String} outPath location on the device
725
+ * @param {Function} next common-js callback
726
+ */
727
+ sftpPut: function(inPath, outPath, next) {
728
+ log.verbose('novacom#Session()#sftpPut()', 'host:' + inPath + ' => ' + 'device:' + outPath);
729
+ const self = this;
730
+ self._checkSftp(next);
731
+
732
+ async.series({
733
+ transfer: function(next) {
734
+ self.ssh.sftp(function(err, sftp) {
735
+ if (err) {
736
+ return setImmediate(next, err);
737
+ }
738
+
739
+ const readStream = fs.createReadStream(inPath),
740
+ writeStream = sftp.createWriteStream(outPath);
741
+
742
+ writeStream.on('close', function() {
743
+ sftp.end();
744
+ setImmediate(next);
745
+ });
746
+
747
+ // Exit when the remote process has terminated
748
+ writeStream.on('exit', function(code, signal) {
749
+ err = makeExecError('sftpPut', code, signal);
750
+ setImmediate(next, err);
751
+ });
752
+
753
+ writeStream.on('error', function(error) {
754
+ log.verbose('novacom#Session()#sftpPut()', "error:", error);
755
+ setImmediate(next, error);
756
+ });
757
+
758
+ readStream.pipe(writeStream);
759
+ });
760
+ }
761
+ }, function(err) {
762
+ setImmediate(next, err);
763
+ });
764
+ },
765
+
766
+ /**
767
+ * Download file on the device
768
+ * @param {String} inPath location on the device
769
+ * @param {String} outPath location on the host
770
+ * @param {Function} next common-js callback
771
+ */
772
+ get: function(inPath, outPath, next) {
773
+ log.verbose("novacom#Session()#get()", "downloading into host:", outPath, "from target:", inPath);
774
+ const self = this;
775
+
776
+ log.verbose("novacom#Session()#get()", "sftpGet()::start");
777
+ self.sftpGet(inPath, outPath, function(err) {
778
+ if (err) {
779
+ log.verbose(err);
780
+ if (4 === err.code || 127 === err.code) {
781
+ log.info("novacom#Session()#get()", "sftp is not available, attempt transfering file via streamPut");
782
+ const os = fs.createWriteStream(outPath);
783
+ os.on('error', function(error) {
784
+ setImmediate(next, errHndl.getErrMsg(error));
785
+ });
786
+ self.streamGet(inPath, os, next);
787
+ } else {
788
+ setImmediate(next, err);
789
+ }
790
+ } else {
791
+ log.verbose("novacom#Session()#get()", "sftpGet()::done");
792
+ setImmediate(next);
793
+ }
794
+ });
795
+ },
796
+
797
+ /**
798
+ * Read a file from the device via ssh stream
799
+ * @param {String} inPath the device file path to be read
800
+ * @param {WritableStream} outStream host-side destination to copy the file into
801
+ * @param {Function} next commonJS callback invoked upon completion or failure
802
+ */
803
+ streamGet: function(inPath, outStream, next) {
804
+ log.verbose('novacom#Session()#streamGet()', "streaming from device:" + inPath);
805
+ const cmd = '/bin/cat ' + inPath;
806
+ this.run(cmd, null /* stdin*/ , outStream /* stdout*/ , process.stderr /* stderr*/ , next);
807
+ },
808
+
809
+ /**
810
+ * Download file on the device via sftp
811
+ * @param {String} inPath location on the device
812
+ * @param {String} outPath location on the host
813
+ * @param {Function} next common-js callback
814
+ */
815
+ sftpGet: function(inPath, outPath, next) {
816
+ log.verbose("novacom#Session()#sftpGet()", "target:" + inPath + " => " + "host:" + outPath);
817
+ const self = this;
818
+ self._checkSftp(next);
819
+ async.series({
820
+ transfer: function(next) {
821
+ self.ssh.sftp(function(err, sftp) {
822
+ if (err) {
823
+ setImmediate(next, err);
824
+ return;
825
+ }
826
+
827
+ const readStream = sftp.createReadStream(inPath),
828
+ writeStream = fs.createWriteStream(outPath);
829
+
830
+ readStream.on('close', function() {
831
+ sftp.end();
832
+ setImmediate(next);
833
+ });
834
+
835
+ // Exit when the remote process has terminated
836
+ readStream.on('exit', function(code, signal) {
837
+ err = makeExecError('sftpGet', code, signal);
838
+ setImmediate(next, err);
839
+ });
840
+
841
+ readStream.on('error', function(error) {
842
+ log.verbose("novacom#Session()#sftpGet()", "error:", error);
843
+ setImmediate(next, error);
844
+ });
845
+
846
+ readStream.pipe(writeStream);
847
+ });
848
+ }
849
+ }, function(err) {
850
+ setImmediate(next, err);
851
+ });
852
+ },
853
+
854
+ /**
855
+ * Run a command on the device
856
+ * @param {String} cmd the device command to run
857
+ * @param {stream.ReadableStream} stdin given as novacom process stdin
858
+ * @param {stream.WritableStream} stdout given as novacom process stdout
859
+ * @param {stream.WritableStream} stderr given as novacom process stderr
860
+ * @param {Function} next commonJS callback invoked upon completion or failure
861
+ */
862
+ run: function(cmd, stdin, stdout, stderr, next) {
863
+ this.run_ssh(cmd, {}, stdin, stdout, stderr, next);
864
+ },
865
+
866
+ /**
867
+ * Run a command with exec option on the device
868
+ * @param {String} cmd the device command to run
869
+ * @param {Object} opt given as exec option
870
+ * @param {stream.ReadableStream} stdin given as novacom process stdin
871
+ * @param {stream.WritableStream} stdout given as novacom process stdout
872
+ * @param {stream.WritableStream} stderr given as novacom process stderr
873
+ * @param {Function} next commonJS callback invoked upon completion or failure
874
+ */
875
+ runWithOption: function(cmd, opt, stdin, stdout, stderr, next) {
876
+ this.run_ssh(cmd, opt, stdin, stdout, stderr, next);
877
+ },
878
+
879
+ /**
880
+ * Run a command on the device
881
+ * @param {String} cmd the device command to run
882
+ * @param {Object} opt given as exec option
883
+ * @param {stream.ReadableStream} stdin given as novacom process stdin
884
+ * @param {stream.WritableStream} stdout given as novacom process stdout
885
+ * @param {stream.WritableStream} stderr given as novacom process stderr
886
+ * @param {Function} next commonJS callback invoked upon completion or failure
887
+ */
888
+ run_ssh: function(cmd, opt, stdin, stdout, stderr, next) {
889
+ log.info("novacom#Session()#run()", "cmd:" + cmd + ", opt:" + JSON.stringify(opt));
890
+ if (typeof next !== 'function') {
891
+ throw errHndl.getErrMsg("MISSING_CALLBACK", "next", util.inspect(next));
892
+ }
893
+
894
+ // plumb output
895
+ const write = {},
896
+ obj = {};
897
+ let orgErrMsg;
898
+
899
+ if (!stdout) {
900
+ log.silly("novacom#Session()#run()", "stdout: none");
901
+ write.stdout = function() {};
902
+ } else if (stdout instanceof stream.Stream) {
903
+ log.silly("novacom#Session()#run()", "stdout: stream");
904
+ write.stdout = stdout.write;
905
+ obj.stdout = stdout;
906
+ } else if (stdout instanceof Function) {
907
+ log.silly("novacom#Session()#run()", "stdout: function");
908
+ write.stdout = stdout;
909
+ } else {
910
+ setImmediate(next, errHndl.getErrMsg("INVALID_VALUE", "stdout", util.inspect(stdout)));
911
+ }
912
+
913
+ if (!stderr) {
914
+ log.silly("novacom#Session()#run()", "stderr: none");
915
+ write.stderr = function() {};
916
+ } else if (stderr instanceof stream.Stream) {
917
+ log.silly("novacom#Session()#run()", "stderr: stream");
918
+ write.stderr = stderr.write;
919
+ obj.stderr = stderr;
920
+ } else if (stderr instanceof Function) {
921
+ log.silly("novacom#Session()#run()", "stderr: function");
922
+ write.stderr = stderr;
923
+ } else {
924
+ setImmediate(next, errHndl.getErrMsg("INVALID_VALUE", "stderr", util.inspect(stderr)));
925
+ }
926
+
927
+ // execute command
928
+ this.ssh.exec(cmd, opt, (function(err, chStream) {
929
+ log.silly("novacom#Session()#run()", "exec cmd:" + cmd + ", opt:" + JSON.stringify(opt) + ", err:" + err);
930
+ if (err) {
931
+ return setImmediate(next, err);
932
+ }
933
+
934
+ // manual pipe(): handle & divert data chunks
935
+ chStream.on('data', function(data, extended) {
936
+ extended = extended || 'stdout';
937
+ log.verbose("novacom#Session()#run()", "on data (" + extended + ")");
938
+ write[extended].bind(obj[extended])(data);
939
+ }).stderr.on('data', function(data) {
940
+ log.verbose("novacom#Session()#run()", "on data (stderr)");
941
+ orgErrMsg = data.toString();
942
+ });
943
+
944
+ // manual pipe(): handle EOF
945
+ chStream.on('end', function() {
946
+ log.silly("novacom#Session()#run()", "event EOF from (cmd:" + cmd + ")");
947
+ if ((stdout !== process.stdout) && (stdout instanceof stream.Stream)) {
948
+ stdout.end();
949
+ }
950
+ if ((stderr !== process.stderr) && (stderr instanceof stream.Stream)) {
951
+ stderr.end();
952
+ }
953
+ });
954
+
955
+ // Exit when the remote process has terminated
956
+ chStream.on('exit', function(code, signal) {
957
+ log.silly("novacom#Session()#run()", "event exit code:" + code + ', signal:' + signal + " (cmd:" + cmd + ")");
958
+ err = makeExecError(cmd, code, signal, orgErrMsg);
959
+ setImmediate(next, err);
960
+ });
961
+
962
+ // Exit if the 'exit' event was not
963
+ // received (dropbear <= 0.51)
964
+ chStream.on('close', function() {
965
+ log.silly("novacom#Session()#run()", "event close (cmd:" + cmd + ")");
966
+ if (err === undefined) {
967
+ setImmediate(next);
968
+ }
969
+ });
970
+
971
+ if (stdin) {
972
+ stdin.pipe(chStream);
973
+ log.verbose("novacom#Session()#run()", "resuming input");
974
+ }
975
+ }));
976
+ },
977
+
978
+ /**
979
+ * Run a command on the device considerless return stdout
980
+ * @param {String} cmd the device command to run
981
+ * @param {Function} callback invoked upon exit event
982
+ * @param {Function} next commonJS callback invoked upon completion or failure
983
+ */
984
+ runNoHangup: function(cmd, cbData, cbExit, next) {
985
+ this.runNoHangup_ssh(cmd, cbData, cbExit, next);
986
+ },
987
+
988
+ /**
989
+ * Run a command on the device considerless return stdout
990
+ * @param {String} cmd the device command to run
991
+ * @param {Function} callback invoked upon exit event
992
+ * @param {Function} next commonJS callback invoked upon completion or failure
993
+ */
994
+ runNoHangup_ssh: function(cmd, cbData, cbExit, next) {
995
+ log.info("novacom#Session()#runNoHangup()", "cmd=" + cmd);
996
+ if (arguments.length < 2) {
997
+ throw errHndl.getErrMsg("MISSING_CALLBACK", "next");
998
+ }
999
+
1000
+ for (const arg in arguments) {
1001
+ if (typeof arguments[arg] === 'undefined') {
1002
+ delete arguments[arg];
1003
+ arguments.length--;
1004
+ }
1005
+ }
1006
+
1007
+ switch (arguments.length) {
1008
+ case 2:
1009
+ next = cbData;
1010
+ cbData = cbExit = null;
1011
+ break;
1012
+ case 3:
1013
+ next = cbExit;
1014
+ cbExit = cbData;
1015
+ cbData = null;
1016
+ break;
1017
+ default:
1018
+ break;
1019
+ }
1020
+
1021
+ if (typeof next !== 'function') {
1022
+ throw errHndl.getErrMsg("MISSING_CALLBACK", "next", util.inspect(next));
1023
+ }
1024
+
1025
+ // execute command
1026
+ this.ssh.exec(cmd, (function(err, _stream) {
1027
+ log.verbose("novacom#Session()#run()", "exec cmd:" + cmd + ", err:" + err);
1028
+ if (err) {
1029
+ return setImmediate(next, err);
1030
+ }
1031
+
1032
+ _stream.on('data', function(data) {
1033
+ const str = (Buffer.isBuffer(data)) ? data.toString() : data;
1034
+ log.verbose("novacom#Session()#runNoHangup()#onData", str);
1035
+ if (cbData) cbData(data);
1036
+ }).stderr.on('data', function(data) {
1037
+ const str = (Buffer.isBuffer(data)) ? data.toString() : data;
1038
+ log.verbose("novacom#Session()#runNoHangup()#onData#stderr#", str);
1039
+ if (cbData) cbData(data);
1040
+ });
1041
+
1042
+ // Exit when the remote process has terminated
1043
+ if (cbExit) {
1044
+ _stream.on('exit', function(code, signal) {
1045
+ log.verbose("novacom#Session()#runNoHangup()", "event exit code=" + code + ', signal=' + signal + " (cmd: " + cmd + ")");
1046
+ err = makeExecError(cmd, code, signal);
1047
+ cbExit(err);
1048
+ });
1049
+ }
1050
+ setImmediate(next);
1051
+ }));
1052
+ },
1053
+
1054
+ /**
1055
+ * Forward the given device port on the host.
1056
+ * As any other public method, this one can be called
1057
+ * only once the ssh session has emitted the 'ready'
1058
+ * event, so as part of the Session()#next callback.
1059
+ * @public
1060
+ * @param {Function} next commonJS callback invoked upon completion or failure
1061
+ */
1062
+ forward: function(devicePort, localPort, forwardName, next) {
1063
+ log.info("novacom#Session()#forward()", "devicePort:", devicePort, ", localPort:", localPort);
1064
+ const session = this;
1065
+ let forwardInUse = false,
1066
+ registerName = null;
1067
+
1068
+ if (typeof forwardName === 'function') {
1069
+ next = forwardName;
1070
+ } else if (forwardName) {
1071
+ registerName = forwardName;
1072
+ }
1073
+
1074
+ if (localPort !== 0) {
1075
+ if (session.forwardedPorts.indexOf({
1076
+ name: registerName,
1077
+ local: localPort,
1078
+ device: devicePort
1079
+ }) > 0) {
1080
+ forwardInUse = true;
1081
+ }
1082
+ } else if (session.forwardedPorts.filter(function(forwardItem) {
1083
+ return (forwardItem.device === devicePort && forwardItem.name === registerName);
1084
+ }).length > 0) {
1085
+ forwardInUse = true;
1086
+ }
1087
+
1088
+ if (forwardInUse) {
1089
+ return setImmediate(next);
1090
+ }
1091
+
1092
+ const localServer = net.createServer(function(inCnx) {
1093
+ log.info("novacom#Session()#forward()", "new client, localPort:", localPort);
1094
+ log.verbose("novacom#Session()#forward()", "new client, from: " + inCnx.remoteAddress + ':' + inCnx.remotePort);
1095
+
1096
+ inCnx.on('error', function(err) {
1097
+ log.verbose("novacom#Session()#forward()", "inCnx::error, err::" + err);
1098
+ });
1099
+
1100
+ // Open the outbound connection on the device to match the incoming client.
1101
+ session.ssh.forwardOut("127.0.0.1" /* srcAddr*/ , inCnx.remotePort /* srcPort*/ , "127.0.0.1" /* dstAddr*/ , devicePort /* dstPort*/ , function(err, outCnx) {
1102
+ if (err) {
1103
+ console.log("novacom#Session()#forward()", "failed forwarding client localPort:",
1104
+ localPort, "(inCnx.remotePort:", inCnx.remotePort, ")=> devicePort:", devicePort);
1105
+ log.warn("novacom#Session()#forward()", "failed forwarding client localPort:",
1106
+ localPort, "=> devicePort:", devicePort);
1107
+ inCnx.destroy();
1108
+ return;
1109
+ }
1110
+
1111
+ log.info("novacom#Session()#forward()", "connected, devicePort:", devicePort);
1112
+ inCnx.on('data', function(data) {
1113
+ if (outCnx.writable && outCnx.writable === true) {
1114
+ if (outCnx.write(data) === false) {
1115
+ inCnx.pause();
1116
+ }
1117
+ }
1118
+ });
1119
+
1120
+ inCnx.on('close', function(had_err) {
1121
+ log.verbose("novacom#Session()#forward()", "inCnx::close, had_err:", had_err);
1122
+ outCnx.destroy();
1123
+ });
1124
+
1125
+ outCnx.on('drain', function() {
1126
+ inCnx.resume();
1127
+ });
1128
+
1129
+ outCnx.on('data', function(data) {
1130
+ inCnx.write(data);
1131
+ });
1132
+
1133
+ outCnx.on('close', function(had_err) {
1134
+ log.verbose("novacom#Session()#forward()", "outCnx::close, had_err:", had_err);
1135
+ });
1136
+ });
1137
+ });
1138
+
1139
+ session.ssh.on('close', function() {
1140
+ localServer.close();
1141
+ });
1142
+
1143
+ try {
1144
+ localServer.listen(localPort, null, (function() {
1145
+ const localServerPort = localServer.address().port;
1146
+ session.forwardedPorts.push({
1147
+ name: registerName,
1148
+ local: localServerPort,
1149
+ device: devicePort
1150
+ });
1151
+ setImmediate(next);
1152
+ }));
1153
+ } catch (err) {
1154
+ setImmediate(next, err);
1155
+ }
1156
+ },
1157
+
1158
+ getLocalPortByName: function(queryName) {
1159
+ const session = this;
1160
+ let found = null;
1161
+ session.forwardedPorts.forEach(function(portItem) {
1162
+ if (portItem.name === queryName) {
1163
+ found = portItem.local;
1164
+ return;
1165
+ }
1166
+ });
1167
+ return found;
1168
+ },
1169
+
1170
+ runHostedAppServer: function(url, next) {
1171
+ server.runServer(url, 0, function(err, serverInfo) {
1172
+ if (serverInfo && serverInfo.port) {
1173
+ this.setHostedAppServerPort(serverInfo.port);
1174
+ }
1175
+ next(err);
1176
+ }.bind(this));
1177
+ },
1178
+
1179
+ setHostedAppServerPort: function(port) {
1180
+ this.hostedAppServerPort = port;
1181
+ },
1182
+
1183
+ getHostedAppServerPort: function() {
1184
+ return this.hostedAppServerPort;
1185
+ }
1186
+ };
1187
+
1188
+ if (typeof module !== 'undefined' && module.exports) {
1189
+ module.exports = novacom;
1190
+ }
1191
+ }());