UNPKG

react-mqtt-hooks

Version:
235 lines (226 loc) 6.7 kB
"use client"; // src/contexts/mqtt-connector.tsx import mqtt from "mqtt"; import { useEffect, useState } from "react"; // src/internals/utils.ts function parseMessage(message) { try { return JSON.parse(message.toString()); } catch { return message.toString(); } } // src/internals/mqtt-cache.ts var MqttCache = class _MqttCache { static instance; cache = /* @__PURE__ */ new Map(); mqttClient = null; customParser = void 0; activeSubscriptions = /* @__PURE__ */ new Set(); static getInstance() { if (!_MqttCache.instance) { _MqttCache.instance = new _MqttCache(); } return _MqttCache.instance; } setClient(client) { const previousClient = this.mqttClient; this.mqttClient = client; if (previousClient) { previousClient.removeAllListeners("message"); } if (client) { this.setupMessageListener(); this.resubscribeToActiveTopics(); client.on("connect", () => { this.resubscribeToActiveTopics(); }); } } setCustomParser(parser) { this.customParser = parser; } setupMessageListener() { if (!this.mqttClient) { return; } this.mqttClient.on("message", (topic, message) => { const parsedMsg = this.customParser ? this.customParser(message) : parseMessage(message); this.setData(topic, parsedMsg); this.notifySubscribers(topic, parsedMsg); }); } resubscribeToActiveTopics() { if (!this.mqttClient || !this.mqttClient.connected) return; this.activeSubscriptions.forEach((topic) => { this.mqttClient?.subscribe(topic); }); } notifySubscribers(topic, data) { const cacheItem = this.cache.get(topic); if (cacheItem && cacheItem.subscribers.size > 0) { cacheItem.subscribers.forEach((callback) => callback(data)); } } getData(topic) { return this.cache.get(topic)?.data; } setData(topic, data) { const item = this.cache.get(topic); if (item) { item.data = data; item.lastUpdated = (/* @__PURE__ */ new Date()).toLocaleString(); } else { this.cache.set(topic, { data, subscribers: /* @__PURE__ */ new Map(), lastUpdated: (/* @__PURE__ */ new Date()).toLocaleString() }); } } subscribe(topic, callback, subscriberId) { if (!this.cache.has(topic)) { this.cache.set(topic, { data: void 0, subscribers: /* @__PURE__ */ new Map(), lastUpdated: null }); } const item = this.cache.get(topic); item.subscribers.set(subscriberId, callback); if (item.subscribers.size === 1) { this.activeSubscriptions.add(topic); if (this.mqttClient?.connected) { this.mqttClient.subscribe(topic); } } return item.subscribers.size; } unsubscribe(topic, subscriberId) { const item = this.cache.get(topic); if (!item) return false; item.subscribers.delete(subscriberId); if (item.subscribers.size === 0) { this.activeSubscriptions.delete(topic); if (this.mqttClient?.connected) { this.mqttClient.unsubscribe(topic); return true; } } return false; } }; // src/contexts/mqtt-context.tsx import { createContext } from "react"; var MqttContext = createContext(null); // src/contexts/mqtt-connector.tsx import { jsx } from "react/jsx-runtime"; function MqttConnector({ children, url, options, customParser }) { const [mqttClient, setMqttClient] = useState(null); const cache = MqttCache.getInstance(); useEffect(() => { const client = mqtt.connect(url, options); setMqttClient(client); client.on("connect", () => { cache.setClient(client); cache.setCustomParser(customParser); }); return () => { cache.setClient(null); client.end(); }; }, [url, options, cache, customParser]); return ( // disable this rule to make it compatible with React 18 // eslint-disable-next-line react/no-context-provider /* @__PURE__ */ jsx(MqttContext.Provider, { value: mqttClient, children }) ); } // src/hooks/use-mqtt-client.ts import { useContext } from "react"; function useMqttClient() { const mqttClient = useContext(MqttContext); return mqttClient; } // src/hooks/use-topic.ts import { useEffect as useEffect2, useId, useState as useState2 } from "react"; function useTopic(topic) { const mqttClient = useMqttClient(); const cache = MqttCache.getInstance(); const subscriberId = useId(); const [data, setData] = useState2(() => { return topic ? cache.getData(topic) : void 0; }); useEffect2(() => { if (!topic) return; const handleDataUpdate = (newData) => { setData(newData); }; cache.subscribe(topic, handleDataUpdate, subscriberId); const cachedData = cache.getData(topic); if (cachedData !== void 0) { setData(cachedData); } return () => { cache.unsubscribe(topic, subscriberId); }; }, [mqttClient, topic, cache, subscriberId]); return data; } // src/hooks/use-topics.ts import { useEffect as useEffect3, useId as useId2, useMemo, useState as useState3 } from "react"; function useTopics(topics) { const mqttClient = useMqttClient(); const cache = MqttCache.getInstance(); const baseSubscriberId = useId2(); const normalizedTopics = useMemo(() => [...new Set(topics)].sort(), [topics]); const [dataMap, setDataMap] = useState3(() => { return normalizedTopics.reduce((acc, topic) => { const cachedData = cache.getData(topic); if (cachedData !== void 0) { acc[topic] = cachedData; } return acc; }, {}); }); useEffect3(() => { if (normalizedTopics.length === 0) return; const initialData = normalizedTopics.reduce((acc, topic) => { const cachedData = cache.getData(topic); if (cachedData !== void 0) { acc[topic] = cachedData; } return acc; }, {}); if (Object.keys(initialData).length > 0) { setDataMap((prev) => ({ ...prev, ...initialData })); } normalizedTopics.forEach((topic) => { const observerId = `${baseSubscriberId}-${topic}`; const handleDataUpdate = (newData) => { setDataMap((prev) => ({ ...prev, [topic]: newData })); }; cache.subscribe(topic, handleDataUpdate, observerId); }); return () => { normalizedTopics.forEach((topic) => { cache.unsubscribe(topic, `${baseSubscriberId}-${topic}`); }); }; }, [mqttClient, normalizedTopics, cache, baseSubscriberId]); return dataMap; } // src/index.ts var _cache = MqttCache.getInstance(); export { MqttConnector, _cache, useMqttClient, useTopic, useTopics }; //# sourceMappingURL=index.js.map