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

telefonicaid / iotagent-json / 15191954336

22 May 2025 04:31PM UTC coverage: 79.362% (-0.5%) from 79.881%
15191954336

push

github

web-flow
Merge pull request #800 from telefonicaid/task/set_default_notification_handler

allow receive command notifications from CB

525 of 750 branches covered (70.0%)

Branch coverage included in aggregate %.

28 of 30 new or added lines in 2 files covered. (93.33%)

13 existing lines in 3 files now uncovered.

1117 of 1319 relevant lines covered (84.69%)

135.95 hits per line

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

75.05
/lib/bindings/HTTPBinding.js
1
/*
2
 * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
3
 *
4
 * This file is part of iotagent-json
5
 *
6
 * iotagent-json is free software: you can redistribute it and/or
7
 * modify it under the terms of the GNU Affero General Public License as
8
 * published by the Free Software Foundation, either version 3 of the License,
9
 * or (at your option) any later version.
10
 *
11
 * iotagent-json is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
 * See the GNU Affero General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Affero General Public
17
 * License along with iotagent-json.
18
 * If not, seehttp://www.gnu.org/licenses/.
19
 *
20
 * For those usages not covered by the GNU Affero General Public License
21
 * please contact with::[contacto@tid.es]
22
 */
23

24
/* eslint-disable no-unused-vars */
25

26
const fs = require('fs');
1✔
27
const iotAgentLib = require('iotagent-node-lib');
1✔
28
const regenerateTransid = iotAgentLib.regenerateTransid;
1✔
29
const finishSouthBoundTransaction = iotAgentLib.finishSouthBoundTransaction;
1✔
30
const fillService = iotAgentLib.fillService;
1✔
31
const intoTrans = iotAgentLib.intoTrans;
1✔
32
const _ = require('underscore');
1✔
33
const commandHandler = require('../commandHandler');
1✔
34
const async = require('async');
1✔
35
const apply = async.apply;
1✔
36
const request = iotAgentLib.request;
1✔
37
const errors = require('../errors');
1✔
38
const express = require('express');
1✔
39
const iotaUtils = require('../iotaUtils');
1✔
40
const http = require('http');
1✔
41
const https = require('https');
1✔
42
const commonBindings = require('../commonBindings');
1✔
43
const bodyParser = require('body-parser');
1✔
44
require('body-parser-xml')(bodyParser);
1✔
45
const constants = require('../constants');
1✔
46
let context = {
1✔
47
    op: 'IOTAJSON.HTTP.Binding'
48
};
49

50
const config = require('../configService');
1✔
51
let httpBindingServer;
52
const transport = 'HTTP';
1✔
53

54
const { promisify } = require('util');
1✔
55
const json = promisify(bodyParser.json({ strict: false, limit: config.getConfig().iota.expressLimit })); // accept anything JSON.parse accepts.
1✔
56
const text = promisify(bodyParser.text({ limit: config.getConfig().iota.expressLimit }));
1✔
57
const raw = promisify(bodyParser.raw({ limit: config.getConfig().iota.expressLimit }));
1✔
58
const xml2js = require('xml2js');
1✔
59
const xmlStripPrefix = xml2js.processors.stripPrefix;
1✔
60
const xml = promisify(
1✔
61
    bodyParser.xml({
62
        xmlParseOptions: {
63
            // XML namespaces might change from one request to the next.
64
            // It is useful to remove them from the document,
65
            // to be able to refer to tags later in JEXL transformations.
66
            // See https://github.com/Leonidas-from-XIV/node-xml2js/issues/87
67
            tagNameProcessors: [xmlStripPrefix],
68
            attrNameProcessors: [xmlStripPrefix]
69
        }
70
    })
71
);
72

73
function parserBody() {
74
    // generic bodyParser
75
    return function (req, res, next) {
262✔
76
        if (req.is('text/plain')) {
8✔
77
            text(req, res).then(() => next(), next);
2✔
78
        } else if (req.is('application/octet-stream')) {
6✔
79
            raw(req, res).then(() => next(), next);
2✔
80
        } else if (req.is('application/soap+xml')) {
4✔
81
            xml(req, res).then(() => next(), next);
2✔
82
        } else {
83
            // req.is('json')
84
            json(req, res).then(() => next(), next);
2✔
85
        }
86
    };
87
}
88

89
function checkMandatoryParams(queryPayload) {
90
    return function (req, res, next) {
1,310✔
91
        const notFoundParams = [];
156✔
92
        let error;
93

94
        req.apiKey = req.query.k;
156✔
95
        req.deviceId = req.query.i;
156✔
96
        req.attr = req.params ? req.params.attrValue : undefined;
156!
97

98
        if (!req.apiKey) {
156!
99
            notFoundParams.push('API Key');
×
100
        }
101

102
        if (!req.deviceId) {
156!
103
            notFoundParams.push('Device Id');
×
104
        }
105

106
        // Check if retrievingParam
107
        if (queryPayload && !req.query.d && req.query.getCmd !== '1') {
156!
108
            notFoundParams.push('Payload');
×
109
        }
110
        if (
156!
111
            req.method === 'POST' &&
323✔
112
            !req.is('json') &&
113
            !req.is('text/plain') &&
114
            !req.is('application/octet-stream') &&
115
            !req.is('application/soap+xml')
116
        ) {
117
            error = new errors.UnsupportedType(
×
118
                'application/json, text/plain, application/octet-stream, application/soap+xml'
119
            );
120
        }
121

122
        if (notFoundParams.length !== 0) {
156!
123
            next(new errors.MandatoryParamsNotFound(notFoundParams));
×
124
        } else {
125
            next(error);
156✔
126
        }
127
    };
128
}
129

130
function parseData(req, res, next) {
131
    let data;
132
    let error;
133
    let payload;
134

135
    if (req.body) {
13✔
136
        config.getLogger().debug(context, 'Using body %s', req.body);
12✔
137
        data = req.body;
12✔
138
        regenerateTransid(data);
12✔
139
    } else {
140
        payload = req.query.d;
1✔
141
        regenerateTransid(payload);
1✔
142
        config.getLogger().debug(context, 'Parsing payload %s', payload);
1✔
143

144
        try {
1✔
145
            if (payload) {
1!
146
                data = JSON.parse(payload);
×
147
            }
148
        } catch (e) {
149
            error = e;
×
150
        }
151
    }
152

153
    if (error) {
13!
154
        next(error);
×
155
    } else {
156
        req.jsonPayload = data;
13✔
157
        if (req.body !== undefined) {
13✔
158
            try {
12✔
159
                // This is just for log data
160
                data = data.toString('hex');
12✔
161
            } catch (e) {
162
                // no error should be reported
163
            }
164
        }
165
        config.getLogger().debug(context, 'Parsed data: %j', data);
13✔
166
        next();
13✔
167
    }
168
}
169

170
function parseDataMultipleMeasure(req, res, next) {
171
    let data;
172
    let error;
173
    let payload;
174
    let dataArray;
175
    dataArray = [];
143✔
176
    if (req.body) {
143!
177
        config.getLogger().debug(context, 'Using body %s', req.body);
143✔
178
        if (!Array.isArray(req.body)) {
143✔
179
            dataArray.push(req.body);
127✔
180
        } else {
181
            dataArray = req.body;
16✔
182
        }
183
        regenerateTransid(dataArray);
143✔
184
    } else {
185
        payload = req.query.d;
×
186
        regenerateTransid(payload);
×
187
        config.getLogger().debug(context, 'Parsing payload %s', payload);
×
188
        try {
×
189
            if (payload) {
×
190
                data = JSON.parse(payload);
×
191
                dataArray.push(data);
×
192
            }
193
        } catch (e) {
194
            error = e;
×
195
        }
196
    }
197
    if (error) {
143!
198
        next(error);
×
199
    } else {
200
        req.jsonPayload = dataArray;
143✔
201
        config.getLogger().debug(context, 'Parsed data array: %j', dataArray);
143✔
202
        next();
143✔
203
    }
204
}
205

