All files / common subscription-manager.js

100% Statements 93/93
100% Branches 54/54
100% Functions 18/18
100% Lines 88/88

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140    7x 7x 7x 7x     40x 40x 40x       915x       95x     51x 51x     95x 92x 92x 92x 92x 92x 276x 276x 276x 228x 228x   276x 276x   92x 92x 92x 85x 85x   7x     25x 25x 1x   24x 24x           71x 71x 231x 231x 62x 39x   62x   169x   71x 71x     1067x 1067x 1x   1067x 1x   1067x     4960x 1411x 1410x   3549x 3549x 3549x 2096x   3546x 3546x 1799x       40x 40x 40x 200x 40x 947x 4x     36x 1x   35x 915x 915x 915x 915x 915x 915x   35x 1067x 1067x           35x 95x 95x 95x 95x 95x 469x       7x  
"use strict";
/*! Copyright (c) 2021 Siemens AG. Licensed under the MIT License. */
Object.defineProperty(exports, "__esModule", { value: true });
exports.SubscriptionManager = void 0;
const uuid_1 = require("uuid");
const mqtt_utils_1 = require("./mqtt-utils");
class SubscriptionManager {
    constructor(topicFormat, interfaceName, protocolVersion) {
        this._subscriptions = new Map();
        this._subscriptionIds = new Map();
        this._compileMqttTopic(topicFormat, interfaceName, protocolVersion);
    }
    getMqttTopic(topic, subject) {
        var _a, _b;
        return this._constructMqttTopic((_a = subject.manufacturer) !== null && _a !== void 0 ? _a : "+", (_b = subject.serialNumber) !== null && _b !== void 0 ? _b : "+", topic !== null && topic !== void 0 ? topic : "+");
    }
    getMqttTopicUtf8Length(topic, subject) {
        var _a, _b;
        return this._getMqttTopicUtf8Length((_a = subject.manufacturer) !== null && _a !== void 0 ? _a : "+", (_b = subject.serialNumber) !== null && _b !== void 0 ? _b : "+", topic !== null && topic !== void 0 ? topic : "+");
    }
    clear() {
        this._subscriptions.clear();
        this._subscriptionIds.clear();
    }
    add(topic, subject, handler) {
        (0, mqtt_utils_1.assertMqttTopicUtf8Count)(this.getMqttTopicUtf8Length(topic, subject));
        const id = (0, uuid_1.v4)();
        const path = [subject.manufacturer, subject.serialNumber, topic];
        let pathIndex = path.length - 1;
        let map = this._subscriptions;
        while (pathIndex !== -1) {
            const key = path[pathIndex];
            let value = map.get(key);
            if (value === undefined) {
                value = new Map();
                map.set(key, value);
            }
            map = value;
            pathIndex--;
        }
        this._subscriptionIds.set(id, map);
        map.set(id, handler);
        if (map.size === 1) {
            const mqttTopic = map["mqttTopic"] = this.getMqttTopic(topic, subject);
            return { id, mqttTopic, requiresSubscribe: true };
        }
        return { id, mqttTopic: map["mqttTopic"], requiresSubscribe: false };
    }
    remove(id) {
        const subIdsMap = this._subscriptionIds.get(id);
        if (subIdsMap === undefined || !subIdsMap.has(id)) {
            return undefined;
        }
        subIdsMap.delete(id);
        return {
            mqttTopic: subIdsMap["mqttTopic"],
            requiresUnsubscribe: subIdsMap.size === 0,
        };
    }
    getAll() {
        const mqttTopics = [];
        const walk = (map) => {
            const mqttTopic = map["mqttTopic"];
            if (mqttTopic !== undefined) {
                if (map.size > 0) {
                    mqttTopics.push(mqttTopic);
                }
                return;
            }
            map.forEach(subMap => walk(subMap));
        };
        walk(this._subscriptions);
        return mqttTopics;
    }
    find(mqttTopic, subject) {
        const path = this._deconstructMqttTopic(mqttTopic);
        if (path[0] === undefined) {
            path[0] = subject.manufacturer;
        }
        if (path[1] === undefined) {
            path[1] = subject.serialNumber;
        }
        return [this._findInternal(this._subscriptions, path, path.length - 1), path[2]];
    }
    *_findInternal(map, path, pathIndex) {
        if (pathIndex === -1) {
            yield* map.entries();
            return;
        }
        const key = path[pathIndex];
        let value = map.get(key);
        if (value !== undefined) {
            yield* this._findInternal(value, path, pathIndex - 1);
        }
        value = map.get(undefined);
        if (value !== undefined) {
            yield* this._findInternal(value, path, pathIndex - 1);
        }
    }
    _compileMqttTopic(topicFormat, interfaceName, protocolVersion) {
        const majorVersion = `v${protocolVersion.substring(0, protocolVersion.indexOf("."))}`;
        const placeholders = ["%interfaceName%", "%majorVersion%", "%manufacturer%", "%serialNumber%", "%topic%"];
        const levels = topicFormat.split("/");
        const indices = placeholders.map(p => levels.indexOf(p));
        for (let i = 0; i < indices.length; i++) {
            if (levels.some((l, li) => l.search(placeholders[i]) !== -1 && li !== indices[i])) {
                throw new Error(`Invalid topic format: ${placeholders[i]} placeholder not a complete topic level or specified multiple times`);
            }
        }
        if (indices[4] === -1) {
            throw new Error("Invalid topic format: %topic% placeholder is missing");
        }
        this._constructMqttTopic = (manufacturer, serialNumber, topic) => {
            levels[indices[0]] = interfaceName;
            levels[indices[1]] = majorVersion;
            levels[indices[2]] = manufacturer;
            levels[indices[3]] = serialNumber;
            levels[indices[4]] = topic;
            return levels.join("/");
        };
        this._deconstructMqttTopic = (mqttTopic) => {
            const mqttLevels = mqttTopic.split("/");
            return [
                mqttLevels[indices[2]],
                mqttLevels[indices[3]],
                mqttLevels[indices[4]],
            ];
        };
        this._getMqttTopicUtf8Length = (manufacturer, serialNumber, topic) => {
            levels[indices[0]] = interfaceName;
            levels[indices[1]] = majorVersion;
            levels[indices[2]] = manufacturer;
            levels[indices[3]] = serialNumber;
            levels[indices[4]] = topic;
            return levels.reduce((prev, cur, index, arr) => prev + (0, mqtt_utils_1.getUtf8BytesCount)(cur) + (index === arr.length - 1 ? 0 : 1), 0);
        };
    }
}
exports.SubscriptionManager = SubscriptionManager;