UNPKG

azure-iot-device

Version:
395 lines 23.9 kB
/* eslint-disable @typescript-eslint/adjacent-overload-signatures */ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.InternalClient = void 0; const events_1 = require("events"); const fs = require("fs"); const dbg = require("debug"); const debug = dbg('azure-iot-device:InternalClient'); const debugErrors = dbg('azure-iot-device:InternalClient:Errors'); const azure_iot_common_1 = require("azure-iot-common"); const azure_iot_common_2 = require("azure-iot-common"); const device_method_1 = require("./device_method"); const twin_1 = require("./twin"); /** * @private * Default maximum operation timeout for client operations: 4 minutes. */ const MAX_OPERATION_TIMEOUT = 240000; function safeCallback(callback, error, result) { if (callback) callback(error, result); } /** * @private */ class InternalClient extends events_1.EventEmitter { constructor(transport, connStr) { /*Codes_SRS_NODE_INTERNAL_CLIENT_05_001: [The Client constructor shall throw ReferenceError if the transport argument is falsy.]*/ if (!transport) throw new ReferenceError('transport is \'' + transport + '\''); super(); this._userRegisteredMethodListener = false; if (connStr) { throw new azure_iot_common_1.errors.InvalidOperationError('the connectionString parameter of the constructor is not used - users of the SDK should be using the `fromConnectionString` factory method.'); } this._transport = transport; this._transport.on('error', (err) => { // errors right now bubble up through the disconnect handler. // ultimately we would like to get rid of that disconnect event and rely on the error event instead debugErrors('Transport error: ' + err); }); /*Codes_SRS_NODE_INTERNAL_CLIENT_41_001: [A `connect` event will be emitted when a `connected` event is received from the transport.]*/ this._transport.on('connected', () => this.emit('connect')); this._methodCallbackMap = {}; this._disconnectHandler = (err) => { if (err) { debugErrors('transport disconnect event: ' + err); } else { debug('transport disconnect event: no error'); } if (err && this._retryPolicy.shouldRetry(err)) { /*Codes_SRS_NODE_INTERNAL_CLIENT_16_098: [If the transport emits a `disconnect` event while the client is subscribed to direct methods the retry policy shall be used to reconnect and re-enable the feature using the transport `enableMethods` method.]*/ if (this._userRegisteredMethodListener) { debug('re-enabling Methods link'); this._enableMethods((err) => { if (err) { debugErrors('Error enabling methods: ' + err); /*Codes_SRS_NODE_INTERNAL_CLIENT_16_100: [If the retry policy fails to reestablish the direct methods functionality a `disconnect` event shall be emitted with a `results.Disconnected` object.]*/ this.emit('disconnect', new azure_iot_common_1.results.Disconnected(err)); } }); } /*Codes_SRS_NODE_INTERNAL_CLIENT_16_099: [If the transport emits a `disconnect` event while the client is subscribed to desired properties updates the retry policy shall be used to reconnect and re-enable the feature using the transport `enableTwinDesiredPropertiesUpdates` method.]*/ if (this._twin && this._twin.userRegisteredDesiredPropertiesListener) { debug('re-enabling Twin'); this._twin.enableTwinDesiredPropertiesUpdates((err) => { if (err) { debugErrors('Error enabling twin: ' + err); /*Codes_SRS_NODE_INTERNAL_CLIENT_16_101: [If the retry policy fails to reestablish the twin desired properties updates functionality a `disconnect` event shall be emitted with a `results.Disconnected` object.]*/ this.emit('disconnect', new azure_iot_common_1.results.Disconnected(err)); } }); } } else { this.emit('disconnect', new azure_iot_common_1.results.Disconnected(err)); } }; /*Codes_SRS_NODE_INTERNAL_CLIENT_16_045: [If the transport successfully establishes a connection the `open` method shall subscribe to the `disconnect` event of the transport.]*/ this._transport.on('disconnect', this._disconnectHandler); this._retryPolicy = new azure_iot_common_2.ExponentialBackOffWithJitter(); this._maxOperationTimeout = MAX_OPERATION_TIMEOUT; } /*Codes_SRS_NODE_INTERNAL_CLIENT_05_016: [When a Client method encounters an error in the transport, the callback function (indicated by the done argument) shall be invoked with the following arguments: err - the standard JavaScript Error object, with a response property that points to a transport-specific response object, and a responseBody property that contains the body of the transport response.]*/ /*Codes_SRS_NODE_INTERNAL_CLIENT_05_017: [With the exception of receive, when a Client method completes successfully, the callback function (indicated by the done argument) shall be invoked with the following arguments: err - null response - a transport-specific response object]*/ updateSharedAccessSignature(sharedAccessSignature, updateSasCallback) { /*Codes_SRS_NODE_INTERNAL_CLIENT_16_031: [The updateSharedAccessSignature method shall throw a ReferenceError if the sharedAccessSignature parameter is falsy.]*/ if (!sharedAccessSignature) throw new ReferenceError('sharedAccessSignature is falsy'); const retryOp = new azure_iot_common_2.RetryOperation('updateSharedAccessSignature', this._retryPolicy, this._maxOperationTimeout); retryOp.retry((opCallback) => { this._transport.updateSharedAccessSignature(sharedAccessSignature, opCallback); }, (err, result) => { if (!err) { this.emit('_sharedAccessSignatureUpdated'); } safeCallback(updateSasCallback, err, result); }); } open(openCallback) { return (0, azure_iot_common_1.callbackToPromise)((_callback) => { const retryOp = new azure_iot_common_2.RetryOperation('open', this._retryPolicy, this._maxOperationTimeout); retryOp.retry((opCallback) => { this._transport.connect(opCallback); }, (connectErr, connectResult) => { /*Codes_SRS_NODE_INTERNAL_CLIENT_16_060: [The `open` method shall call the `_callback` callback with a null error object and a `results.Connected()` result object if the transport is already connected, doesn't need to connect or has just connected successfully.]*/ safeCallback(_callback, connectErr, connectResult); }); }, openCallback); } sendEvent(message, sendEventCallback) { return (0, azure_iot_common_1.callbackToPromise)((_callback) => { const retryOp = new azure_iot_common_2.RetryOperation('sendEvent', this._retryPolicy, this._maxOperationTimeout); retryOp.retry((opCallback) => { /*Codes_SRS_NODE_INTERNAL_CLIENT_05_007: [The sendEvent method shall send the event indicated by the message argument via the transport associated with the Client instance.]*/ this._transport.sendEvent(message, opCallback); }, (err, result) => { safeCallback(_callback, err, result); }); }, sendEventCallback); } sendEventBatch(messages, sendEventBatchCallback) { return (0, azure_iot_common_1.callbackToPromise)((_callback) => { const retryOp = new azure_iot_common_2.RetryOperation('sendEventBatch', this._retryPolicy, this._maxOperationTimeout); retryOp.retry((opCallback) => { /*Codes_SRS_NODE_INTERNAL_CLIENT_05_008: [The sendEventBatch method shall send the list of events (indicated by the messages argument) via the transport associated with the Client instance.]*/ this._transport.sendEventBatch(messages, opCallback); }, (err, result) => { safeCallback(_callback, err, result); }); }, sendEventBatchCallback); } close(closeCallback) { return (0, azure_iot_common_1.callbackToPromise)((_callback) => { this._closeTransport((err, result) => { safeCallback(_callback, err, result); }); }, closeCallback); } setTransportOptions(options, done) { return (0, azure_iot_common_1.callbackToPromise)((_callback) => { /*Codes_SRS_NODE_INTERNAL_CLIENT_16_024: [The ‘setTransportOptions’ method shall throw a ‘ReferenceError’ if the options object is falsy] */ if (!options) throw new ReferenceError('options cannot be falsy.'); /*Codes_SRS_NODE_INTERNAL_CLIENT_16_025: [The ‘setTransportOptions’ method shall throw a ‘NotImplementedError’ if the transport doesn’t implement a ‘setOption’ method.] */ if (typeof this._transport.setOptions !== 'function') throw new azure_iot_common_1.errors.NotImplementedError('setOptions does not exist on this transport'); const clientOptions = { http: { receivePolicy: options } }; const retryOp = new azure_iot_common_2.RetryOperation('setTransportOptions', this._retryPolicy, this._maxOperationTimeout); retryOp.retry((opCallback) => { /*Codes_SRS_NODE_INTERNAL_CLIENT_16_021: [The ‘setTransportOptions’ method shall call the ‘setOptions’ method on the transport object.]*/ this._transport.setOptions(clientOptions, opCallback); }, (err) => { if (err) { safeCallback(_callback, err); } else { safeCallback(_callback, null, new azure_iot_common_1.results.TransportConfigured()); } }); }, done); } setOptions(options, done) { return (0, azure_iot_common_1.callbackToPromise)((_callback) => { /*Codes_SRS_NODE_INTERNAL_CLIENT_16_042: [The `setOptions` method shall throw a `ReferenceError` if the options object is falsy.]*/ if (!options) throw new ReferenceError('options cannot be falsy.'); /*Codes_SRS_NODE_INTERNAL_CLIENT_06_001: [The `setOptions` method shall assume the `ca` property is the name of an already existent file and it will attempt to read that file as a pem into a string value and pass the string to config object `ca` property. Otherwise, it is assumed to be a pem string.] */ if (options.ca) { // eslint-disable-next-line security/detect-non-literal-fs-filename fs.readFile(options.ca, 'utf8', (err, contents) => { if (!err) { const localOptions = {}; for (const k in options) { localOptions[k] = options[k]; } localOptions.ca = contents; this._invokeSetOptions(localOptions, _callback); } else { this._invokeSetOptions(options, _callback); } }); } else { this._invokeSetOptions(options, _callback); } }, done); } complete(message, completeCallback) { return (0, azure_iot_common_1.callbackToPromise)((_callback) => { /*Codes_SRS_NODE_INTERNAL_CLIENT_16_016: [The ‘complete’ method shall throw a ReferenceError if the ‘message’ parameter is falsy.] */ if (!message) throw new ReferenceError('message is \'' + message + '\''); const retryOp = new azure_iot_common_2.RetryOperation('complete', this._retryPolicy, this._maxOperationTimeout); retryOp.retry((opCallback) => { this._transport.complete(message, opCallback); }, (err, result) => { safeCallback(_callback, err, result); }); }, completeCallback); } reject(message, rejectCallback) { return (0, azure_iot_common_1.callbackToPromise)((_callback) => { /*Codes_SRS_NODE_INTERNAL_CLIENT_16_018: [The reject method shall throw a ReferenceError if the ‘message’ parameter is falsy.] */ if (!message) throw new ReferenceError('message is \'' + message + '\''); const retryOp = new azure_iot_common_2.RetryOperation('reject', this._retryPolicy, this._maxOperationTimeout); retryOp.retry((opCallback) => { this._transport.reject(message, opCallback); }, (err, result) => { safeCallback(_callback, err, result); }); }, rejectCallback); } abandon(message, abandonCallback) { return (0, azure_iot_common_1.callbackToPromise)((_callback) => { /*Codes_SRS_NODE_INTERNAL_CLIENT_16_017: [The abandon method shall throw a ReferenceError if the ‘message’ parameter is falsy.] */ if (!message) throw new ReferenceError('message is \'' + message + '\''); const retryOp = new azure_iot_common_2.RetryOperation('abandon', this._retryPolicy, this._maxOperationTimeout); retryOp.retry((opCallback) => { this._transport.abandon(message, opCallback); }, (err, result) => { safeCallback(_callback, err, result); }); }, abandonCallback); } getTwin(done) { return (0, azure_iot_common_1.callbackToPromise)((_callback) => { /*Codes_SRS_NODE_INTERNAL_CLIENT_16_094: [If this is the first call to `getTwin` the method shall instantiate a new `Twin` object and pass it the transport currently in use.]*/ if (!this._twin) { this._twin = new twin_1.Twin(this._transport, this._retryPolicy, this._maxOperationTimeout); } /*Codes_SRS_NODE_INTERNAL_CLIENT_16_095: [The `getTwin` method shall call the `get()` method on the `Twin` object currently in use and pass it its `done` argument for a callback.]*/ this._twin.get(_callback); }, done); } /** * Sets the retry policy used by the client on all operations. The default is {@link azure-iot-common.ExponentialBackoffWithJitter|ExponentialBackoffWithJitter}. * * @param policy {RetryPolicy} The retry policy that should be used for all future operations. */ setRetryPolicy(policy) { /*Codes_SRS_NODE_INTERNAL_CLIENT_16_083: [The `setRetryPolicy` method shall throw a `ReferenceError` if the policy object is falsy.]*/ if (!policy) { throw new ReferenceError('\'policy\' cannot be \'' + policy + '\''); } else { /*Codes_SRS_NODE_INTERNAL_CLIENT_16_084: [The `setRetryPolicy` method shall throw an `ArgumentError` if the policy object doesn't have a `shouldRetry` method.]*/ /*Codes_SRS_NODE_INTERNAL_CLIENT_16_085: [The `setRetryPolicy` method shall throw an `ArgumentError` if the policy object doesn't have a `nextRetryTimeout` method.]*/ if (typeof policy.shouldRetry !== 'function' || typeof policy.nextRetryTimeout !== 'function') { throw new azure_iot_common_1.errors.ArgumentError('A policy object must have a maxTimeout property that is a number and a nextRetryTimeout method.'); } } /*Codes_SRS_NODE_INTERNAL_CLIENT_16_086: [Any operation happening after a `setRetryPolicy` call should use the policy set during that call.]*/ this._retryPolicy = policy; /*Codes_SRS_NODE_INTERNAL_CLIENT_16_096: [The `setRetryPolicy` method shall call the `setRetryPolicy` method on the twin if it is set and pass it the `policy` object.]*/ if (this._twin) { this._twin.setRetryPolicy(policy); } } _onDeviceMethod(methodName, callback) { // // We want to always retain that the we want to have this feature enabled because the API (.on) doesn't really // provide for the capability to say it failed. It can certainly fail because a network operation is required to // enable. // By saving this off, we are strictly honoring that the feature is enabled. If it doesn't turn on we signal via // the emitted 'error' that something bad happened. // But if we ever again attain a connected state, this feature will be operational. // this._userRegisteredMethodListener = true; // validate input args this._validateDeviceMethodInputs(methodName, callback); this._methodCallbackMap[methodName] = callback; this._addMethodCallback(methodName, callback); this._enableMethods((err) => { if (err) { this.emit('error', err); } }); } _invokeSetOptions(options, done) { // Making this an operation that can be retried because we cannot assume the transport's behavior (whether it's going to disconnect/reconnect, etc). const retryOp = new azure_iot_common_2.RetryOperation('_invokeSetOptions', this._retryPolicy, this._maxOperationTimeout); retryOp.retry((opCallback) => { this._transport.setOptions(options, opCallback); }, (err) => { /*Codes_SRS_NODE_INTERNAL_CLIENT_16_043: [The `done` callback shall be invoked no parameters when it has successfully finished setting the client and/or transport options.]*/ /*Codes_SRS_NODE_INTERNAL_CLIENT_16_044: [The `done` callback shall be invoked with a standard javascript `Error` object and no result object if the client could not be configured as requested.]*/ safeCallback(done, err); }); } _validateDeviceMethodInputs(methodName, callback) { // Codes_SRS_NODE_INTERNAL_CLIENT_13_020: [ onDeviceMethod shall throw a ReferenceError if methodName is falsy. ] if (!methodName) { throw new ReferenceError('methodName cannot be \'' + methodName + '\''); } // Codes_SRS_NODE_INTERNAL_CLIENT_13_024: [ onDeviceMethod shall throw a TypeError if methodName is not a string. ] if (typeof (methodName) !== 'string') { throw new TypeError('methodName\'s type is \'' + typeof (methodName) + '\'. A string was expected.'); } // Codes_SRS_NODE_INTERNAL_CLIENT_13_022: [ onDeviceMethod shall throw a ReferenceError if callback is falsy. ] if (!callback) { throw new ReferenceError('callback cannot be \'' + callback + '\''); } // Codes_SRS_NODE_INTERNAL_CLIENT_13_025: [ onDeviceMethod shall throw a TypeError if callback is not a Function. ] if (typeof (callback) !== 'function') { throw new TypeError('callback\'s type is \'' + typeof (callback) + '\'. A function reference was expected.'); } // Codes_SRS_NODE_INTERNAL_CLIENT_13_023: [ onDeviceMethod shall throw an Error if a listener is already subscribed for a given method call. ] if (this._methodCallbackMap[methodName]) { throw new Error('A handler for this method has already been registered with the client.'); } } _addMethodCallback(methodName, callback) { // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; this._transport.onDeviceMethod(methodName, (message) => { // build the response object const response = new device_method_1.DeviceMethodResponse(message.requestId, self._transport); // build the request object try { const request = new device_method_1.DeviceMethodRequest(message.requestId, message.methods.methodName, message.body); // Codes_SRS_NODE_INTERNAL_CLIENT_13_001: [ The onDeviceMethod method shall cause the callback function to be invoked when a cloud-to-device method invocation signal is received from the IoT Hub service. ] callback(request, response); } catch (err) { response.send(400, 'Invalid request format: ' + err.message, (err) => { if (err) { debugErrors('Error sending invalid request response back to application: ' + err); } }); } }); } _enableMethods(callback) { debug('enabling methods'); const retryOp = new azure_iot_common_2.RetryOperation('_enableMethods', this._retryPolicy, this._maxOperationTimeout); retryOp.retry((opCallback) => { this._transport.enableMethods(opCallback); }, (err) => { if (!err) { debug('enabled methods'); } else { debugErrors('Error while enabling methods: ' + err); } callback(err); }); } // Currently there is no code making use of this function, because there is no "removeDeviceMethod" corresponding to "onDeviceMethod" // private _disableMethods(callback: (err?: Error) => void): void { // if (this._methodsEnabled) { // this._transport.disableMethods((err) => { // if (!err) { // this._methodsEnabled = false; // } // callback(err); // }); // } else { // callback(); // } // } _closeTransport(closeCallback) { const onDisconnected = (err, result) => { /*Codes_SRS_NODE_INTERNAL_CLIENT_16_056: [The `close` method shall not throw if the `closeCallback` is not passed.]*/ /*Codes_SRS_NODE_INTERNAL_CLIENT_16_055: [The `close` method shall call the `closeCallback` function when done with either a single Error object if it failed or null and a results.Disconnected object if successful.]*/ safeCallback(closeCallback, err, result); }; /*Codes_SRS_NODE_INTERNAL_CLIENT_16_046: [The `close` method shall remove the listener that has been attached to the transport `disconnect` event.]*/ this._transport.removeListener('disconnect', this._disconnectHandler); /*Codes_SRS_NODE_INTERNAL_CLIENT_16_001: [The `close` function shall call the transport's `disconnect` function if it exists.]*/ this._transport.disconnect((disconnectError, disconnectResult) => { onDisconnected(disconnectError, disconnectResult); }); } } exports.InternalClient = InternalClient; // SAS token created by the client have a lifetime of 60 minutes, renew every 45 minutes /** * @private */ InternalClient.sasRenewalInterval = 2700000; //# sourceMappingURL=internal_client.js.map