• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

suculent / thinx-device-api / #252646148

07 May 2025 09:25PM UTC coverage: 59.0% (-13.0%) from 72.046%
#252646148

push

suculent
overridden vulnerable cookies and deprecated superagent

1504 of 3464 branches covered (43.42%)

Branch coverage included in aggregate %.

6700 of 10441 relevant lines covered (64.17%)

5.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

32.34
/lib/thinx/deployment.js
1
/*
2
 * This THiNX Device Management API module is responsible for managing deployments for each device.
3
 */
4

5
var fs = require("fs-extra");
1✔
6
var util = require("util");
1✔
7
var semver = require("semver");
1✔
8
var mkdirp = require("mkdirp");
1✔
9
var typeOf = require("typeof");
1✔
10
var finder = require("fs-finder");
1✔
11

12
var Globals = require("./globals.js");
1✔
13
var app_config = Globals.app_config();
1✔
14

15
var Sanitka = require("./sanitka"); var sanitka = new Sanitka();
1✔
16
const Plugins = require("./plugins");
1✔
17
const Util = require("./util.js");
1✔
18
const Filez = require("./files.js");
1✔
19

20
var debug_deployment = app_config.debug.deployment || false;
1!
21
var debug_device = app_config.debug.device || false;
1✔
22

23
module.exports = class Deployment {
1✔
24

25
        deployPathForOwner(owner) {
26
                let s_owner = sanitka.owner(owner);
15✔
27
                if (s_owner === false) {
15!
28
                        console.log("🚫  [critical] cannot provide deployPath without owner");
×
29
                }
30
                return app_config.data_root + app_config.deploy_root + "/" + s_owner;
15✔
31
        }
32

33
        latestFirmwareEnvelope(owner, xudid) {
34
                var udid = sanitka.udid(xudid);
12✔
35
                var path = Filez.deployPathForDevice(owner, udid);
12✔
36

37
                if (!Util.isDefined(path) || !Util.isDefined(udid)) {
12!
38
                        console.log("☣️ [error] LFE path undefined.");
×
39
                        return false;
×
40
                }
41

42
                // check if any build exists for this device
43
                if (!fs.existsSync(path)) { // lgtm [js/path-injection]
12!
44
                        console.log("☣️ [error] LFE path does not exist.");
12✔
45
                        return false;
12✔
46
                }
47

48
                var envpath = path + "/build.json";
×
49
                if (fs.existsSync(envpath)) { // lgtm [js/path-injection]
×
50
                        return JSON.parse(fs.readFileSync(envpath)); // lgtm [js/path-injection]
×
51
                }
52

53
                console.log("ℹ️ [info] Device", udid, "has no firmware available.");
×
54
                return false;
×
55
        }
56

57
        latestFile(files) {
58
                var latest_date = 0;
×
59
                var latest_firmware = files[0];
×
60
                for (var index in files) {
×
61
                        var filename = files[index];
×
62
                        var stats = fs.statSync(filename);
×
63
                        var mtime = new Date(util.inspect(stats.mtime));
×
64
                        if (mtime > latest_date) {
×
65
                                latest_date = mtime;
×
66
                                latest_firmware = filename;
×
67
                        }
68
                }
69
                return latest_firmware;
×
70
        }
71

72
        fixAvailableVersion(version) {
73

74
                if (!semver.valid(version)) {
×
75

76
                        let version_string;
77
                        let available_version = version.split(":");
×
78
                        if (available_version.length > 1) {
×
79
                                version_string = available_version[1];
×
80
                        }
81

82
                        let gobliiins = version_string.split(".");
×
83
                        var number_of_dots = gobliiins.length - 1;
×
84

85
                        var deployment_version;
86

87
                        switch (number_of_dots) {
×
88

89
                                case 0:
90
                                        deployment_version = [
×
91
                                                0,
92
                                                0,
93
                                                available_version
94
                                        ];
95
                                        break;
×
96

97
                                case 1:
98
                                        deployment_version = [
×
99
                                                0,
100
                                                gobliiins[0],
101
                                                gobliiins[1]
102
                                        ];
103
                                        break;
×
104

105
                                case 3:
106
                                        deployment_version = [
×
107
                                                gobliiins[0],
108
                                                gobliiins[1],
109
                                                parseInt(gobliiins[2]) + parseInt(gobliiins[3]),
110
                                        ];
111
                                        break;
×
112

113
                                case 4:
114
                                        deployment_version = [
×
115
                                                gobliiins[0],
116
                                                gobliiins[1],
117
                                                parseInt(gobliiins[2]) + parseInt(gobliiins[3]) + parseInt(gobliiins[4]),
118
                                        ];
119
                                        break;
×
120

121
                                default:
122
                                        deployment_version = [
×
123
                                                gobliiins[0],
124
                                                gobliiins[1],
125
                                                gobliiins[2],
126
                                        ];
127
                                        break;
×
128
                        }
129

130
                        version = deployment_version.join(".");
×
131
                        if (debug_deployment) console.log("🔨 [debug] [hasUpdateAvailable] Deployed version collapsed: " + version);
×
132
                }
133
                return version;
×
134
        }
135

136
        getAvailableVersion(owner, xudid) {
137
                // In case of attempt to install completely different firmware, bypasses version check...
138
                var udid = sanitka.udid(xudid);
9✔
139
                var envelope = this.latestFirmwareEnvelope(owner, udid);
9✔
140
                var available_version;
141

142
                // In case of same firmware flavour, check the version and upgrade only if new is available.
143
                if ((typeof (envelope) !== "undefined") &&
9!
144
                        (typeof (envelope.version) !== "undefined") &&
145
                        (envelope.version !== null)) {
146
                        console.log("ℹ️ [info] Latest Available Firmware for", xudid, "is", envelope.version);
×
147
                        available_version = this.fixAvailableVersion(envelope.version);
×
148
                }
149
                return available_version;
9✔
150
        }
151

152
        getAvailableEnvironmentHash(owner, xudid) {
153
                // In case of attempt to install completely different firmware, bypasses version check...
154
                var udid = sanitka.udid(xudid);
×
155
                var envelope = this.latestFirmwareEnvelope(owner, udid);
×
156
                var available_hash = null; // should be null for no env...
×
157

158
                // In case of same firmware flavour, check the version and upgrade only if new is available.
159
                if ((typeof (envelope) !== "undefined") &&
×
160
                        (typeof (envelope.env_hash) !== "undefined") &&
161
                        (envelope.env_hash !== null)) {
162
                        console.log("ℹ️ [info] Available (latest) firmware env_hash: " + envelope.env_hash);
×
163
                        available_hash = envelope.env_hash;
×
164
                }
165

166
                if ((typeof (envelope) !== "undefined")) {
×
167
                        console.log("ℹ️ [info] getAvailableEnvironmentHash envelope", envelope);
×
168
                }
169

170
                return available_hash;
×
171
        }
172

173
        parseDeviceVersion(deviceVersion) {
174
                if (typeof (deviceVersion) === "undefined" || deviceVersion === null) {
9!
175
                        deviceVersion = "0.0.1";
×
176
                }
177
                var pattern = /[0-9.]/;
9✔
178
                var pattern_valid = new RegExp(pattern).test(deviceVersion);
9✔
179

180
                if (!pattern_valid) {
9!
181
                        console.log("⚠️ [warning] [parseDeviceVersion] Device version invalid: " + deviceVersion);
×
182
                }
183

184
                if (!semver.valid(deviceVersion)) {
9!
185
                        var device_version = [0, 0, 0];
×
186
                        var dev_version_array = deviceVersion.split(".");
×
187
                        for (var index1 in dev_version_array) {
×
188
                                device_version[index1] = dev_version_array[index1];
×
189
                        }
190
                        console.log("⚠️ [warning] [parseDeviceVersion] Invalid semantic versioning in: " + deviceVersion);
×
191
                        deviceVersion = device_version.join(".");
×
192
                        console.log("⚠️ [warning] [parseDeviceVersion] Semantic versioning changed to: " + deviceVersion);
×
193
                }
194
                return deviceVersion;
9✔
195
        }
196

197
        supportedPlatform(platform) {
198

199
                if (!Util.isDefined(platform)) {
2!
200
                        //console.log("[supportedPlatform] error: platform not defined: ", platform);
201
                        return false;
2✔
202
                }
203

204
                switch (platform) {
×
205

206
                        case "mongooseos":
207
                        case "mongoose":
208
                        case "python":
209
                        case "micropython":
210
                        case "nodemcu":
211
                        case "pine64":
212
                        case "platformio":
213
                        case "arduino":
214
                                return true;
×
215

216
                        case "nodejs":
217
                        case "sigfox":
218
                        default:
219
                                console.log("[supportedPlatform] returning unsupported platform:", platform);
×
220
                                return false;
×
221
                }
222
        }
223

224
        platformSupportsUpdate(device) {
225

226
                if (!Util.isDefined(device)) {
2!
227
                        console.log("☣️ [error] [deployment] Cannot platformSupportsUpdate without device!");
×
228
                        return false;
×
229
                }
230

231
                let platform;
232

233
                // Extract platform part before ':' if any
234
                if (Util.isDefined(device.platform)) {
2!
235
                        platform = device.platform; // fetch as is
×
236
                        if (platform.indexOf(":") !== -1) {
×
237
                                var platform_array = platform.split(":");
×
238
                                platform = platform_array[0]; // strip if contains colon
×
239
                        }
240
                }
241

242
                return this.supportedPlatform(platform);
2✔
243
        }
244

245
        initWithOwner(v_owner) {
246
                var user_path = this.deployPathForOwner(v_owner); // validates
15✔
247
                if (!Util.isDefined(user_path)) {
15!
248
                        console.log("☣️ [error] deployment.js: No deploy path with owner " + v_owner);
×
249
                        return;
×
250
                }
251
                mkdirp(user_path); // lgtm [js/path-injection]
15✔
252
        }
253

254
        initWithDevice(device) {
255
                if (!Util.isDefined(device)) {
2!
256
                        console.log("☣️ [error] [deployment] Cannot init deployment without device!");
×
257
                        return;
×
258
                }
259
                this.initWithOwner(device.owner, null, (success, response) => {
2✔
260
                        console.log("ℹ️ [info] initWithDevice success: " + success + " response " +
×
261
                                response);
262
                });
263
        }
264

265
        deploymentPathForDeviceOwner(owner, udid) {
266
                return this.deployPathForOwner(owner) + "/" + udid;
×
267
        }
268

269
        supportedExtensions(callback) {
270

271
                let manager = new Plugins(this);
×
272

273
                (async () => manager.loadFromConfig('./lib/thinx/plugins/plugins.json'))()
×
274
                        .then(async () => manager.extensions())
×
275
                        .then(extensions => {
276
                                callback(extensions);
×
277
                        }).catch(e => {
278
                                console.log(e);
×
279
                                callback([]);
×
280
                        });
281
        }
282

283
        latestFirmwarePath(in_owner, udid, callback) {
284
                if (!Util.isDefined(in_owner) || !Util.isDefined(udid) || typeOf(in_owner) == "object") {
3!
285
                        console.log(`☣️ [error] invalid LFP owner ${in_owner} with udid: ${udid}`);
×
286
                        callback(false);
×
287
                        return;
×
288
                }
289
                let owner = sanitka.owner(in_owner);
3✔
290
                var latest_firmware = false;
3✔
291
                var fpath = Filez.deployPathForDevice(owner, udid) + "/build.json";
3✔
292
                if (!fs.existsSync(fpath)) { // lgtm [js/path-injection]
3!
293
                        console.log(`☣️ [error] Envelope ${fpath} not found.`);
3✔
294
                        callback(false);
3✔
295
                        return;
3✔
296
                }
297
                var dpath = Filez.deployPathForDevice(owner, udid);
×
298
                this.supportedExtensions((extensions) => {
×
299
                        console.log("ℹ️ [info] supported extensions", extensions);
×
300
                        for (var extension in extensions) {
×
301
                                var files = finder.in(dpath).findFiles(extension);
×
302
                                if (files.length === 0) continue;
×
303
                                latest_firmware = files[0];
×
304
                                let latest = this.latestFile(files);
×
305
                                if (latest !== "undefined") {
×
306
                                        latest_firmware = latest;
×
307
                                }
308
                        }
309
                        callback(latest_firmware);
×
310
                });
311
        }
312

313
        latestFirmwareArtifact(owner, udid) {
314
                var dpath = Filez.deployPathForDevice(owner, udid);
×
315
                var files = finder.in(dpath).findFiles("*.zip");
×
316
                return this.latestFile(files);
×
317
        }
318

319
        artifact(owner, udid, build_id) {
320
                var fpath = `${Filez.deployPathForDevice(owner, udid)}/${build_id}/${build_id}.zip`;
×
321
                let data = null;
×
322
                if (fs.existsSync(fpath)) {
×
323
                        try {
×
324
                                data = fs.readFileSync(fpath);
×
325
                        } catch (e) {
326
                                console.log("☣️ [error] caught exception", e, "while reading artifact at", fpath);
×
327
                        }
328
                } else {
329
                        console.log("[warning] artifact not found at", fpath);
×
330
                }
331
                return data; // lgtm [js/path-injection]
×
332
        }
333

334
        validateHasUpdateAvailable(device) {
335

336
                let has = true;
1✔
337

338
                if (!Util.isDefined(device)) {
1!
339
                        console.log("☣️ [error] [validateHasUpdateAvailable] Cannot init deployment without device!");
×
340
                        has = false;
×
341
                }
342

343
                if (!Util.isDefined(device.owner)) {
1!
344
                        console.log("☣️ [error] [validateHasUpdateAvailable] Device has no owner.");
×
345
                        console.log({ device });
×
346
                        has = false;
×
347
                }
348

349
                if (!Util.isDefined(device.udid)) {
1!
350
                        console.log("☣️ [error] [validateHasUpdateAvailable] Device has no udid.");
×
351
                        console.log({ device });
×
352
                        has = false;
×
353
                }
354

355
                if (!this.platformSupportsUpdate(device)) {
1!
356
                        console.log("☣️ [error] [validateHasUpdateAvailable] Device does not support updates.");
1✔
357
                        has = false;
1✔
358
                }
359

360
                return has;
1✔
361
        }
362

363
        hasUpdateAvailable(device) {
364

365
                const owner = device.owner;
9✔
366

367
                const deviceVersion = this.parseDeviceVersion(device.version);
9✔
368

369
                var available_version = this.getAvailableVersion(owner, device.udid);
9✔
370
                if (typeof (available_version) === "undefined") {
9!
371
                        if (debug_device) console.log("ℹ️ [info] No firmware update available.");
9!
372
                        return false;
9✔
373
                }
374

375
                // Device version lower than available version
376
                var outdated = semver.lt(deviceVersion, available_version);
×
377
                if (outdated) {
×
378
                        console.log("ℹ️ [info] Device has:", deviceVersion, ", update available to:", available_version);
×
379
                        return true;
×
380
                }
381

382
                // Device version same, but environment may change
383
                if (semver.eq(deviceVersion, available_version)) {
×
384
                        // versions equal, update may happen
385
                        if (debug_device) console.log("ℹ️ [info] Device version is up-to-date.");
×
386

387
                        const deviceHash = device.env_hash;
×
388
                        if (typeof (deviceHash) !== "undefined" && deviceHash !== null) {
×
389
                                if (deviceHash.indexOf("cafebabe") === 0) {
×
390
                                        console.log("⚠️ [warning] Device has default environment, will not perform update (dev version).");
×
391
                                } else {
392
                                        if (deviceHash.indexOf(this.getAvailableEnvironmentHash(owner, device.udid)) == -1) {
×
393
                                                console.log("ℹ️ [info] Device version is same but environment changed -> should provide update.");
×
394
                                                outdated = true;
×
395
                                        }
396
                                }
397
                        }
398
                } else {
399
                        console.log("ℹ️ [info] Device version is newer than available.");
×
400
                }
401

402
                return outdated;
×
403
        }
404
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc