@webos-tools/cli 3.0.6 → 3.1.1
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.
- package/.eslintignore +1 -1
- package/.eslintrc.js +52 -52
- package/APIs.js +79 -79
- package/CHANGELOG.md +157 -138
- package/LICENSE +201 -201
- package/README.md +218 -218
- package/bin/ares-config.js +199 -199
- package/bin/ares-device-info.js +30 -30
- package/bin/ares-device.js +219 -219
- package/bin/ares-generate.js +274 -270
- package/bin/ares-inspect.js +179 -179
- package/bin/ares-install.js +223 -223
- package/bin/ares-launch.js +318 -318
- package/bin/ares-log.js +255 -255
- package/bin/ares-novacom.js +220 -223
- package/bin/ares-package.js +336 -336
- package/bin/ares-pull.js +156 -156
- package/bin/ares-push.js +155 -155
- package/bin/ares-server.js +174 -174
- package/bin/ares-setup-device.js +528 -520
- package/bin/ares-shell.js +132 -132
- package/bin/ares.js +166 -166
- package/files/conf/ares.json +49 -49
- package/files/conf/command-service.json +73 -73
- package/files/conf/config.json +31 -22
- package/files/conf/ipk.json +30 -30
- package/files/conf/novacom-devices.json +35 -35
- package/files/conf/query/query-app.json +14 -14
- package/files/conf/query/query-hosted.json +18 -18
- package/files/conf/query/query-package.json +10 -10
- package/files/conf/query/query-service.json +6 -6
- package/files/conf/sdk.json +8 -8
- package/files/conf/template.json +57 -57
- package/files/conf/webos_emul +27 -27
- package/files/conf-base/env/sdk-ose.json +8 -8
- package/files/conf-base/env/sdk-tv.json +8 -8
- package/files/conf-base/profile/config-ose.json +29 -21
- package/files/conf-base/profile/config-tv.json +31 -22
- package/files/conf-base/query/query-app.json +14 -14
- package/files/conf-base/query/query-hosted.json +18 -18
- package/files/conf-base/query/query-package.json +10 -10
- package/files/conf-base/query/query-service.json +6 -6
- package/files/conf-base/template-conf/ose-templates.json +67 -67
- package/files/conf-base/template-conf/tv-sdk-templates.json +57 -57
- package/files/help/ares-config.help +43 -43
- package/files/help/ares-device.help +94 -94
- package/files/help/ares-generate.help +65 -65
- package/files/help/ares-inspect.help +70 -70
- package/files/help/ares-install.help +90 -90
- package/files/help/ares-launch.help +100 -100
- package/files/help/ares-log-pmlogd.help +84 -84
- package/files/help/ares-log.help +101 -101
- package/files/help/ares-novacom.help +68 -68
- package/files/help/ares-package.help +101 -101
- package/files/help/ares-pull.help +38 -38
- package/files/help/ares-push.help +38 -38
- package/files/help/ares-server.help +39 -39
- package/files/help/ares-setup-device.help +116 -75
- package/files/help/ares-shell.help +42 -42
- package/files/help/ares.help +47 -47
- package/files/help/readme.help +23 -23
- package/files/schema/ApplicationDescription.schema +319 -319
- package/files/schema/NovacomDevices.schema +61 -61
- package/files/templates/ose-sdk-templates/appinfo/appinfo.json +10 -10
- package/files/templates/ose-sdk-templates/bootplate-web/index.html +88 -88
- package/files/templates/ose-sdk-templates/hosted-webapp/index.html +13 -13
- package/files/templates/ose-sdk-templates/icon/icon.png +0 -0
- package/files/templates/ose-sdk-templates/js-service/helloclient.js +31 -31
- package/files/templates/ose-sdk-templates/js-service/helloworld_webos_service.js +188 -188
- package/files/templates/ose-sdk-templates/qml-app/main.qml +68 -68
- package/files/templates/ose-sdk-templates/qmlappinfo/appinfo.json +10 -10
- package/files/templates/ose-sdk-templates/serviceinfo/package.json +11 -11
- package/files/templates/ose-sdk-templates/serviceinfo/services.json +8 -8
- package/files/templates/tv-sdk-templates/appinfo/appinfo.json +10 -10
- package/files/templates/tv-sdk-templates/bootplate-web/index.html +58 -58
- package/files/templates/tv-sdk-templates/bootplate-web/webOSTVjs-1.2.10/LICENSE-2.0.txt +202 -202
- package/files/templates/tv-sdk-templates/hosted-webapp/index.html +14 -14
- package/files/templates/tv-sdk-templates/js-service/helloworld_service.js +39 -39
- package/files/templates/tv-sdk-templates/packageinfo/packageinfo.json +3 -3
- package/files/templates/tv-sdk-templates/serviceinfo/package.json +11 -11
- package/files/templates/tv-sdk-templates/serviceinfo/services.json +8 -8
- package/files/templates/tv-sdk-templates/webicon/icon.png +0 -0
- package/files/templates/tv-sdk-templates/webicon/largeIcon.png +0 -0
- package/lib/base/ares.html +40 -40
- package/lib/base/cli-appdata.js +290 -290
- package/lib/base/cli-control.js +44 -44
- package/lib/base/common-tools.js +29 -29
- package/lib/base/error-handler.js +265 -265
- package/lib/base/file-watcher.js +155 -155
- package/lib/base/help-format.js +147 -147
- package/lib/base/luna.js +178 -178
- package/lib/base/novacom.js +1202 -1191
- package/lib/base/sdkenv.js +59 -59
- package/lib/base/server.js +137 -137
- package/lib/base/setup-device.js +335 -328
- package/lib/base/version-tools.js +79 -79
- package/lib/device.js +1419 -1419
- package/lib/generator.js +377 -377
- package/lib/inspect.js +493 -494
- package/lib/install.js +463 -463
- package/lib/launch.js +605 -605
- package/lib/log.js +584 -584
- package/lib/package.js +2147 -2129
- package/lib/pull.js +231 -231
- package/lib/pusher.js +210 -210
- package/lib/session.js +74 -74
- package/lib/shell.js +193 -193
- package/lib/tar-filter-pack.js +62 -62
- package/lib/util/copy.js +31 -31
- package/lib/util/createFileName.js +40 -40
- package/lib/util/eof.js +30 -30
- package/lib/util/json.js +63 -63
- package/lib/util/merge.js +14 -14
- package/lib/util/objclone.js +40 -40
- package/lib/util/spinner.js +37 -37
- package/npm-shrinkwrap.json +9242 -9116
- package/package.json +100 -100
- package/scripts/postinstall.js +24 -24
- package/spec/helpers/reporter.js +65 -65
- package/spec/jsSpecs/apiTest/generator.spec.js +372 -372
- package/spec/jsSpecs/apiTest/inspector.spec.js +89 -89
- package/spec/jsSpecs/apiTest/installer.spec.js +67 -67
- package/spec/jsSpecs/apiTest/launcher.spec.js +150 -150
- package/spec/jsSpecs/apiTest/packager.spec.js +194 -194
- package/spec/jsSpecs/apiTest/puller.spec.js +101 -101
- package/spec/jsSpecs/apiTest/pusher.spec.js +103 -103
- package/spec/jsSpecs/apiTest/server.spec.js +115 -115
- package/spec/jsSpecs/apiTest/setupDevice.spec.js +93 -93
- package/spec/jsSpecs/apiTest/shell.spec.js +49 -49
- package/spec/jsSpecs/ares-config.spec.js +78 -78
- package/spec/jsSpecs/ares-device.spec.js +443 -443
- package/spec/jsSpecs/ares-generate.spec.js +397 -397
- package/spec/jsSpecs/ares-inspect.spec.js +252 -252
- package/spec/jsSpecs/ares-install.spec.js +150 -150
- package/spec/jsSpecs/ares-launch.spec.js +301 -301
- package/spec/jsSpecs/ares-log.spec.js +824 -824
- package/spec/jsSpecs/ares-novacom.spec.js +149 -149
- package/spec/jsSpecs/ares-package.spec.js +1211 -1211
- package/spec/jsSpecs/ares-pull.spec.js +157 -157
- package/spec/jsSpecs/ares-push.spec.js +146 -146
- package/spec/jsSpecs/ares-server.spec.js +160 -160
- package/spec/jsSpecs/ares-setup-device.spec.js +300 -281
- package/spec/jsSpecs/ares-shell.spec.js +220 -220
- package/spec/jsSpecs/ares.spec.js +83 -83
- package/spec/jsSpecs/common-spec.js +169 -169
- package/spec/support/jasmine.json +22 -22
- package/spec/tempFiles/nativeApp/auto/pkg_arm64/GLES2 +0 -0
- package/spec/tempFiles/nativeApp/auto/pkg_arm64/appinfo.json +9 -9
- package/spec/tempFiles/nativeApp/ose/pkg_arm/Hello +0 -0
- package/spec/tempFiles/nativeApp/ose/pkg_arm/appinfo.json +8 -8
- package/spec/tempFiles/nativeApp/ose/pkg_arm/package.properties +2 -2
- package/spec/tempFiles/nativeApp/oseEmul/pkg_x86/Hello +0 -0
- package/spec/tempFiles/nativeApp/oseEmul/pkg_x86/appinfo.json +9 -9
- package/spec/tempFiles/nativeApp/rsi/pkg_x86/GLES2 +0 -0
- package/spec/tempFiles/nativeApp/rsi/pkg_x86/appinfo.json +9 -9
- package/spec/tempFiles/sign/sign.crt +32 -32
- package/spec/tempFiles/sign/signPriv.key +52 -52
- package/spec/test_data/ares-generate.json +41 -41
- package/spec/test_data/ares.json +33 -33
package/lib/base/novacom.js
CHANGED
|
@@ -1,1191 +1,1202 @@
|
|
|
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
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
*
|
|
723
|
-
* @param {
|
|
724
|
-
* @param {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
writeStream.on('
|
|
754
|
-
|
|
755
|
-
setImmediate(next
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
*
|
|
811
|
-
* @param {
|
|
812
|
-
* @param {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
readStream.on('
|
|
842
|
-
|
|
843
|
-
setImmediate(next
|
|
844
|
-
});
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
*
|
|
868
|
-
* @param {
|
|
869
|
-
* @param {
|
|
870
|
-
* @param {stream.
|
|
871
|
-
* @param {
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
*
|
|
881
|
-
* @param {
|
|
882
|
-
* @param {
|
|
883
|
-
* @param {stream.
|
|
884
|
-
* @param {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
if (
|
|
914
|
-
log.silly("novacom#Session()#run()", "
|
|
915
|
-
write.
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
} else
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
});
|
|
954
|
-
|
|
955
|
-
//
|
|
956
|
-
chStream.on('
|
|
957
|
-
log.silly("novacom#Session()#run()", "event
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
*
|
|
991
|
-
* @param {
|
|
992
|
-
* @param {Function}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
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.Client();
|
|
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
|
+
//Explicit overrides for the default transport layer algorithms used for the connection.
|
|
610
|
+
this.target.algorithms = {
|
|
611
|
+
"kex": [
|
|
612
|
+
"diffie-hellman-group1-sha1",
|
|
613
|
+
"ecdh-sha2-nistp256",
|
|
614
|
+
"ecdh-sha2-nistp384",
|
|
615
|
+
"ecdh-sha2-nistp521",
|
|
616
|
+
"diffie-hellman-group-exchange-sha256",
|
|
617
|
+
"diffie-hellman-group14-sha1"
|
|
618
|
+
],
|
|
619
|
+
}
|
|
620
|
+
this.ssh.connect(this.target);
|
|
621
|
+
|
|
622
|
+
process.on("SIGHUP", _clearSession);
|
|
623
|
+
process.on("SIGINT", _clearSession);
|
|
624
|
+
process.on("SIGQUIT", _clearSession);
|
|
625
|
+
process.on("SIGTERM", _clearSession);
|
|
626
|
+
process.on("exit", function() {
|
|
627
|
+
_clearSession();
|
|
628
|
+
});
|
|
629
|
+
// Node.js cannot handle SIGKILL, SIGSTOP
|
|
630
|
+
// process.on("SIGKILL", _clearSession);
|
|
631
|
+
// process.on("SIGSTOP", _clearSession);
|
|
632
|
+
}
|
|
633
|
+
return this;
|
|
634
|
+
|
|
635
|
+
function _next(err) {
|
|
636
|
+
setImmediate(next, (err ? errHndl.getErrMsg(err) : err), this);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function _clearSession() {
|
|
640
|
+
log.verbose("novacom#Session()#begin()", "clear Session");
|
|
641
|
+
self.end();
|
|
642
|
+
setTimeout(function() {
|
|
643
|
+
process.exit();
|
|
644
|
+
}, 500);
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* @return the resolved device actually in use for this session
|
|
650
|
+
*/
|
|
651
|
+
getDevice: function() {
|
|
652
|
+
return this.target;
|
|
653
|
+
},
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Suspend the novacom session. Underlying resources
|
|
657
|
+
* are released (eg. SSH connections are closed).
|
|
658
|
+
*/
|
|
659
|
+
end: function() {
|
|
660
|
+
log.info('novacom#Session()#end()', "user-requested termination");
|
|
661
|
+
if (this.ssh) {
|
|
662
|
+
this.ssh.end();
|
|
663
|
+
}
|
|
664
|
+
return this;
|
|
665
|
+
},
|
|
666
|
+
|
|
667
|
+
_checkSftp: function(next) {
|
|
668
|
+
// FIXME: This is workaround to prevent hang from ssh2.sftp()
|
|
669
|
+
// - issue in ssh2: https://github.com/mscdex/ssh2/issues/240
|
|
670
|
+
// This way only works with ssh2@0.2.x, not working with ssh2@0.4.x, ssh2@0.3.x.
|
|
671
|
+
const self = this;
|
|
672
|
+
self.ssh.subsys('sftp', function(err, _stream) {
|
|
673
|
+
if (err) {
|
|
674
|
+
return setImmediate(next, err);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
_stream.once('data', function(data) {
|
|
678
|
+
const regex = new RegExp("sftp-server(.| )+not found", "gi");
|
|
679
|
+
if (data.toString().match(regex)) {
|
|
680
|
+
const sftpError = errHndl.getErrMsg("UNABLE_USE_SFTP");
|
|
681
|
+
sftpError.code = 4;
|
|
682
|
+
return setImmediate(next, sftpError);
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
},
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Upload a file on the device
|
|
690
|
+
* @param {String} inPath location on the host
|
|
691
|
+
* @param {String} outPath location on the device
|
|
692
|
+
* @param {Function} next common-js callback
|
|
693
|
+
*/
|
|
694
|
+
put: function(inPath, outPath, next) {
|
|
695
|
+
log.info("novacom#Session()#put()", "uploding into device:", outPath, "from host:", inPath);
|
|
696
|
+
const self = this;
|
|
697
|
+
let inStream;
|
|
698
|
+
|
|
699
|
+
log.verbose("novacom#Session()#put()", "sftpPut()::start");
|
|
700
|
+
self.sftpPut(inPath, outPath, function(err) {
|
|
701
|
+
if (err) {
|
|
702
|
+
log.verbose(err);
|
|
703
|
+
if (4 === err.code || 127 === err.code) {
|
|
704
|
+
log.info("novacom#Session()#put()", "sftp is not available, attempt transfering file via streamPut");
|
|
705
|
+
inStream = fs.createReadStream(inPath);
|
|
706
|
+
self.streamPut(outPath, inStream, next);
|
|
707
|
+
} else if (14 === err.code) {
|
|
708
|
+
const detailMsg = errHndl.getErrMsg("NO_FREE_SPACE");
|
|
709
|
+
setImmediate(next, detailMsg);
|
|
710
|
+
} else {
|
|
711
|
+
setImmediate(next, err);
|
|
712
|
+
}
|
|
713
|
+
} else {
|
|
714
|
+
log.info("novacom#Session()#put()", "sftpPut()::done");
|
|
715
|
+
setImmediate(next);
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
},
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Upload a file on the device via ssh stream
|
|
722
|
+
* @param {String} outPath location on the device
|
|
723
|
+
* @param {ReadableStream} inStream paused host-side source
|
|
724
|
+
* @param {Function} next common-js callback
|
|
725
|
+
*/
|
|
726
|
+
streamPut: function(outPath, inStream, next) {
|
|
727
|
+
log.info("novacom#Session()#streamPut()", "streaming into device:" + outPath);
|
|
728
|
+
const cmd = '/bin/cat > "' + outPath + '"';
|
|
729
|
+
this.run(cmd, inStream /* stdin*/ , null /* stdout*/ , process.stderr /* stderr*/ , next);
|
|
730
|
+
},
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Upload a file on the device via sftp
|
|
734
|
+
* @param {String} inPath location on the host
|
|
735
|
+
* @param {String} outPath location on the device
|
|
736
|
+
* @param {Function} next common-js callback
|
|
737
|
+
*/
|
|
738
|
+
sftpPut: function(inPath, outPath, next) {
|
|
739
|
+
log.verbose('novacom#Session()#sftpPut()', 'host:' + inPath + ' => ' + 'device:' + outPath);
|
|
740
|
+
const self = this;
|
|
741
|
+
self._checkSftp(next);
|
|
742
|
+
|
|
743
|
+
async.series({
|
|
744
|
+
transfer: function(next) {
|
|
745
|
+
self.ssh.sftp(function(err, sftp) {
|
|
746
|
+
if (err) {
|
|
747
|
+
return setImmediate(next, err);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const readStream = fs.createReadStream(inPath),
|
|
751
|
+
writeStream = sftp.createWriteStream(outPath);
|
|
752
|
+
|
|
753
|
+
writeStream.on('close', function() {
|
|
754
|
+
sftp.end();
|
|
755
|
+
setImmediate(next);
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
// Exit when the remote process has terminated
|
|
759
|
+
writeStream.on('exit', function(code, signal) {
|
|
760
|
+
err = makeExecError('sftpPut', code, signal);
|
|
761
|
+
setImmediate(next, err);
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
writeStream.on('error', function(error) {
|
|
765
|
+
log.verbose('novacom#Session()#sftpPut()', "error:", error);
|
|
766
|
+
setImmediate(next, error);
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
readStream.pipe(writeStream);
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
}, function(err) {
|
|
773
|
+
setImmediate(next, err);
|
|
774
|
+
});
|
|
775
|
+
},
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Download file on the device
|
|
779
|
+
* @param {String} inPath location on the device
|
|
780
|
+
* @param {String} outPath location on the host
|
|
781
|
+
* @param {Function} next common-js callback
|
|
782
|
+
*/
|
|
783
|
+
get: function(inPath, outPath, next) {
|
|
784
|
+
log.verbose("novacom#Session()#get()", "downloading into host:", outPath, "from target:", inPath);
|
|
785
|
+
const self = this;
|
|
786
|
+
|
|
787
|
+
log.verbose("novacom#Session()#get()", "sftpGet()::start");
|
|
788
|
+
self.sftpGet(inPath, outPath, function(err) {
|
|
789
|
+
if (err) {
|
|
790
|
+
log.verbose(err);
|
|
791
|
+
if (4 === err.code || 127 === err.code) {
|
|
792
|
+
log.info("novacom#Session()#get()", "sftp is not available, attempt transfering file via streamPut");
|
|
793
|
+
const os = fs.createWriteStream(outPath);
|
|
794
|
+
os.on('error', function(error) {
|
|
795
|
+
setImmediate(next, errHndl.getErrMsg(error));
|
|
796
|
+
});
|
|
797
|
+
self.streamGet(inPath, os, next);
|
|
798
|
+
} else {
|
|
799
|
+
setImmediate(next, err);
|
|
800
|
+
}
|
|
801
|
+
} else {
|
|
802
|
+
log.verbose("novacom#Session()#get()", "sftpGet()::done");
|
|
803
|
+
setImmediate(next);
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
},
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* Read a file from the device via ssh stream
|
|
810
|
+
* @param {String} inPath the device file path to be read
|
|
811
|
+
* @param {WritableStream} outStream host-side destination to copy the file into
|
|
812
|
+
* @param {Function} next commonJS callback invoked upon completion or failure
|
|
813
|
+
*/
|
|
814
|
+
streamGet: function(inPath, outStream, next) {
|
|
815
|
+
log.verbose('novacom#Session()#streamGet()', "streaming from device:" + inPath);
|
|
816
|
+
const cmd = '/bin/cat ' + inPath;
|
|
817
|
+
this.run(cmd, null /* stdin*/ , outStream /* stdout*/ , process.stderr /* stderr*/ , next);
|
|
818
|
+
},
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Download file on the device via sftp
|
|
822
|
+
* @param {String} inPath location on the device
|
|
823
|
+
* @param {String} outPath location on the host
|
|
824
|
+
* @param {Function} next common-js callback
|
|
825
|
+
*/
|
|
826
|
+
sftpGet: function(inPath, outPath, next) {
|
|
827
|
+
log.verbose("novacom#Session()#sftpGet()", "target:" + inPath + " => " + "host:" + outPath);
|
|
828
|
+
const self = this;
|
|
829
|
+
self._checkSftp(next);
|
|
830
|
+
async.series({
|
|
831
|
+
transfer: function(next) {
|
|
832
|
+
self.ssh.sftp(function(err, sftp) {
|
|
833
|
+
if (err) {
|
|
834
|
+
setImmediate(next, err);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const readStream = sftp.createReadStream(inPath),
|
|
839
|
+
writeStream = fs.createWriteStream(outPath);
|
|
840
|
+
|
|
841
|
+
readStream.on('close', function() {
|
|
842
|
+
sftp.end();
|
|
843
|
+
setImmediate(next);
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
// Exit when the remote process has terminated
|
|
847
|
+
readStream.on('exit', function(code, signal) {
|
|
848
|
+
err = makeExecError('sftpGet', code, signal);
|
|
849
|
+
setImmediate(next, err);
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
readStream.on('error', function(error) {
|
|
853
|
+
log.verbose("novacom#Session()#sftpGet()", "error:", error);
|
|
854
|
+
setImmediate(next, error);
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
readStream.pipe(writeStream);
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
}, function(err) {
|
|
861
|
+
setImmediate(next, err);
|
|
862
|
+
});
|
|
863
|
+
},
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Run a command on the device
|
|
867
|
+
* @param {String} cmd the device command to run
|
|
868
|
+
* @param {stream.ReadableStream} stdin given as novacom process stdin
|
|
869
|
+
* @param {stream.WritableStream} stdout given as novacom process stdout
|
|
870
|
+
* @param {stream.WritableStream} stderr given as novacom process stderr
|
|
871
|
+
* @param {Function} next commonJS callback invoked upon completion or failure
|
|
872
|
+
*/
|
|
873
|
+
run: function(cmd, stdin, stdout, stderr, next) {
|
|
874
|
+
this.run_ssh(cmd, {}, stdin, stdout, stderr, next);
|
|
875
|
+
},
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Run a command with exec option on the device
|
|
879
|
+
* @param {String} cmd the device command to run
|
|
880
|
+
* @param {Object} opt given as exec option
|
|
881
|
+
* @param {stream.ReadableStream} stdin given as novacom process stdin
|
|
882
|
+
* @param {stream.WritableStream} stdout given as novacom process stdout
|
|
883
|
+
* @param {stream.WritableStream} stderr given as novacom process stderr
|
|
884
|
+
* @param {Function} next commonJS callback invoked upon completion or failure
|
|
885
|
+
*/
|
|
886
|
+
runWithOption: function(cmd, opt, stdin, stdout, stderr, next) {
|
|
887
|
+
this.run_ssh(cmd, opt, stdin, stdout, stderr, next);
|
|
888
|
+
},
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* Run a command on the device
|
|
892
|
+
* @param {String} cmd the device command to run
|
|
893
|
+
* @param {Object} opt given as exec option
|
|
894
|
+
* @param {stream.ReadableStream} stdin given as novacom process stdin
|
|
895
|
+
* @param {stream.WritableStream} stdout given as novacom process stdout
|
|
896
|
+
* @param {stream.WritableStream} stderr given as novacom process stderr
|
|
897
|
+
* @param {Function} next commonJS callback invoked upon completion or failure
|
|
898
|
+
*/
|
|
899
|
+
run_ssh: function(cmd, opt, stdin, stdout, stderr, next) {
|
|
900
|
+
log.info("novacom#Session()#run()", "cmd:" + cmd + ", opt:" + JSON.stringify(opt));
|
|
901
|
+
if (typeof next !== 'function') {
|
|
902
|
+
throw errHndl.getErrMsg("MISSING_CALLBACK", "next", util.inspect(next));
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// plumb output
|
|
906
|
+
const write = {},
|
|
907
|
+
obj = {};
|
|
908
|
+
let orgErrMsg;
|
|
909
|
+
|
|
910
|
+
if (!stdout) {
|
|
911
|
+
log.silly("novacom#Session()#run()", "stdout: none");
|
|
912
|
+
write.stdout = function() {};
|
|
913
|
+
} else if (stdout instanceof stream.Stream) {
|
|
914
|
+
log.silly("novacom#Session()#run()", "stdout: stream");
|
|
915
|
+
write.stdout = stdout.write;
|
|
916
|
+
obj.stdout = stdout;
|
|
917
|
+
} else if (stdout instanceof Function) {
|
|
918
|
+
log.silly("novacom#Session()#run()", "stdout: function");
|
|
919
|
+
write.stdout = stdout;
|
|
920
|
+
} else {
|
|
921
|
+
return setImmediate(next, errHndl.getErrMsg("INVALID_VALUE", "stdout", util.inspect(stdout)));
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
if (!stderr) {
|
|
925
|
+
log.silly("novacom#Session()#run()", "stderr: none");
|
|
926
|
+
write.stderr = function() {};
|
|
927
|
+
} else if (stderr instanceof stream.Stream) {
|
|
928
|
+
log.silly("novacom#Session()#run()", "stderr: stream");
|
|
929
|
+
write.stderr = stderr.write;
|
|
930
|
+
obj.stderr = stderr;
|
|
931
|
+
} else if (stderr instanceof Function) {
|
|
932
|
+
log.silly("novacom#Session()#run()", "stderr: function");
|
|
933
|
+
write.stderr = stderr;
|
|
934
|
+
} else {
|
|
935
|
+
return setImmediate(next, errHndl.getErrMsg("INVALID_VALUE", "stderr", util.inspect(stderr)));
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// execute command
|
|
939
|
+
this.ssh.exec(cmd, opt, (function(err, chStream) {
|
|
940
|
+
log.silly("novacom#Session()#run()", "exec cmd:" + cmd + ", opt:" + JSON.stringify(opt) + ", err:" + err);
|
|
941
|
+
if (err) {
|
|
942
|
+
return setImmediate(next, err);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// manual pipe(): handle & divert data chunks
|
|
946
|
+
chStream.on('data', function(data, extended) {
|
|
947
|
+
extended = extended || 'stdout';
|
|
948
|
+
log.verbose("novacom#Session()#run()", "on data (" + extended + ")");
|
|
949
|
+
write[extended].bind(obj[extended])(data);
|
|
950
|
+
}).stderr.on('data', function(data) {
|
|
951
|
+
log.verbose("novacom#Session()#run()", "on data (stderr)");
|
|
952
|
+
orgErrMsg = data.toString();
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
// manual pipe(): handle EOF
|
|
956
|
+
chStream.on('end', function() {
|
|
957
|
+
log.silly("novacom#Session()#run()", "event EOF from (cmd:" + cmd + ")");
|
|
958
|
+
if ((stdout !== process.stdout) && (stdout instanceof stream.Stream)) {
|
|
959
|
+
stdout.end();
|
|
960
|
+
}
|
|
961
|
+
if ((stderr !== process.stderr) && (stderr instanceof stream.Stream)) {
|
|
962
|
+
stderr.end();
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
// Exit when the remote process has terminated
|
|
967
|
+
chStream.on('exit', function(code, signal) {
|
|
968
|
+
log.silly("novacom#Session()#run()", "event exit code:" + code + ', signal:' + signal + " (cmd:" + cmd + ")");
|
|
969
|
+
err = makeExecError(cmd, code, signal, orgErrMsg);
|
|
970
|
+
setImmediate(next, err);
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
// Exit if the 'exit' event was not
|
|
974
|
+
// received (dropbear <= 0.51)
|
|
975
|
+
chStream.on('close', function() {
|
|
976
|
+
log.silly("novacom#Session()#run()", "event close (cmd:" + cmd + ")");
|
|
977
|
+
if (err === undefined) {
|
|
978
|
+
setImmediate(next);
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
if (stdin) {
|
|
983
|
+
stdin.pipe(chStream);
|
|
984
|
+
log.verbose("novacom#Session()#run()", "resuming input");
|
|
985
|
+
}
|
|
986
|
+
}));
|
|
987
|
+
},
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* Run a command on the device considerless return stdout
|
|
991
|
+
* @param {String} cmd the device command to run
|
|
992
|
+
* @param {Function} callback invoked upon exit event
|
|
993
|
+
* @param {Function} next commonJS callback invoked upon completion or failure
|
|
994
|
+
*/
|
|
995
|
+
runNoHangup: function(cmd, cbData, cbExit, next) {
|
|
996
|
+
this.runNoHangup_ssh(cmd, cbData, cbExit, next);
|
|
997
|
+
},
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* Run a command on the device considerless return stdout
|
|
1001
|
+
* @param {String} cmd the device command to run
|
|
1002
|
+
* @param {Function} callback invoked upon exit event
|
|
1003
|
+
* @param {Function} next commonJS callback invoked upon completion or failure
|
|
1004
|
+
*/
|
|
1005
|
+
runNoHangup_ssh: function(cmd, cbData, cbExit, next) {
|
|
1006
|
+
log.info("novacom#Session()#runNoHangup()", "cmd=" + cmd);
|
|
1007
|
+
if (arguments.length < 2) {
|
|
1008
|
+
throw errHndl.getErrMsg("MISSING_CALLBACK", "next");
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
for (const arg in arguments) {
|
|
1012
|
+
if (typeof arguments[arg] === 'undefined') {
|
|
1013
|
+
delete arguments[arg];
|
|
1014
|
+
arguments.length--;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
switch (arguments.length) {
|
|
1019
|
+
case 2:
|
|
1020
|
+
next = cbData;
|
|
1021
|
+
cbData = cbExit = null;
|
|
1022
|
+
break;
|
|
1023
|
+
case 3:
|
|
1024
|
+
next = cbExit;
|
|
1025
|
+
cbExit = cbData;
|
|
1026
|
+
cbData = null;
|
|
1027
|
+
break;
|
|
1028
|
+
default:
|
|
1029
|
+
break;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
if (typeof next !== 'function') {
|
|
1033
|
+
throw errHndl.getErrMsg("MISSING_CALLBACK", "next", util.inspect(next));
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// execute command
|
|
1037
|
+
this.ssh.exec(cmd, (function(err, _stream) {
|
|
1038
|
+
log.verbose("novacom#Session()#run()", "exec cmd:" + cmd + ", err:" + err);
|
|
1039
|
+
if (err) {
|
|
1040
|
+
return setImmediate(next, err);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
_stream.on('data', function(data) {
|
|
1044
|
+
const str = (Buffer.isBuffer(data)) ? data.toString() : data;
|
|
1045
|
+
log.verbose("novacom#Session()#runNoHangup()#onData", str);
|
|
1046
|
+
if (cbData) cbData(data);
|
|
1047
|
+
}).stderr.on('data', function(data) {
|
|
1048
|
+
const str = (Buffer.isBuffer(data)) ? data.toString() : data;
|
|
1049
|
+
log.verbose("novacom#Session()#runNoHangup()#onData#stderr#", str);
|
|
1050
|
+
if (cbData) cbData(data);
|
|
1051
|
+
});
|
|
1052
|
+
|
|
1053
|
+
// Exit when the remote process has terminated
|
|
1054
|
+
if (cbExit) {
|
|
1055
|
+
_stream.on('exit', function(code, signal) {
|
|
1056
|
+
log.verbose("novacom#Session()#runNoHangup()", "event exit code=" + code + ', signal=' + signal + " (cmd: " + cmd + ")");
|
|
1057
|
+
err = makeExecError(cmd, code, signal);
|
|
1058
|
+
cbExit(err);
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
setImmediate(next);
|
|
1062
|
+
}));
|
|
1063
|
+
},
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* Forward the given device port on the host.
|
|
1067
|
+
* As any other public method, this one can be called
|
|
1068
|
+
* only once the ssh session has emitted the 'ready'
|
|
1069
|
+
* event, so as part of the Session()#next callback.
|
|
1070
|
+
* @public
|
|
1071
|
+
* @param {Function} next commonJS callback invoked upon completion or failure
|
|
1072
|
+
*/
|
|
1073
|
+
forward: function(devicePort, localPort, forwardName, next) {
|
|
1074
|
+
log.info("novacom#Session()#forward()", "devicePort:", devicePort, ", localPort:", localPort);
|
|
1075
|
+
const session = this;
|
|
1076
|
+
let forwardInUse = false,
|
|
1077
|
+
registerName = null;
|
|
1078
|
+
|
|
1079
|
+
if (typeof forwardName === 'function') {
|
|
1080
|
+
next = forwardName;
|
|
1081
|
+
} else if (forwardName) {
|
|
1082
|
+
registerName = forwardName;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
if (localPort !== 0) {
|
|
1086
|
+
if (session.forwardedPorts.indexOf({
|
|
1087
|
+
name: registerName,
|
|
1088
|
+
local: localPort,
|
|
1089
|
+
device: devicePort
|
|
1090
|
+
}) > 0) {
|
|
1091
|
+
forwardInUse = true;
|
|
1092
|
+
}
|
|
1093
|
+
} else if (session.forwardedPorts.filter(function(forwardItem) {
|
|
1094
|
+
return (forwardItem.device === devicePort && forwardItem.name === registerName);
|
|
1095
|
+
}).length > 0) {
|
|
1096
|
+
forwardInUse = true;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
if (forwardInUse) {
|
|
1100
|
+
return setImmediate(next);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
const localServer = net.createServer(function(inCnx) {
|
|
1104
|
+
log.info("novacom#Session()#forward()", "new client, localPort:", localPort);
|
|
1105
|
+
log.verbose("novacom#Session()#forward()", "new client, from: " + inCnx.remoteAddress + ':' + inCnx.remotePort);
|
|
1106
|
+
|
|
1107
|
+
inCnx.on('error', function(err) {
|
|
1108
|
+
log.verbose("novacom#Session()#forward()", "inCnx::error, err::" + err);
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
// Open the outbound connection on the device to match the incoming client.
|
|
1112
|
+
session.ssh.forwardOut("127.0.0.1" /* srcAddr*/ , inCnx.remotePort /* srcPort*/ , "127.0.0.1" /* dstAddr*/ , devicePort /* dstPort*/ , function(err, outCnx) {
|
|
1113
|
+
if (err) {
|
|
1114
|
+
console.log("novacom#Session()#forward()", "failed forwarding client localPort:",
|
|
1115
|
+
localPort, "(inCnx.remotePort:", inCnx.remotePort, ")=> devicePort:", devicePort);
|
|
1116
|
+
log.warn("novacom#Session()#forward()", "failed forwarding client localPort:",
|
|
1117
|
+
localPort, "=> devicePort:", devicePort);
|
|
1118
|
+
inCnx.destroy();
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
log.info("novacom#Session()#forward()", "connected, devicePort:", devicePort);
|
|
1123
|
+
inCnx.on('data', function(data) {
|
|
1124
|
+
if (outCnx.writable && outCnx.writable === true) {
|
|
1125
|
+
if (outCnx.write(data) === false) {
|
|
1126
|
+
inCnx.pause();
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
inCnx.on('close', function(had_err) {
|
|
1132
|
+
log.verbose("novacom#Session()#forward()", "inCnx::close, had_err:", had_err);
|
|
1133
|
+
outCnx.destroy();
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
outCnx.on('drain', function() {
|
|
1137
|
+
inCnx.resume();
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
outCnx.on('data', function(data) {
|
|
1141
|
+
inCnx.write(data);
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
outCnx.on('close', function(had_err) {
|
|
1145
|
+
log.verbose("novacom#Session()#forward()", "outCnx::close, had_err:", had_err);
|
|
1146
|
+
});
|
|
1147
|
+
});
|
|
1148
|
+
});
|
|
1149
|
+
|
|
1150
|
+
session.ssh.on('close', function() {
|
|
1151
|
+
localServer.close();
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
try {
|
|
1155
|
+
localServer.listen(localPort, null, (function() {
|
|
1156
|
+
const localServerPort = localServer.address().port;
|
|
1157
|
+
session.forwardedPorts.push({
|
|
1158
|
+
name: registerName,
|
|
1159
|
+
local: localServerPort,
|
|
1160
|
+
device: devicePort
|
|
1161
|
+
});
|
|
1162
|
+
setImmediate(next);
|
|
1163
|
+
}));
|
|
1164
|
+
} catch (err) {
|
|
1165
|
+
setImmediate(next, err);
|
|
1166
|
+
}
|
|
1167
|
+
},
|
|
1168
|
+
|
|
1169
|
+
getLocalPortByName: function(queryName) {
|
|
1170
|
+
const session = this;
|
|
1171
|
+
let found = null;
|
|
1172
|
+
session.forwardedPorts.forEach(function(portItem) {
|
|
1173
|
+
if (portItem.name === queryName) {
|
|
1174
|
+
found = portItem.local;
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
return found;
|
|
1179
|
+
},
|
|
1180
|
+
|
|
1181
|
+
runHostedAppServer: function(url, next) {
|
|
1182
|
+
server.runServer(url, 0, function(err, serverInfo) {
|
|
1183
|
+
if (serverInfo && serverInfo.port) {
|
|
1184
|
+
this.setHostedAppServerPort(serverInfo.port);
|
|
1185
|
+
}
|
|
1186
|
+
next(err);
|
|
1187
|
+
}.bind(this));
|
|
1188
|
+
},
|
|
1189
|
+
|
|
1190
|
+
setHostedAppServerPort: function(port) {
|
|
1191
|
+
this.hostedAppServerPort = port;
|
|
1192
|
+
},
|
|
1193
|
+
|
|
1194
|
+
getHostedAppServerPort: function() {
|
|
1195
|
+
return this.hostedAppServerPort;
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
|
|
1199
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
1200
|
+
module.exports = novacom;
|
|
1201
|
+
}
|
|
1202
|
+
}());
|