206
function executeCommand(apiKey, group, device, cmdName, serializedPayload, extraHeaders, contentType, callback) {
207
    const options = {
12✔
208
        url: device.endpoint || (group && group.endpoint ? group.endpoint : undefined),
24!
209
        method: 'POST',
210
        body: serializedPayload,
211
        headers: {
212
            'fiware-service': device.service,
213
            'fiware-servicepath': device.subservice,
214
            'content-type': contentType
215
        }
216
    };
217
    if (options.url || extraHeaders) {
12!
218
        // endpoint and extraHeaders could be an expression
219
        const parser = iotAgentLib.dataPlugins.expressionTransformation;
12✔
220
        let attrList = iotAgentLib.dataPlugins.utils.getIdTypeServSubServiceFromDevice(device);
12✔
221
        attrList = device.staticAttributes ? attrList.concat(device.staticAttributes) : attrList.concat([]);
12!
222
        const ctxt = parser.extractContext(attrList, device);
12✔
223
        config.getLogger().debug(context, 'attrList %j for device %j', attrList, device);
12✔
224
        if (options.url) {
12!
225
            let endpointRes = null;
12✔
226
            try {
12✔
227
                endpointRes = parser.applyExpression(options.url, ctxt, device);
12✔
228
            } catch (e) {
229
                // no error should be reported
230
            }
231
            options.url = endpointRes ? endpointRes : options.url;
12✔
232
        }
233
        if (extraHeaders) {
12✔
234
            if (
4!
235
                typeof extraHeaders !== 'object' ||
16✔
236
                extraHeaders === null ||
237
                Array.isArray(extraHeaders) ||
238
                Object.getPrototypeOf(extraHeaders) !== Object.prototype
239
            ) {
240
                config
×
241
                    .getLogger()
242
                    .info(
243
                        context,
244
                        'extraHeaders %j in device %j command %j  not plain object',
245
                        extraHeaders,
246
                        device,
247
                        cmdName
248
                    );
249
            } else {
250
                for (const key in extraHeaders) {
4✔
251
                    if (Object.prototype.hasOwnProperty.call(extraHeaders, key)) {
4!
252
                        const headerValue = extraHeaders[key];
4✔
253
                        let headerRes = null;
4✔
254
                        try {
4✔
255
                            headerRes = parser.applyExpression(headerValue, ctxt, device);
4✔
256
                        } catch (e) {
257
                            // no error should be reported
258
                        }
259
                        options.headers[key] = headerRes;
4✔
260
                    }
261
                } // for
262
            }
263
        }
264
    }
265
    if (config.getConfig().http.timeout) {
12!
266
        options.timeout = config.getConfig().http.timeout;
×
267
    }
268
    config
12✔
269
        .getLogger()
270
        .debug(
271
            context,
272
            'Sending command %s with payload %s and with http options %j',
273
            cmdName,
274
            serializedPayload,
275
            options
276
        );
277
    request(options, function (error, response, body) {
12✔
278
        if (error || !response || (response.statusCode !== 200 && response.statusCode !== 201)) {
12!
279
            callback(new errors.HTTPCommandResponseError(response ? response.statusCode : 400, error));
×
280
        } else if (apiKey) {
12!
281
            config
12✔
282
                .getLogger()
283
                .info(
284
                    context,
285
                    'Cmd: %j was sent to the device %s with http options %j',
286
                    serializedPayload,
287
                    cmdName,
288
                    options
289
                );
290
            process.nextTick(commandHandler.updateCommand.bind(null, apiKey, device.id, device, body));
12✔
291
            callback();
12✔
292
        } else {
293
            config
×
294
                .getLogger()
295
                .info(
296
                    context,
297
                    'Cmd: %j was sent to the device %s with http options %j',
298
                    serializedPayload,
299
                    cmdName,
300
                    options
301
                );
302
            callback();
×
303
        }
304
    });
305
}
306

307
function addTimestamp(req, res, next) {
308
    const arr = req.jsonPayload;
153✔
309
    let timeStampData;
310
    for (const i in arr) {
153✔
311
        if (req.query.t && arr[i]) {
382✔
312
            timeStampData = arr[i];
4✔
313
            timeStampData[constants.TIMESTAMP_ATTRIBUTE] = req.query.t;
4✔
314
        }
315
    }
316
    next();
153✔
317
}
318

