UNPKG

react-mqtt-hooks

Version:
1 lines 14.5 kB
{"version":3,"sources":["../src/index.ts","../src/contexts/mqtt-connector.tsx","../src/internals/utils.ts","../src/internals/mqtt-cache.ts","../src/contexts/mqtt-context.tsx","../src/hooks/use-mqtt-client.ts","../src/hooks/use-topic.ts","../src/hooks/use-topics.ts"],"sourcesContent":["\"use client\";\nimport { MqttConnector } from \"./contexts/mqtt-connector\";\nimport useMqttClient from \"./hooks/use-mqtt-client\";\nimport useTopic from \"./hooks/use-topic\";\nimport useTopics from \"./hooks/use-topics\";\nimport { MqttCache } from \"./internals/mqtt-cache\";\n\nconst _cache = MqttCache.getInstance();\n\nexport { _cache, MqttConnector, useMqttClient, useTopic, useTopics };\n","\"use client\";\nimport type { IClientOptions } from \"mqtt\";\nimport type { Buffer } from \"node:buffer\";\nimport type { MqttClientType } from \"./mqtt-context\";\nimport mqtt from \"mqtt\";\nimport { useEffect, useState } from \"react\";\nimport { MqttCache } from \"../internals/mqtt-cache\";\nimport { MqttContext } from \"./mqtt-context\";\n\ntype MqttConnectorProps = {\n children: React.ReactNode;\n url: string;\n options?: IClientOptions;\n customParser?: (message: Buffer) => any;\n};\n\nexport function MqttConnector({ children, url, options, customParser }: MqttConnectorProps) {\n const [mqttClient, setMqttClient] = useState<MqttClientType>(null);\n const cache = MqttCache.getInstance();\n useEffect(() => {\n const client = mqtt.connect(url, options);\n setMqttClient(client);\n client.on(\"connect\", () => {\n cache.setClient(client);\n cache.setCustomParser(customParser);\n });\n\n return () => {\n cache.setClient(null);\n client.end();\n };\n }, [url, options, cache, customParser]);\n return (\n // disable this rule to make it compatible with React 18\n // eslint-disable-next-line react/no-context-provider\n <MqttContext.Provider value={mqttClient}>\n {children}\n </MqttContext.Provider>\n );\n}\n","import type { Buffer } from \"node:buffer\";\n\nexport function parseMessage<T>(message: Buffer): T {\n try {\n return JSON.parse(message.toString()) as T;\n }\n catch {\n return message.toString() as T;\n }\n}\n","/* eslint-disable ts/no-unsafe-function-type */\nimport type { MqttClient } from \"mqtt\";\nimport type { Buffer } from \"node:buffer\";\nimport { parseMessage } from \"./utils\";\n\nexport type CacheItem<T> = {\n data: T;\n subscribers: Map<string, Function>; // subscriberId: callback\n lastUpdated: string | null;\n};\n\n// Global cache for MQTT messages\nexport class MqttCache {\n private static instance: MqttCache;\n private cache = new Map<string, CacheItem<any>>();\n private mqttClient: MqttClient | null = null;\n private customParser: ((message: Buffer) => any) | undefined = undefined;\n private activeSubscriptions = new Set<string>();\n\n static getInstance(): MqttCache {\n if (!MqttCache.instance) {\n MqttCache.instance = new MqttCache();\n }\n return MqttCache.instance;\n }\n\n setClient(client: MqttClient | null) {\n const previousClient = this.mqttClient;\n this.mqttClient = client;\n\n // Clean up previous client\n if (previousClient) {\n previousClient.removeAllListeners(\"message\");\n }\n\n if (client) {\n this.setupMessageListener();\n\n // Resubscribe to all active topics when client changes or reconnects\n this.resubscribeToActiveTopics();\n\n client.on(\"connect\", () => {\n this.resubscribeToActiveTopics();\n });\n }\n }\n\n setCustomParser(parser: ((message: Buffer) => any) | undefined) {\n this.customParser = parser;\n }\n\n private setupMessageListener() {\n if (!this.mqttClient) {\n return;\n }\n this.mqttClient.on(\"message\", (topic: string, message: Buffer) => {\n const parsedMsg = this.customParser ? this.customParser(message) : parseMessage(message);\n this.setData(topic, parsedMsg);\n this.notifySubscribers(topic, parsedMsg);\n });\n }\n\n private resubscribeToActiveTopics() {\n if (!this.mqttClient || !this.mqttClient.connected)\n return;\n\n this.activeSubscriptions.forEach((topic) => {\n this.mqttClient?.subscribe(topic);\n });\n }\n\n private notifySubscribers(topic: string, data: any) {\n const cacheItem = this.cache.get(topic);\n if (cacheItem && cacheItem.subscribers.size > 0) {\n cacheItem.subscribers.forEach(callback => callback(data));\n }\n }\n\n getData<T>(topic: string) {\n return this.cache.get(topic)?.data as T | undefined;\n }\n\n private setData<T>(topic: string, data: T) {\n const item = this.cache.get(topic);\n if (item) {\n item.data = data;\n item.lastUpdated = new Date().toLocaleString();\n }\n else {\n this.cache.set(topic, {\n data,\n subscribers: new Map(),\n lastUpdated: new Date().toLocaleString(),\n });\n }\n }\n\n subscribe(topic: string, callback: Function, subscriberId: string): number {\n if (!this.cache.has(topic)) {\n this.cache.set(topic, {\n data: undefined,\n subscribers: new Map(),\n lastUpdated: null,\n });\n }\n\n const item = this.cache.get(topic)!;\n item.subscribers.set(subscriberId, callback);\n\n // If this is the first subscriber, subscribe to MQTT topic\n if (item.subscribers.size === 1) {\n this.activeSubscriptions.add(topic);\n if (this.mqttClient?.connected) {\n this.mqttClient.subscribe(topic);\n }\n }\n\n return item.subscribers.size;\n }\n\n unsubscribe(topic: string, subscriberId: string): boolean {\n const item = this.cache.get(topic);\n if (!item)\n return false;\n\n item.subscribers.delete(subscriberId);\n\n // If no subscribers left, unsubscribe from MQTT topic\n if (item.subscribers.size === 0) {\n this.activeSubscriptions.delete(topic);\n\n if (this.mqttClient?.connected) {\n this.mqttClient.unsubscribe(topic);\n return true;\n }\n }\n\n return false;\n }\n}\n","import type { MqttClient } from \"mqtt\";\nimport { createContext } from \"react\";\n\nexport type MqttClientType = MqttClient | null;\nexport const MqttContext = createContext<MqttClientType>(null);\n","\"use client\";\nimport type { MqttClientType } from \"../contexts/mqtt-context\";\nimport { useContext } from \"react\";\nimport { MqttContext } from \"../contexts/mqtt-context\";\n\nexport default function useMqttClient(): MqttClientType {\n const mqttClient = useContext(MqttContext);\n return mqttClient;\n}\n","\"use client\";\nimport { useEffect, useId, useState } from \"react\";\nimport { MqttCache } from \"../internals/mqtt-cache\";\nimport useMqttClient from \"./use-mqtt-client\";\n\nexport default function useTopic<T = any>(topic: string | null) {\n const mqttClient = useMqttClient();\n const cache = MqttCache.getInstance();\n const subscriberId = useId(); // Unique ID for this component instance\n\n const [data, setData] = useState<T | undefined>(() => {\n // get data from cache\n return topic ? cache.getData<T>(topic) : undefined;\n });\n\n useEffect(() => {\n if (!topic)\n return;\n\n const handleDataUpdate = (newData: T) => {\n setData(newData);\n };\n\n // Combined subscribe and addObserver\n cache.subscribe(topic, handleDataUpdate, subscriberId);\n\n // Check again for initial data (might have been updated since state init)\n const cachedData = cache.getData<T>(topic);\n if (cachedData !== undefined) {\n setData(cachedData);\n }\n\n return () => {\n // Combined unsubscribe and removeObserver\n cache.unsubscribe(topic, subscriberId);\n };\n }, [mqttClient, topic, cache, subscriberId]);\n\n return data;\n}\n","\"use client\";\nimport { useEffect, useId, useMemo, useState } from \"react\";\nimport { MqttCache } from \"../internals/mqtt-cache\";\nimport useMqttClient from \"./use-mqtt-client\";\n\nexport default function useTopics(topics: string[]) {\n const mqttClient = useMqttClient();\n const cache = MqttCache.getInstance();\n const baseSubscriberId = useId(); // Base unique ID for this component instance\n\n const normalizedTopics = useMemo(() => [...new Set(topics)].sort(), [topics]);\n\n // get data from cache when initializing\n const [dataMap, setDataMap] = useState<Record<string, any>>(() => {\n return normalizedTopics.reduce((acc, topic) => {\n const cachedData = cache.getData(topic);\n if (cachedData !== undefined) {\n acc[topic] = cachedData;\n }\n return acc;\n }, {} as Record<string, any>);\n });\n\n useEffect(() => {\n if (normalizedTopics.length === 0)\n return;\n\n // Initial data refresh\n const initialData = normalizedTopics.reduce((acc, topic) => {\n const cachedData = cache.getData(topic);\n if (cachedData !== undefined) {\n acc[topic] = cachedData;\n }\n return acc;\n }, {} as Record<string, any>);\n\n if (Object.keys(initialData).length > 0) {\n setDataMap(prev => ({ ...prev, ...initialData }));\n }\n\n // Subscribe to all topics\n normalizedTopics.forEach((topic) => {\n const observerId = `${baseSubscriberId}-${topic}`;\n const handleDataUpdate = (newData: any) => {\n setDataMap(prev => ({ ...prev, [topic]: newData }));\n };\n\n // Combined subscribe and addObserver\n cache.subscribe(topic, handleDataUpdate, observerId);\n });\n\n return () => {\n normalizedTopics.forEach((topic) => {\n // Combined unsubscribe and removeObserver\n cache.unsubscribe(topic, `${baseSubscriberId}-${topic}`);\n });\n };\n }, [mqttClient, normalizedTopics, cache, baseSubscriberId]);\n\n return dataMap;\n}\n"],"mappings":"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","names":["import_react","mqtt","import_react","import_react","import_react"]}