import { useState, useEffect } from 'react'; import mqtt from 'mqtt'; import { getMqttUrl, getMqttCredentials, MQTT_TOPIC_COMMAND, MQTT_TOPIC_TICK } from '../config'; /* ========================================================= MQTT 客户端(mqtt.js)—— 后端控制通道 订阅 opc/display/command(页面/媒体/卡片控制)与 opc/dashboard/tick(数据快照) 断线自动重连;未连接时静默降级(页面仍可本地操作) ========================================================= */ let client = null; let status = 'connecting'; // connecting | connected | offline let clientId = null; const listeners = new Set(); function notifyStatus() { window.dispatchEvent(new CustomEvent('dpm:mqtt-status', { detail: { status } })); } /* 每个连接生成全局唯一 client_id(独立客户端 ID 铁律): 优先 crypto.randomUUID(WebView2/现代浏览器),回退时间戳+双段高熵随机 */ function makeClientId() { try { if (window.crypto && typeof window.crypto.randomUUID === 'function') { return `dpm-screen-${crypto.randomUUID().replace(/-/g, '')}`; } } catch { /* 继续回退 */ } return `dpm-screen-${Date.now().toString(36)}-${Math.random().toString(16).slice(2, 12)}-${Math.random().toString(16).slice(2, 10)}`; } export function connectMqtt() { if (client) return; try { // 独立客户端 ID:避免多屏/多实例/多标签页同 ID 被 broker 互相踢下线 clientId = makeClientId(); // 连接时惰性读取 MQTT 地址/账号:打包部署时 main.jsx 引导已从后端 /api/config 覆盖 const { username, password } = getMqttCredentials(); client = mqtt.connect(getMqttUrl(), { reconnectPeriod: 3000, connectTimeout: 8000, clientId, clean: true, username: username || undefined, password: password || undefined, }); client.on('connect', () => { status = 'connected'; notifyStatus(); client.subscribe([MQTT_TOPIC_COMMAND, MQTT_TOPIC_TICK], { qos: 1 }); }); client.on('reconnect', () => { status = 'connecting'; notifyStatus(); }); client.on('close', () => { status = 'offline'; notifyStatus(); }); client.on('error', () => { status = 'offline'; notifyStatus(); }); client.on('message', (topic, payload) => { let data = null; try { data = JSON.parse(payload.toString()); } catch { return; } listeners.forEach((fn) => fn(topic, data)); }); } catch (e) { status = 'offline'; notifyStatus(); } } export function getMqttStatus() { return status; } /** 当前连接的稳定 client_id */ export function getClientId() { return clientId; } /** 发布消息(大屏心跳等) */ export function publishMqtt(topic, payload) { if (!client || status !== 'connected') return false; try { client.publish(topic, JSON.stringify(payload), { qos: 1 }); return true; } catch (e) { return false; } } /** 订阅 MQTT 消息,返回取消订阅函数 */ export function onMqttMessage(fn) { listeners.add(fn); return () => listeners.delete(fn); } /** React Hook:MQTT 连接状态 */ export function useMqttStatus() { const [s, setS] = useState(status); useEffect(() => { connectMqtt(); const on = (e) => setS(e.detail.status); window.addEventListener('dpm:mqtt-status', on); return () => window.removeEventListener('dpm:mqtt-status', on); }, []); return s; }