319
function handleIncomingMeasure(req, res, next) {
320
    let values;
321
    context = fillService(context, { service: 'n/a', subservice: 'n/a' });
153✔
322
    config
153✔
323
        .getLogger()
324
        .debug(context, 'Processing multiple HTTP measures for device %s with apiKey %s', req.deviceId, req.apiKey);
325

326
    function updateCommandHandler(error) {
327
        if (error) {
152!
328
            next(error);
×
329
            config.getLogger().error(
×
330
                context,
331
                /*jshint quotmark: double */
332
                "MEASURES-002: Couldn't send the updated values to the Context Broker due to an error: %j",
333
                /*jshint quotmark: single */
334
                error
335
            );
336
        } else {
337
            config
152✔
338
                .getLogger()
339
                .info(
340
                    context,
341
                    'Multiple measures for device %s with apiKey %s successfully updated',
342
                    req.deviceId,
343
                    req.apiKey
344
                );
345

346
            finishSouthBoundTransaction(next);
152✔
347
        }
348
    }
349

350
    function processHTTPWithDevice(device) {
351
        let payloadDataArr;
352
        let attributeArr;
353
        let attributeValues;
354
        attributeArr = [];
153✔
355
        payloadDataArr = [];
153✔
356

357
        if (req.attr && req.jsonPayload) {
153✔
358
            config.getLogger().debug(context, 'Parsing attr %s with value %s', req.attr, req.jsonPayload);
8✔
359
            try {
8✔
360
                req.jsonPayload = req.jsonPayload.toString('hex');
8✔
361
            } catch (e) {
362
                // no error should be reported
363
            }
364
            const theAttr = [{ name: req.attr, value: req.jsonPayload, type: 'None' }];
8✔
365
            attributeArr.push(theAttr);
8✔
366
        } else {
367
            if (!Array.isArray(req.jsonPayload)) {
145✔
368
                payloadDataArr.push(req.jsonPayload);
2✔
369
            } else {
370
                payloadDataArr = req.jsonPayload;
143✔
371
            }
372

373
            if (req.jsonPayload) {
145✔
374
                config.getLogger().debug(context, 'Parsing payloadDataArr %j for device %j', payloadDataArr, device);
144✔
375
                for (const i in payloadDataArr) {
144✔
376
                    values = commonBindings.extractAttributes(device, payloadDataArr[i], device.payloadType);
178✔
377
                    if (values && values[0] && values[0][0]) {
178✔
378
                        // Check multimeasure from a ngsiv2/ngsild entities array
379
                        attributeArr = attributeArr.concat(values);
2✔
380
                    } else {
381
                        attributeArr.push(values);
176✔
382
                    }
383
                }
384
            } else {
385
                attributeArr = [];
1✔
386
            }
387
        }
388
        if (attributeArr.length === 0) {
153✔
389
            finishSouthBoundTransaction(next);
1✔
390
        } else {
391
            config
152✔
392
                .getLogger()
393
                .debug(context, 'Processing measure device %s with attributeArr %j', device.name, attributeArr);
394
            if (req.isCommand) {
152✔
395
                const executions = [];
1✔
396
                for (const j in attributeArr) {
1✔
397
                    attributeValues = attributeArr[j];
1✔
398
                    for (const k in attributeValues) {
1✔
399
                        executions.push(
1✔
400
                            iotAgentLib.setCommandResult.bind(
401
                                null,
402
                                device.name,
403
                                config.getConfig().iota.defaultResource,
404
                                req.apiKey,
405
                                attributeValues[k].name,
406
                                attributeValues[k].value,
407
                                constants.COMMAND_STATUS_COMPLETED,
408
                                device
409
                            )
410
                        );
411
                    }
412
                }
413
                async.parallel(executions, updateCommandHandler);
1✔
414
            } else {
415
                iotAgentLib.update(device.name, device.type, '', attributeArr, device, updateCommandHandler);
151✔
416
            }
417
        }
418
    }
419

420
    function processDeviceMeasure(error, device) {
421
        if (error) {
153!
422
            next(error);
×
423
        } else {
424
            const localContext = _.clone(context);
153✔
425
            req.device = device;
153✔
426
            localContext.service = device.service;
153✔
427
            localContext.subservice = device.subservice;
153✔
428
            intoTrans(localContext, processHTTPWithDevice)(device);
153✔
429
        }
430
    }
431

432
    iotaUtils.retrieveDevice(req.deviceId, req.apiKey, processDeviceMeasure);
153✔
433
}
434

