UNPKG

aws-crt

Version:

NodeJS/browser bindings to the aws-c-* libraries

558 lines 26.1 kB
"use strict"; /* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MqttClientConnection = exports.MqttClient = exports.MqttWill = exports.QoS = void 0; /** * * A module containing support for mqtt connection establishment and operations. * * @packageDocumentation * @module mqtt * @mergeTarget */ var mqtt = __importStar(require("mqtt")); var WebsocketUtils = __importStar(require("./ws")); var auth = __importStar(require("./auth")); var trie_1 = require("./trie"); var event_1 = require("../common/event"); var browser_1 = require("../browser"); var mqtt_1 = require("../common/mqtt"); var mqtt_shared_1 = require("../common/mqtt_shared"); var mqtt_2 = require("../common/mqtt"); Object.defineProperty(exports, "QoS", { enumerable: true, get: function () { return mqtt_2.QoS; } }); Object.defineProperty(exports, "MqttWill", { enumerable: true, get: function () { return mqtt_2.MqttWill; } }); /** * MQTT client * * @category MQTT */ var MqttClient = /** @class */ (function () { function MqttClient(bootstrap) { } /** * Creates a new {@link MqttClientConnection} * @param config Configuration for the connection * @returns A new connection */ MqttClient.prototype.new_connection = function (config) { return new MqttClientConnection(this, config); }; return MqttClient; }()); exports.MqttClient = MqttClient; /** * @internal */ var MqttBrowserClientState; (function (MqttBrowserClientState) { MqttBrowserClientState[MqttBrowserClientState["Connected"] = 0] = "Connected"; MqttBrowserClientState[MqttBrowserClientState["Stopped"] = 1] = "Stopped"; })(MqttBrowserClientState || (MqttBrowserClientState = {})); ; /** @internal */ var TopicTrie = /** @class */ (function (_super) { __extends(TopicTrie, _super); function TopicTrie() { return _super.call(this, '/') || this; } TopicTrie.prototype.find_node = function (key, op) { var e_1, _a; var parts = this.split_key(key); var current = this.root; var parent = undefined; try { for (var parts_1 = __values(parts), parts_1_1 = parts_1.next(); !parts_1_1.done; parts_1_1 = parts_1.next()) { var part = parts_1_1.value; var child = current.children.get(part); if (!child) { child = current.children.get('#'); if (child) { return child; } child = current.children.get('+'); } if (!child) { if (op == trie_1.TrieOp.Insert) { current.children.set(part, child = new trie_1.Node(part)); } else { return undefined; } } parent = current; current = child; } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (parts_1_1 && !parts_1_1.done && (_a = parts_1.return)) _a.call(parts_1); } finally { if (e_1) throw e_1.error; } } if (parent && op == trie_1.TrieOp.Delete) { parent.children.delete(current.key); } return current; }; return TopicTrie; }(trie_1.Trie)); /** * MQTT client connection * * @category MQTT */ var MqttClientConnection = /** @class */ (function (_super) { __extends(MqttClientConnection, _super); /** * @param client The client that owns this connection * @param config The configuration for this connection */ function MqttClientConnection(client, config) { var _this = _super.call(this) || this; _this.client = client; _this.config = config; _this.subscriptions = new TopicTrie(); _this.connection_count = 0; // track number of times in a row that reconnect has been attempted // use exponential backoff between subsequent failed attempts _this.reconnect_count = 0; _this.reconnect_min_sec = mqtt_1.DEFAULT_RECONNECT_MIN_SEC; _this.reconnect_max_sec = mqtt_1.DEFAULT_RECONNECT_MAX_SEC; _this.currentState = MqttBrowserClientState.Stopped; _this.desiredState = MqttBrowserClientState.Stopped; _this.on_connect = function (connack) { _this.on_online(connack.sessionPresent); }; _this.on_online = function (session_present) { _this.currentState = MqttBrowserClientState.Connected; if (++_this.connection_count == 1) { _this.emit('connect', session_present); } else { /** Reset reconnect times after reconnect succeed. */ _this.reset_reconnect_times(); _this.emit('resume', 0, session_present); } }; _this.on_close = function () { /* * Only emit an interruption event if we were connected, otherwise we just failed to reconnect after * a disconnection. */ if (_this.currentState == MqttBrowserClientState.Connected) { _this.currentState = MqttBrowserClientState.Stopped; _this.emit('interrupt', -1); } /* Only try and reconnect if our desired state is connected, ie no one has called disconnect() */ if (_this.desiredState == MqttBrowserClientState.Connected) { var waitTime = _this.get_reconnect_time_sec(); _this.reconnectTask = setTimeout(function () { /** Emit reconnect after backoff time */ _this.reconnect_count++; _this.connection.reconnect(); }, waitTime * 1000); } }; _this.on_disconnected = function () { _this.emit('disconnect'); }; _this.on_error = function (error) { _this.emit('error', new browser_1.CrtError(error)); }; _this.on_message = function (topic, payload, packet) { // pass payload as ArrayBuffer var array_buffer = payload.buffer.slice(payload.byteOffset, payload.byteOffset + payload.byteLength); var callback = _this.subscriptions.find(topic); if (callback) { callback(topic, array_buffer, packet.dup, packet.qos, packet.retain); } _this.emit('message', topic, array_buffer, packet.dup, packet.qos, packet.retain); }; var create_websocket_stream = function (client) { return WebsocketUtils.create_websocket_stream(_this.config); }; var transform_websocket_url = function (url, options, client) { return WebsocketUtils.create_websocket_url(_this.config); }; if (config == null || config == undefined) { throw new browser_1.CrtError("MqttClientConnection constructor: config not defined"); } var will = _this.config.will ? { topic: _this.config.will.topic, payload: (0, mqtt_shared_1.normalize_payload)(_this.config.will.payload), qos: _this.config.will.qos, retain: _this.config.will.retain, } : undefined; if (config.reconnect_min_sec !== undefined) { _this.reconnect_min_sec = config.reconnect_min_sec; // clamp max, in case they only passed in min _this.reconnect_max_sec = Math.max(_this.reconnect_min_sec, _this.reconnect_max_sec); } if (config.reconnect_max_sec !== undefined) { _this.reconnect_max_sec = config.reconnect_max_sec; // clamp min, in case they only passed in max (or passed in min > max) _this.reconnect_min_sec = Math.min(_this.reconnect_min_sec, _this.reconnect_max_sec); } _this.reset_reconnect_times(); // If the credentials are set but no the credentials_provider if (_this.config.credentials_provider == undefined && _this.config.credentials != undefined) { var provider = new auth.StaticCredentialProvider({ aws_region: _this.config.credentials.aws_region, aws_access_id: _this.config.credentials.aws_access_id, aws_secret_key: _this.config.credentials.aws_secret_key, aws_sts_token: _this.config.credentials.aws_sts_token }); _this.config.credentials_provider = provider; } var websocketXform = (_this.config.websocket || {}).protocol != 'wss-custom-auth' ? transform_websocket_url : undefined; _this.connection = new mqtt.MqttClient(create_websocket_stream, { // service default is 1200 seconds keepalive: _this.config.keep_alive ? _this.config.keep_alive : 1200, clientId: _this.config.client_id, connectTimeout: _this.config.ping_timeout ? _this.config.ping_timeout : 30 * 1000, clean: _this.config.clean_session, username: _this.config.username, password: _this.config.password, reconnectPeriod: _this.reconnect_max_sec * 1000, will: will, transformWsUrl: websocketXform, }); _this.connection.on('connect', _this.on_connect); _this.connection.on('error', _this.on_error); _this.connection.on('message', _this.on_message); _this.connection.on('close', _this.on_close); _this.connection.on('end', _this.on_disconnected); return _this; } MqttClientConnection.prototype.on = function (event, listener) { return _super.prototype.on.call(this, event, listener); }; /** * Open the actual connection to the server (async). * @returns A Promise which completes whether the connection succeeds or fails. * If connection fails, the Promise will reject with an exception. * If connection succeeds, the Promise will return a boolean that is * true for resuming an existing session, or false if the session is new */ MqttClientConnection.prototype.connect = function () { return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_a) { this.desiredState = MqttBrowserClientState.Connected; setTimeout(function () { _this.uncork(); }, 0); return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () { var provider, on_connect_error; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: provider = this.config.credentials_provider; if (!provider) return [3 /*break*/, 2]; return [4 /*yield*/, provider.refreshCredentials()]; case 1: _a.sent(); _a.label = 2; case 2: on_connect_error = function (error) { reject(new browser_1.CrtError(error)); }; this.connection.once('connect', function (connack) { _this.connection.removeListener('error', on_connect_error); resolve(connack.sessionPresent); }); this.connection.once('error', on_connect_error); return [2 /*return*/]; } }); }); })]; }); }); }; /** * The connection will automatically reconnect. To cease reconnection attempts, call {@link disconnect}. * To resume the connection, call {@link connect}. * @deprecated */ MqttClientConnection.prototype.reconnect = function () { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, this.connect()]; }); }); }; /** * Publish message (async). * If the device is offline, the PUBLISH packet will be sent once the connection resumes. * * @param topic Topic name * @param payload Contents of message * @param qos Quality of Service for delivering this message * @param retain If true, the server will store the message and its QoS so that it can be * delivered to future subscribers whose subscriptions match the topic name * @returns Promise which returns a {@link MqttRequest} which will contain the packet id of * the PUBLISH packet. * * * For QoS 0, completes as soon as the packet is sent. * * For QoS 1, completes when PUBACK is received. * * For QoS 2, completes when PUBCOMP is received. */ MqttClientConnection.prototype.publish = function (topic, payload, qos, retain) { if (retain === void 0) { retain = false; } return __awaiter(this, void 0, void 0, function () { var payload_data; var _this = this; return __generator(this, function (_a) { payload_data = (0, mqtt_shared_1.normalize_payload)(payload); return [2 /*return*/, new Promise(function (resolve, reject) { _this.connection.publish(topic, payload_data, { qos: qos, retain: retain }, function (error, packet) { if (error) { reject(new browser_1.CrtError(error)); return _this.on_error(error); } var id = undefined; if (qos != mqtt_1.QoS.AtMostOnce) { id = packet.messageId; } resolve({ packet_id: id }); }); })]; }); }); }; /** * Subscribe to a topic filter (async). * The client sends a SUBSCRIBE packet and the server responds with a SUBACK. * * subscribe() may be called while the device is offline, though the async * operation cannot complete successfully until the connection resumes. * * Once subscribed, `callback` is invoked each time a message matching * the `topic` is received. It is possible for such messages to arrive before * the SUBACK is received. * * @param topic Subscribe to this topic filter, which may include wildcards * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param on_message Optional callback invoked when message received. * @returns Promise which returns a {@link MqttSubscribeRequest} which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. */ MqttClientConnection.prototype.subscribe = function (topic, qos, on_message) { return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_a) { this.subscriptions.insert(topic, on_message); return [2 /*return*/, new Promise(function (resolve, reject) { _this.connection.subscribe(topic, { qos: qos }, function (error, packet) { if (error) { reject(new browser_1.CrtError(error)); return _this.on_error(error); } var sub = packet[0]; resolve({ topic: sub.topic, qos: sub.qos }); }); })]; }); }); }; /** * Unsubscribe from a topic filter (async). * The client sends an UNSUBSCRIBE packet, and the server responds with an UNSUBACK. * @param topic The topic filter to unsubscribe from. May contain wildcards. * @returns Promise which returns a {@link MqttRequest} which will contain the packet id * of the UNSUBSCRIBE packet being acknowledged. Promise is resolved when an * UNSUBACK is received from the server or is rejected when an exception occurs. */ MqttClientConnection.prototype.unsubscribe = function (topic) { return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_a) { this.subscriptions.remove(topic); return [2 /*return*/, new Promise(function (resolve, reject) { _this.connection.unsubscribe(topic, undefined, function (error, packet) { if (error) { reject(new browser_1.CrtError(error)); return _this.on_error(error); } resolve({ packet_id: packet ? packet.messageId : undefined, }); }); })]; }); }); }; /** * Close the connection (async). * @returns Promise which completes when the connection is closed. */ MqttClientConnection.prototype.disconnect = function () { return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_a) { this.desiredState = MqttBrowserClientState.Stopped; /* If the user wants to disconnect, stop the recurrent connection task */ if (this.reconnectTask) { clearTimeout(this.reconnectTask); this.reconnectTask = undefined; } return [2 /*return*/, new Promise(function (resolve) { /* * The original implementation did not force the disconnect so in our update to fix the promise resolution, * we need to keep that contract. */ _this.connection.end(false, {}, resolve); })]; }); }); }; MqttClientConnection.prototype.reset_reconnect_times = function () { this.reconnect_count = 0; }; /** * Returns seconds until next reconnect attempt. */ MqttClientConnection.prototype.get_reconnect_time_sec = function () { if (this.reconnect_min_sec == 0 && this.reconnect_max_sec == 0) { return 0; } // Uses "FullJitter" backoff algorithm, described here: // https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ // We slightly vary the algorithm described on the page, // which takes (base,cap) and may result in 0. // But we take (min,max) as parameters, and don't don't allow results less than min. var cap = this.reconnect_max_sec - this.reconnect_min_sec; var base = Math.max(this.reconnect_min_sec, 1); /** Use Math.pow() since IE does not support ** operator */ var sleep = Math.random() * Math.min(cap, base * Math.pow(2, this.reconnect_count)); return this.reconnect_min_sec + sleep; }; /** * Emitted when the connection successfully establishes itself for the first time * * @event */ MqttClientConnection.CONNECT = 'connect'; /** * Emitted when connection has disconnected sucessfully. * * @event */ MqttClientConnection.DISCONNECT = 'disconnect'; /** * Emitted when an error occurs. The error will contain the error * code and message. * * @event */ MqttClientConnection.ERROR = 'error'; /** * Emitted when the connection is dropped unexpectedly. The error will contain the error * code and message. The underlying mqtt implementation will attempt to reconnect. * * @event */ MqttClientConnection.INTERRUPT = 'interrupt'; /** * Emitted when the connection reconnects (after an interrupt). Only triggers on connections after the initial one. * * @event */ MqttClientConnection.RESUME = 'resume'; /** * Emitted when any MQTT publish message arrives. * * @event */ MqttClientConnection.MESSAGE = 'message'; return MqttClientConnection; }(event_1.BufferedEventEmitter)); exports.MqttClientConnection = MqttClientConnection; //# sourceMappingURL=mqtt.js.map