@aws-amplify/pubsub
Version:
Pubsub category of aws-amplify
396 lines • 20.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
var Paho = tslib_1.__importStar(require("../vendor/paho-mqtt"));
var uuid_1 = require("uuid");
var zen_observable_ts_1 = tslib_1.__importDefault(require("zen-observable-ts"));
var PubSubProvider_1 = require("./PubSubProvider");
var PubSub_1 = require("../types/PubSub");
var core_1 = require("@aws-amplify/core");
var ConnectionStateMonitor_1 = require("../utils/ConnectionStateMonitor");
var ReconnectionMonitor_1 = require("../utils/ReconnectionMonitor");
var constants_1 = require("./constants");
var logger = new core_1.ConsoleLogger('MqttOverWSProvider');
function mqttTopicMatch(filter, topic) {
var filterArray = filter.split('/');
var length = filterArray.length;
var topicArray = topic.split('/');
for (var i = 0; i < length; ++i) {
var left = filterArray[i];
var right = topicArray[i];
if (left === '#')
return topicArray.length >= length;
if (left !== '+' && left !== right)
return false;
}
return length === topicArray.length;
}
exports.mqttTopicMatch = mqttTopicMatch;
var ClientsQueue = /** @class */ (function () {
function ClientsQueue() {
this.promises = new Map();
}
ClientsQueue.prototype.get = function (clientId, clientFactory) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var cachedPromise, newPromise;
var _this = this;
return tslib_1.__generator(this, function (_a) {
cachedPromise = this.promises.get(clientId);
if (cachedPromise)
return [2 /*return*/, cachedPromise];
if (clientFactory) {
newPromise = clientFactory(clientId);
this.promises.set(clientId, newPromise);
newPromise.catch(function () { return _this.promises.delete(clientId); });
return [2 /*return*/, newPromise];
}
return [2 /*return*/, undefined];
});
});
};
Object.defineProperty(ClientsQueue.prototype, "allClients", {
get: function () {
return Array.from(this.promises.keys());
},
enumerable: true,
configurable: true
});
ClientsQueue.prototype.remove = function (clientId) {
this.promises.delete(clientId);
};
return ClientsQueue;
}());
var dispatchPubSubEvent = function (event, data, message) {
core_1.Hub.dispatch('pubsub', { event: event, data: data, message: message }, 'PubSub', constants_1.AMPLIFY_SYMBOL);
};
var topicSymbol = typeof Symbol !== 'undefined' ? Symbol('topic') : '@@topic';
var MqttOverWSProvider = /** @class */ (function (_super) {
tslib_1.__extends(MqttOverWSProvider, _super);
function MqttOverWSProvider(options) {
if (options === void 0) { options = {}; }
var _this = _super.call(this, tslib_1.__assign(tslib_1.__assign({}, options), { clientId: options.clientId || uuid_1.v4() })) || this;
_this._clientsQueue = new ClientsQueue();
_this.connectionStateMonitor = new ConnectionStateMonitor_1.ConnectionStateMonitor();
_this.reconnectionMonitor = new ReconnectionMonitor_1.ReconnectionMonitor();
_this._topicObservers = new Map();
_this._clientIdObservers = new Map();
// Monitor the connection health state and pass changes along to Hub
_this.connectionStateMonitor.connectionStateObservable.subscribe(function (connectionStateChange) {
dispatchPubSubEvent(constants_1.CONNECTION_STATE_CHANGE, {
provider: _this,
connectionState: connectionStateChange,
}, "Connection state is " + connectionStateChange);
_this.connectionState = connectionStateChange;
// Trigger reconnection when the connection is disrupted
if (connectionStateChange === PubSub_1.ConnectionState.ConnectionDisrupted) {
_this.reconnectionMonitor.record(ReconnectionMonitor_1.ReconnectEvent.START_RECONNECT);
}
else if (connectionStateChange !== PubSub_1.ConnectionState.Connecting) {
// Trigger connected to halt reconnection attempts
_this.reconnectionMonitor.record(ReconnectionMonitor_1.ReconnectEvent.HALT_RECONNECT);
}
});
return _this;
}
Object.defineProperty(MqttOverWSProvider.prototype, "clientId", {
get: function () {
return this.options.clientId;
},
enumerable: true,
configurable: true
});
Object.defineProperty(MqttOverWSProvider.prototype, "endpoint", {
get: function () {
return Promise.resolve(this.options.aws_pubsub_endpoint);
},
enumerable: true,
configurable: true
});
Object.defineProperty(MqttOverWSProvider.prototype, "clientsQueue", {
get: function () {
return this._clientsQueue;
},
enumerable: true,
configurable: true
});
Object.defineProperty(MqttOverWSProvider.prototype, "isSSLEnabled", {
get: function () {
return !this.options['aws_appsync_dangerously_connect_to_http_endpoint_for_testing'];
},
enumerable: true,
configurable: true
});
MqttOverWSProvider.prototype.getProviderName = function () {
return 'MqttOverWSProvider';
};
MqttOverWSProvider.prototype.onDisconnect = function (_a) {
var clientId = _a.clientId, errorCode = _a.errorCode, args = tslib_1.__rest(_a, ["clientId", "errorCode"]);
if (errorCode !== 0) {
logger.warn(clientId, JSON.stringify(tslib_1.__assign({ errorCode: errorCode }, args), null, 2));
if (!clientId) {
return;
}
var clientIdObservers = this._clientIdObservers.get(clientId);
if (!clientIdObservers) {
return;
}
this.disconnect(clientId);
}
};
MqttOverWSProvider.prototype.newClient = function (_a) {
var url = _a.url, clientId = _a.clientId;
return tslib_1.__awaiter(this, void 0, void 0, function () {
var client, connected;
var _this = this;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
case 0:
logger.debug('Creating new MQTT client', clientId);
this.connectionStateMonitor.record(ConnectionStateMonitor_1.CONNECTION_CHANGE.OPENING_CONNECTION);
client = new Paho.Client(url, clientId);
client.onMessageArrived = function (_a) {
var topic = _a.destinationName, msg = _a.payloadString;
_this._onMessage(topic, msg);
};
client.onConnectionLost = function (_a) {
var errorCode = _a.errorCode, args = tslib_1.__rest(_a, ["errorCode"]);
_this.onDisconnect(tslib_1.__assign({ clientId: clientId, errorCode: errorCode }, args));
_this.connectionStateMonitor.record(ConnectionStateMonitor_1.CONNECTION_CHANGE.CLOSED);
};
return [4 /*yield*/, new Promise(function (resolve, reject) {
client.connect({
useSSL: _this.isSSLEnabled,
mqttVersion: 3,
onSuccess: function () { return resolve(true); },
onFailure: function () {
if (clientId)
_this._clientsQueue.remove(clientId);
_this.connectionStateMonitor.record(ConnectionStateMonitor_1.CONNECTION_CHANGE.CLOSED);
resolve(false);
},
});
})];
case 1:
connected = _b.sent();
if (connected) {
this.connectionStateMonitor.record(ConnectionStateMonitor_1.CONNECTION_CHANGE.CONNECTION_ESTABLISHED);
}
return [2 /*return*/, client];
}
});
});
};
MqttOverWSProvider.prototype.connect = function (clientId, options) {
if (options === void 0) { options = {}; }
return tslib_1.__awaiter(this, void 0, void 0, function () {
var _this = this;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.clientsQueue.get(clientId, function (clientId) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
var client;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.newClient(tslib_1.__assign(tslib_1.__assign({}, options), { clientId: clientId }))];
case 1:
client = _a.sent();
if (client) {
// Once connected, subscribe to all topics registered observers
this._topicObservers.forEach(function (_value, key) {
client.subscribe(key);
});
}
return [2 /*return*/, client];
}
});
}); })];
case 1: return [2 /*return*/, _a.sent()];
}
});
});
};
MqttOverWSProvider.prototype.disconnect = function (clientId) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var client;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.clientsQueue.get(clientId)];
case 1:
client = _a.sent();
if (client && client.isConnected()) {
client.disconnect();
}
this.clientsQueue.remove(clientId);
this.connectionStateMonitor.record(ConnectionStateMonitor_1.CONNECTION_CHANGE.CLOSED);
return [2 /*return*/];
}
});
});
};
MqttOverWSProvider.prototype.publish = function (topics, msg) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var targetTopics, message, client;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
targetTopics = [].concat(topics);
message = JSON.stringify(msg);
return [4 /*yield*/, this.clientsQueue.get(this.clientId)];
case 1:
client = _a.sent();
if (client) {
logger.debug('Publishing to topic(s)', targetTopics.join(','), message);
targetTopics.forEach(function (topic) { return client.send(topic, message); });
}
else {
logger.debug('Publishing to topic(s) failed', targetTopics.join(','), message);
}
return [2 /*return*/];
}
});
});
};
MqttOverWSProvider.prototype._onMessage = function (topic, msg) {
try {
var matchedTopicObservers_1 = [];
this._topicObservers.forEach(function (observerForTopic, observerTopic) {
if (mqttTopicMatch(observerTopic, topic)) {
matchedTopicObservers_1.push(observerForTopic);
}
});
var parsedMessage_1 = JSON.parse(msg);
if (typeof parsedMessage_1 === 'object') {
// @ts-ignore
parsedMessage_1[topicSymbol] = topic;
}
matchedTopicObservers_1.forEach(function (observersForTopic) {
observersForTopic.forEach(function (observer) { return observer.next(parsedMessage_1); });
});
}
catch (error) {
logger.warn('Error handling message', error, msg);
}
};
MqttOverWSProvider.prototype.subscribe = function (topics, options) {
var _this = this;
if (options === void 0) { options = {}; }
var targetTopics = [].concat(topics);
logger.debug('Subscribing to topic(s)', targetTopics.join(','));
var reconnectSubscription;
return new zen_observable_ts_1.default(function (observer) {
targetTopics.forEach(function (topic) {
// this._topicObservers is used to notify the observers according to the topic received on the message
var observersForTopic = _this._topicObservers.get(topic);
if (!observersForTopic) {
observersForTopic = new Set();
_this._topicObservers.set(topic, observersForTopic);
}
observersForTopic.add(observer);
});
var _a = options.clientId, clientId = _a === void 0 ? _this.clientId : _a;
// this._clientIdObservers is used to close observers when client gets disconnected
var observersForClientId = _this._clientIdObservers.get(clientId);
if (!observersForClientId) {
observersForClientId = new Set();
}
if (observersForClientId) {
observersForClientId.add(observer);
_this._clientIdObservers.set(clientId, observersForClientId);
}
(function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
var getClient;
var _this = this;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
getClient = function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
var _a, url, _b, client_1, e_1;
return tslib_1.__generator(this, function (_c) {
switch (_c.label) {
case 0:
_c.trys.push([0, 5, , 6]);
_a = options.url;
if (!(_a === void 0)) return [3 /*break*/, 2];
return [4 /*yield*/, this.endpoint];
case 1:
_b = _c.sent();
return [3 /*break*/, 3];
case 2:
_b = _a;
_c.label = 3;
case 3:
url = _b;
return [4 /*yield*/, this.connect(clientId, { url: url })];
case 4:
client_1 = _c.sent();
if (client_1 !== undefined) {
targetTopics.forEach(function (topic) {
client_1.subscribe(topic);
});
}
return [3 /*break*/, 6];
case 5:
e_1 = _c.sent();
logger.debug('Error forming connection', e_1);
return [3 /*break*/, 6];
case 6: return [2 /*return*/];
}
});
}); };
// Establish the initial connection
return [4 /*yield*/, getClient()];
case 1:
// Establish the initial connection
_a.sent();
// Add an observable to the reconnection list to manage reconnection for this subscription
reconnectSubscription = new zen_observable_ts_1.default(function (observer) {
_this.reconnectionMonitor.addObserver(observer);
}).subscribe(function () {
getClient();
});
return [2 /*return*/];
}
});
}); })();
return function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
var client;
var _this = this;
var _a, _b;
return tslib_1.__generator(this, function (_c) {
switch (_c.label) {
case 0: return [4 /*yield*/, this.clientsQueue.get(clientId)];
case 1:
client = _c.sent();
reconnectSubscription === null || reconnectSubscription === void 0 ? void 0 : reconnectSubscription.unsubscribe();
if (client) {
(_a = this._clientIdObservers.get(clientId)) === null || _a === void 0 ? void 0 : _a.delete(observer);
// No more observers per client => client not needed anymore
if (((_b = this._clientIdObservers.get(clientId)) === null || _b === void 0 ? void 0 : _b.size) === 0) {
this.disconnect(clientId);
this.connectionStateMonitor.record(ConnectionStateMonitor_1.CONNECTION_CHANGE.CLOSING_CONNECTION);
this._clientIdObservers.delete(clientId);
}
targetTopics.forEach(function (topic) {
var observersForTopic = _this._topicObservers.get(topic) ||
new Set();
observersForTopic.delete(observer);
// if no observers exists for the topic, topic should be removed
if (observersForTopic.size === 0) {
_this._topicObservers.delete(topic);
if (client.isConnected()) {
client.unsubscribe(topic);
}
}
});
}
return [2 /*return*/, null];
}
});
}); };
});
};
return MqttOverWSProvider;
}(PubSubProvider_1.AbstractPubSubProvider));
exports.MqttOverWSProvider = MqttOverWSProvider;
//# sourceMappingURL=MqttOverWSProvider.js.map