435
function isCommand(req, res, next) {
436
    if (
1!
437
        req.path ===
438
        (config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH) + constants.HTTP_COMMANDS_PATH
1!
439
    ) {
440
        req.isCommand = true;
1✔
441
    }
442

443
    next();
1✔
444
}
445

446
function sendConfigurationToDevice(apiKey, group, deviceId, results, callback) {
447
    function handleDeviceResponse(innerCallback) {
448
        return function (error, response, body) {
3✔
449
            if (error) {
3!
450
                innerCallback(error);
×
451
            } else if (response && response.statusCode !== 200) {
3!
452
                innerCallback(new errors.DeviceEndpointError(response.statusCode, body));
×
453
            } else {
454
                innerCallback();
3✔
455
            }
456
        };
457
    }
458

459
    function sendRequest(device, group, results, innerCallback) {
460
        const resultRequest = {
3✔
461
            url:
462
                (device.endpoint || (group && group.endpoint ? group.endpoint : undefined)) +
3!
463
                constants.HTTP_CONFIGURATION_PATH,
464

465
            method: 'POST',
466
            json: iotaUtils.createConfigurationNotification(results),
467
            headers: {
468
                'fiware-service': device.service,
469
                'fiware-servicepath': device.subservice,
470
                'content-type': 'application/json'
471
            }
472
        };
473

474
        request(resultRequest, handleDeviceResponse(innerCallback));
3✔
475
    }
476
    iotaUtils.retrieveDevice(deviceId, apiKey, function (error, device) {
3✔
477
        if (error) {
3!
478
            callback(error);
×
479
        } else if (!device.endpoint) {
3!
480
            callback(new errors.EndpointNotFound(device.id));
×
481
        } else {
482
            sendRequest(device, group, results, callback);
3✔
483
        }
484
    });
485
}
486

487
function handleConfigurationRequest(req, res, next) {
488
    function replyToDevice(error) {
489
        res.set(constants.X_PROCESSING_TIME, Date.now() - req.startTime);
3✔
490
        if (error) {
3!
491
            res.status(error.code).json(error);
×
492
        } else {
493
            res.status(200).json({});
3✔
494
        }
495
    }
496
    iotaUtils.retrieveDevice(req.deviceId, req.apiKey, function (error, device) {
3✔
497
        if (error) {
3!
498
            next(error);
×
499
        } else {
500
            iotaUtils.manageConfiguration(
3✔
501
                req.apiKey,
502
                req.deviceId,
503
                device,
504
                req.jsonPayload,
505
                sendConfigurationToDevice,
506
                replyToDevice
507
            );
508
        }
509
    });
510
}
511

512
function reqTiming(req, res, next) {
513
    req.startTime = Date.now();
156✔
514
    next();
156✔
515
}
516

517
function handleError(error, req, res, next) {
518
    let code = 500;
×
519

520
    config.getLogger().debug(context, 'Error %s handling request: %s', error.name, error.message);
×
521

522
    if (error.code && String(error.code).match(/^[2345]\d\d$/)) {
×
523
        code = error.code;
×
524
    }
525
    if (error.status && String(error.status).match(/^[2345]\d\d$/)) {
×
526
        code = error.status;
×
527
    }
528

529
    res.set(constants.X_PROCESSING_TIME, Date.now() - req.startTime);
×
530
    res.status(code).json({
×
531
        name: error.name,
532
        message: error.message
533
    });
534
}
535

536
/**
537
 * Just fills in the transport protocol in case there is none and polling if endpoint.
538
 *
539
 * @param {Object} device           Device object containing all the information about the device.
540
 * @param {Object} group            Group object containing all the information about the device.
541
 */
542
function setPollingAndDefaultTransport(device, group, callback) {
543
    config.getLogger().debug(context, 'httpbinding.setPollingAndDefaultTransport device %j group %j', device, group);
163✔
544
    if (!device.transport) {
163✔
545
        device.transport = group && group.transport ? group.transport : config.getConfig().defaultTransport;
89!
546
    }
547

548
    if (device.transport === 'HTTP') {
163✔
549
        if (device.endpoint) {
111✔
550
            device.polling = false;
75✔
551
        } else {
552
            device.polling = !(group && group.endpoint);
36✔
553
        }
554
    }
555
    callback(null, device);
163✔
556
}
557

558
/**
559
 * Device provisioning handler.
560
 *
561
 * @param {Object} device           Device object containing all the information about the provisioned device.
562
 */
563
function deviceProvisioningHandler(device, callback) {
564
    config.getLogger().debug(context, 'httpbinding.deviceProvisioningHandler device %j', device);
159✔
565
    let group = {};
159✔
566
    iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', device.apikey, function (
159!
567
        error,
568
        foundGroup
569
    ) {
570
        if (!error) {
159!
571
            group = foundGroup;
×
572
        }
573
        config.getLogger().debug(context, 'httpbinding.deviceProvisioningHandler group %j', group);
159✔
574
        setPollingAndDefaultTransport(device, group, callback);
159✔
575
    });
576
}
577

578
/**
579
 * Device updating handler. This handler just fills in the transport protocol in case there is none.
580
 *
581
 * @param {Object} device           Device object containing all the information about the updated device.
582
 */
583
function deviceUpdatingHandler(newDevice, oldDevice, callback) {
584
    config
4✔
585
        .getLogger()
586
        .debug(context, 'httpbinding.deviceUpdatingHandler newDevice %j oldDevice %j', newDevice, oldDevice);
587
    let group = {};
4✔
588
    iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', oldDevice.apikey, function (
4!
589
        error,
590
        foundGroup
591
    ) {
592
        if (!error) {
4!
593
            group = foundGroup;
×
594
        }
595
        config.getLogger().debug(context, 'httpbinding.deviceUpdatingHandler group %j', group);
4✔
596
        setPollingAndDefaultTransport(newDevice, group, callback);
4✔
597
    });
598
}
599

600
/**
601
 * This middleware checks whether there is any polling command pending to be sent to the device. If there is some,
602
 * add the command information to the return payload. Otherwise it returns an empty payload.
603
 */
604
function returnCommands(req, res, next) {
605
    function updateCommandStatus(device, commandList) {
606
        context = fillService(context, device);
6✔
607
        function createCommandUpdate(command) {
608
            return apply(
6✔
609
                iotAgentLib.setCommandResult,
610
                device.name,
611
                device.resource,
612
                req.query.k,
613
                command.name,
614
                ' ',
615
                'DELIVERED',
616
                device
617
            );
618
        }
619

620
        function cleanCommand(command) {
621
            return apply(iotAgentLib.removeCommand, device.service, device.subservice, device.id, command.name);
6✔
622
        }
623

624
        const updates = commandList.map(createCommandUpdate);
6✔
625
        const cleanCommands = commandList.map(cleanCommand);
6✔
626
        if (updates) {
6!
627
            async.parallel(updates.concat(cleanCommands), function (error, results) {
6✔
628
                if (error) {
6!
629
                    config
×
630
                        .getLogger()
631
                        .error(
632
                            context,
633
                            'Error updating command status after delivering commands for device %s',
634
                            device.id
635
                        );
636
                } else {
637
                    config
6✔
638
                        .getLogger()
639
                        .debug(
640
                            context,
641
                            'Command status updated successfully after delivering command list to device %s',
642
                            device.id
643
                        );
644
                }
645
            });
646
        }
647
    }
648

649
    function parseCommand(item) {
650
        const result = {};
6✔
651
        const cleanedValue = String(item.value).trim();
6✔
652

653
        if (cleanedValue !== '') {
6!
654
            result[item.name] = item.value;
6✔
655
        }
656
        return result;
6✔
657
    }
658

659
    function concatCommand(previous, current) {
660
        if (previous === {}) {
6!
661
            return current;
×
662
        }
663
        return _.extend(previous, current);
6✔
664
    }
665
    if (req.query && req.query.getCmd === '1') {
153✔
666
        iotAgentLib.commandQueue(req.device.service, req.device.subservice, req.deviceId, function (error, list) {
6✔
667
            res.set(constants.X_PROCESSING_TIME, Date.now() - req.startTime);
6✔
668
            if (error || !list || list.count === 0) {
6!
669
                if (req.accepts('json')) {
×
670
                    res.status(200).send({});
×
671
                } else {
672
                    res.status(200).send('');
×
673
                }
674
            } else {
675
                if (req.accepts('json')) {
6!
676
                    res.status(200).send(list.commands.map(parseCommand).reduce(concatCommand, {}));
6✔
677
                } else {
678
                    res.status(200).send(JSON.stringify(list.commands.map(parseCommand).reduce(concatCommand, {})));
×
679
                }
680
                process.nextTick(updateCommandStatus.bind(null, req.device, list.commands));
6✔
681
            }
682
        });
683
    } else if (req.accepts('json')) {
147!
684
        res.set(constants.X_PROCESSING_TIME, Date.now() - req.startTime);
147✔
685
        res.status(200).send({});
147✔
686
    } else {
687
        res.set(constants.X_PROCESSING_TIME, Date.now() - req.startTime);
×
688
        res.status(200).send('');
×
689
    }
690
}
691

692
function start(callback) {
693
    const baseRoot = '/';
262✔
694

695
    httpBindingServer = {
262✔
696
        server: null,
697
        app: express(),
698
        router: express.Router()
699
    };
700

701
    if (!config.getConfig().http) {
262!
702
        config
×
703
            .getLogger()
704
            .fatal(context, 'GLOBAL-002: Configuration error. Configuration object [config.http] is missing');
705
        callback(new errors.ConfigurationError('config.http'));
×
706
        return;
×
707
    }
708

709
    httpBindingServer.app.set('port', config.getConfig().http.port);
262✔
710
    httpBindingServer.app.set('host', config.getConfig().http.host || '0.0.0.0');
262!
711
    httpBindingServer.app.use(reqTiming);
262✔
712

713
    httpBindingServer.router.get(
262✔
714
        config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH,
262!
715
        checkMandatoryParams(true),
716
        parseData,
717
        addTimestamp,
718
        handleIncomingMeasure,
719
        returnCommands
720
    );
721

722
    httpBindingServer.router.post(
262✔
723
        config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH,
262!
724
        bodyParser.json({ strict: false, limit: config.getConfig().iota.expressLimit }), // accept anything JSON.parse accepts
725
        checkMandatoryParams(false),
726
        parseDataMultipleMeasure,
727
        addTimestamp,
728
        handleIncomingMeasure,
729
        returnCommands
730
    );
731

732
    httpBindingServer.router.post(
262✔
733
        (config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH) +
262!
734
            '/' +
735
            constants.MEASURES_SUFIX +
736
            '/:attrValue',
737
        parserBody(),
738
        checkMandatoryParams(false),
739
        parseData, // non multiple measures are expected in this route
740
        addTimestamp,
741
        handleIncomingMeasure,
742
        returnCommands
743
    );
744

745
    httpBindingServer.router.post(
262✔
746
        (config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH) + constants.HTTP_COMMANDS_PATH,
262!
747
        bodyParser.json({ strict: false, limit: config.getConfig().iota.expressLimit }), // accept anything JSON.parse accepts.
748
        checkMandatoryParams(false),
749
        parseData,
750
        addTimestamp,
751
        isCommand,
752
        handleIncomingMeasure,
753
        returnCommands
754
    );
755

756
    httpBindingServer.router.post(
262✔
757
        (config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH) + constants.HTTP_CONFIGURATION_PATH,
262!
758
        bodyParser.json({ strict: false, limit: config.getConfig().iota.expressLimit }), // accept anything JSON.parse accepts.
759
        checkMandatoryParams(false),
760
        parseData,
761
        handleConfigurationRequest
762
    );
763

764
    httpBindingServer.app.use(baseRoot, httpBindingServer.router);
262✔
765
    httpBindingServer.app.use(handleError);
262✔
766

767
    if (config.getConfig().http && config.getConfig().http.key && config.getConfig().http.cert) {
262!
768
        const privateKey = fs.readFileSync(config.getConfig().http.key, 'utf8');
×
769
        const certificate = fs.readFileSync(config.getConfig().http.cert, 'utf8');
×
770
        const credentials = { key: privateKey, cert: certificate };
×
771

772
        config.getLogger().info(context, 'HTTPS Binding listening on port %s', config.getConfig().http.port);
×
773
        httpBindingServer.server = https.createServer(credentials, httpBindingServer.app);
×
774
    } else {
775
        config.getLogger().info(context, 'HTTP Binding listening on port %s', config.getConfig().http.port);
262✔
776
        httpBindingServer.server = http.createServer(httpBindingServer.app);
262✔
777
    }
778

779
    httpBindingServer.server.listen(httpBindingServer.app.get('port'), httpBindingServer.app.get('host'), callback);
262✔
780
}
781

782
function stop(callback) {
783
    config.getLogger().info(context, 'Stopping JSON HTTP Binding: ');
262✔
784

785
    if (httpBindingServer) {
262!
786
        httpBindingServer.server.close(function () {
262✔
787
            config.getLogger().info(context, 'HTTP Binding Stopped');
262✔
788
            callback();
262✔
789
        });
790
    } else {
791
        callback();
×
792
    }
793
}
794

795
function sendPushNotifications(device, group, values, callback) {
796
    const executions = _.flatten(
1✔
797
        values.map(commandHandler.generateCommandExecution.bind(null, group.apikey, device, group))
798
    );
799

800
    async.series(executions, function (error) {
1✔
801
        callback(error);
1✔
802
    });
803
}
804

805
function storePollNotifications(device, group, values, callback) {
806
    function addPollNotification(item, innerCallback) {
807
        iotAgentLib.addCommand(device.service, device.subservice, device.id, item, innerCallback);
×
808
    }
UNCOV
809
    async.map(values, addPollNotification, callback);
×
810
}
811

812
function notificationHandler(device, values, callback) {
813
    config.getLogger().debug(context, 'values for command %j and device %j', values, device);
1✔
814

815
    function invokeWithConfiguration(apiKey, callback) {
816
        let group = {};
1✔
817
        iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', apiKey, function (
1!
818
            error,
819
            foundGroup
820
        ) {
821
            if (!error) {
1!
822
                group = foundGroup;
1✔
823
            }
824
            var cmdValue = { type: 'command' };
1✔
825
            for (let val of values) {
1✔
826
                if (val.name === 'cmd') {
13✔
827
                    cmdValue.name = val.value;
1✔
828
                } else if (val.name === 'params') {
12✔
829
                    cmdValue.value = val.value;
1✔
830
                } else {
831
                    // other fields like status, info, onDelivered, OnError
832
                    cmdValue[val.name] = val.value;
11✔
833
                }
834
            }
835
            var cmdValues = [cmdValue];
1✔
836
            config.getLogger().debug(context, 'cmdValues %j', cmdValues);
1✔
837
            iotAgentLib.executeUpdateSideEffects(
1✔
838
                device,
839
                device.id,
840
                device.type,
841
                device.service,
842
                device.subservice,
843
                cmdValues,
844
                function () {
845
                    if (device.endpoint || group.endpoint) {
1!
846
                        sendPushNotifications(device, group, cmdValues, callback);
1✔
847
                    } else {
NEW
848
                        storePollNotifications(device, group, cmdValues, callback);
×
849
                    }
850
                }
851
            );
852
        });
853
    }
854

855
    async.waterfall(
1✔
856
        [apply(iotaUtils.getEffectiveApiKey, device.service, device.subservice, device), invokeWithConfiguration],
857
        callback
858
    );
859
}
860

861
exports.start = start;
1✔
862
exports.stop = stop;
1✔
863
exports.sendConfigurationToDevice = sendConfigurationToDevice;
1✔
864
exports.deviceProvisioningHandler = deviceProvisioningHandler;
1✔
865
exports.deviceUpdatingHandler = deviceUpdatingHandler;
1✔
866
exports.notificationHandler = notificationHandler;
1✔
867
exports.executeCommand = executeCommand;
1✔
868
exports.protocol = 'HTTP';
1✔
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