Files
mai-bot/dashboard/dist/assets/unified-ws-CBnrIqHW.js

2 lines
10 KiB
JavaScript

import{ax as d,aE as S,x as g}from"./index-CuOHsLf7.js";const i={THEME:"maibot-ui-theme",ACCENT_COLOR:"accent-color",ENABLE_ANIMATIONS:"maibot-animations",ENABLE_WAVES_BACKGROUND:"maibot-waves-background",LOG_CACHE_SIZE:"maibot-log-cache-size",LOG_AUTO_SCROLL:"maibot-log-auto-scroll",LOG_FONT_SIZE:"maibot-log-font-size",LOG_LINE_SPACING:"maibot-log-line-spacing",DATA_SYNC_INTERVAL:"maibot-data-sync-interval",WS_RECONNECT_INTERVAL:"maibot-ws-reconnect-interval",WS_MAX_RECONNECT_ATTEMPTS:"maibot-ws-max-reconnect-attempts",COMPLETED_TOURS:"maibot-completed-tours"},l={theme:"system",accentColor:d,enableAnimations:!0,enableWavesBackground:!0,logCacheSize:1e3,logAutoScroll:!0,logFontSize:"xs",logLineSpacing:4,dataSyncInterval:30,wsReconnectInterval:3e3,wsMaxReconnectAttempts:10};function c(o){const e=h(o),n=localStorage.getItem(e);if(n===null)return l[o];const t=l[o];if(typeof t=="boolean")return n==="true";if(typeof t=="number"){const s=parseFloat(n);return isNaN(s)?t:s}return n}function u(o,e){const n=h(o);localStorage.setItem(n,String(e)),window.dispatchEvent(new CustomEvent("maibot-settings-change",{detail:{key:o,value:e}}))}function m(){return{theme:c("theme"),accentColor:c("accentColor"),enableAnimations:c("enableAnimations"),enableWavesBackground:c("enableWavesBackground"),logCacheSize:c("logCacheSize"),logAutoScroll:c("logAutoScroll"),logFontSize:c("logFontSize"),logLineSpacing:c("logLineSpacing"),dataSyncInterval:c("dataSyncInterval"),wsReconnectInterval:c("wsReconnectInterval"),wsMaxReconnectAttempts:c("wsMaxReconnectAttempts")}}function A(){const o=m(),e=localStorage.getItem(i.COMPLETED_TOURS),n=e?JSON.parse(e):[];return{...o,completedTours:n}}function R(o){const e=[],n=[];for(const[t,s]of Object.entries(o)){if(t==="completedTours"){Array.isArray(s)?(localStorage.setItem(i.COMPLETED_TOURS,JSON.stringify(s)),e.push("completedTours")):n.push("completedTours");continue}if(t in l){const r=t,a=l[r];if(typeof s==typeof a){if(r==="theme"&&!["light","dark","system"].includes(s)){n.push(t);continue}if(r==="logFontSize"&&!["xs","sm","base"].includes(s)){n.push(t);continue}u(r,s),e.push(t)}else n.push(t)}else n.push(t)}return{success:e.length>0,imported:e,skipped:n}}function T(){for(const o of Object.keys(l))u(o,l[o]);localStorage.removeItem(i.COMPLETED_TOURS),window.dispatchEvent(new CustomEvent("maibot-settings-reset"))}function y(){const o=[],e=[],n=[];for(let t=0;t<localStorage.length;t++){const s=localStorage.key(t);s&&(s.startsWith("maibot")||s.startsWith("accent-color"))&&n.push(s)}for(const t of n)localStorage.removeItem(t),o.push(t);return{clearedKeys:o,preservedKeys:e}}function I(){let o=0;const e=[];for(let n=0;n<localStorage.length;n++){const t=localStorage.key(n);if(t){const s=localStorage.getItem(t)||"",r=(t.length+s.length)*2;o+=r,e.push({key:t,size:r})}}return e.sort((n,t)=>t.size-n.size),{used:o,items:localStorage.length,details:e}}function C(o){if(o===0)return"0 B";const e=1024,n=["B","KB","MB"],t=Math.floor(Math.log(o)/Math.log(e));return parseFloat((o/Math.pow(e,t)).toFixed(2))+" "+n[t]}function h(o){return{theme:i.THEME,accentColor:i.ACCENT_COLOR,enableAnimations:i.ENABLE_ANIMATIONS,enableWavesBackground:i.ENABLE_WAVES_BACKGROUND,logCacheSize:i.LOG_CACHE_SIZE,logAutoScroll:i.LOG_AUTO_SCROLL,logFontSize:i.LOG_FONT_SIZE,logLineSpacing:i.LOG_LINE_SPACING,dataSyncInterval:i.DATA_SYNC_INTERVAL,wsReconnectInterval:i.WS_RECONNECT_INTERVAL,wsMaxReconnectAttempts:i.WS_MAX_RECONNECT_ATTEMPTS}[o]}function f(o){return o.op==="response"}function w(o){return o.op==="event"}async function p(){try{const o=await g("/api/webui/ws-token",{method:"GET",credentials:"include"});if(!o.ok)return null;const e=await o.json();return e.success&&e.token?e.token:null}catch(o){return console.error("获取统一 WebSocket token 失败:",o),null}}class b{heartbeatIntervalMs=3e4;heartbeatTimeoutMs=9e4;connectPromise=null;connectionListeners=new Set;eventListeners=new Set;hasConnectedOnce=!1;heartbeatIntervalId=null;lastPongAt=0;manualDisconnect=!1;pendingRequests=new Map;reconnectAttempts=0;reconnectListeners=new Set;reconnectTimeout=null;requestCounter=0;status="idle";statusListeners=new Set;subscriptions=new Map;ws=null;getReconnectDelay(){const e=c("wsReconnectInterval");return Math.min(e*Math.max(this.reconnectAttempts,1),3e4)}getMaxReconnectAttempts(){return c("wsMaxReconnectAttempts")}getSubscriptionKey(e,n){return`${e}:${n}`}nextRequestId(){return this.requestCounter+=1,`ws-${Date.now()}-${this.requestCounter}`}setStatus(e){if(this.status===e)return;this.status=e,this.statusListeners.forEach(t=>{try{t(e)}catch(s){console.error("WebSocket 状态监听器执行失败:",s)}});const n=e==="connected";this.connectionListeners.forEach(t=>{try{t(n)}catch(s){console.error("WebSocket 连接监听器执行失败:",s)}})}stopHeartbeat(){this.heartbeatIntervalId!==null&&(clearInterval(this.heartbeatIntervalId),this.heartbeatIntervalId=null)}startHeartbeat(){this.stopHeartbeat(),this.heartbeatIntervalId=window.setInterval(()=>{if(this.ws?.readyState!==WebSocket.OPEN)return;const e=Date.now();if(this.lastPongAt>0&&e-this.lastPongAt>this.heartbeatTimeoutMs){console.warn("统一 WebSocket 心跳超时,准备重连"),this.restart().catch(n=>{console.error("统一 WebSocket 心跳重连失败:",n)});return}this.ws.send(JSON.stringify({op:"ping"}))},this.heartbeatIntervalMs)}clearReconnectTimer(){this.reconnectTimeout!==null&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null)}rejectPendingRequests(e){this.pendingRequests.forEach((n,t)=>{clearTimeout(n.timeoutId),n.reject(e),this.pendingRequests.delete(t)})}scheduleReconnect(){if(this.manualDisconnect)return;if(this.reconnectAttempts>=this.getMaxReconnectAttempts()){console.warn(`统一 WebSocket 达到最大重连次数 (${this.getMaxReconnectAttempts()}),停止重连`);return}this.reconnectAttempts+=1;const e=this.getReconnectDelay();this.clearReconnectTimer(),this.reconnectTimeout=window.setTimeout(()=>{this.connect().catch(n=>{console.error("统一 WebSocket 重连失败:",n)})},e)}async createWebSocketUrl(){const e=await S(),n=await p();return!e||!n?null:`${e}/api/webui/ws?token=${encodeURIComponent(n)}`}async sendRequest(e,n=1e4){if(this.ws?.readyState!==WebSocket.OPEN)throw new Error("统一 WebSocket 尚未连接");const t=e.id;return await new Promise((s,r)=>{const a=window.setTimeout(()=>{this.pendingRequests.delete(t),r(new Error(`统一 WebSocket 请求超时: ${t}`))},n);this.pendingRequests.set(t,{resolve:s,reject:r,timeoutId:a}),this.ws?.send(JSON.stringify(e))})}async restoreState(e){const n=Array.from(this.subscriptions.values());for(const t of n)try{await this.sendRequest({op:"subscribe",id:this.nextRequestId(),domain:t.domain,topic:t.topic,data:t.data??{}})}catch(s){console.error("恢复统一 WebSocket 订阅失败:",s)}e&&this.reconnectListeners.forEach(t=>{try{t()}catch(s){console.error("统一 WebSocket 重连监听器执行失败:",s)}})}handleServerMessage(e,n){if(this.ws!==e)return;let t;try{t=JSON.parse(n)}catch(s){console.error("解析统一 WebSocket 消息失败:",s);return}if(t.op==="pong"){this.lastPongAt=Date.now();return}if(f(t)){const s=t.id;if(!s)return;const r=this.pendingRequests.get(s);if(!r)return;clearTimeout(r.timeoutId),this.pendingRequests.delete(s),t.ok?r.resolve(t.data??{}):r.reject(new Error(t.error?.message??"统一 WebSocket 请求失败"));return}w(t)&&this.eventListeners.forEach(s=>{try{s(t)}catch(r){console.error("统一 WebSocket 事件监听器执行失败:",r)}})}handleClose(e,n){if(this.ws===e){if(this.stopHeartbeat(),this.lastPongAt=0,this.ws=null,this.connectPromise=null,this.setStatus("idle"),this.rejectPendingRequests(new Error(`统一 WebSocket 已关闭 (${n.code})`)),n.code===4001){this.manualDisconnect=!0,window.location.pathname!=="/auth"&&(window.location.href="/auth");return}this.scheduleReconnect()}}async connect(){if(this.ws?.readyState!==WebSocket.OPEN){if(this.connectPromise)return await this.connectPromise;this.manualDisconnect=!1,this.setStatus("connecting"),this.connectPromise=(async()=>{const e=await this.createWebSocketUrl();if(!e)throw this.setStatus("idle"),new Error("无法建立统一 WebSocket 连接");await new Promise((n,t)=>{let s=!1;const r=new WebSocket(e);this.ws=r,r.onopen=()=>{if(this.ws!==r){r.close();return}s=!0;const a=this.hasConnectedOnce;this.hasConnectedOnce=!0,this.reconnectAttempts=0,this.lastPongAt=Date.now(),this.startHeartbeat(),this.setStatus("connected"),n(),this.restoreState(a)},r.onmessage=a=>{this.handleServerMessage(r,a.data)},r.onerror=()=>{this.ws===r&&(s||(s=!0,t(new Error("统一 WebSocket 连接失败"))))},r.onclose=a=>{s||(s=!0,t(new Error(`统一 WebSocket 已关闭 (${a.code})`))),this.handleClose(r,a)}})})();try{await this.connectPromise}finally{this.status!=="connected"&&(this.connectPromise=null)}}}disconnect(){this.manualDisconnect=!0,this.clearReconnectTimer(),this.stopHeartbeat(),this.lastPongAt=0,this.rejectPendingRequests(new Error("统一 WebSocket 已手动断开")),this.connectPromise=null,this.ws&&(this.ws.close(),this.ws=null),this.setStatus("idle")}async restart(){if(this.manualDisconnect=!1,this.clearReconnectTimer(),this.ws){this.ws.close();return}await this.connect()}async call(e){await this.connect();const n=this.nextRequestId();return await this.sendRequest({op:"call",id:n,domain:e.domain,method:e.method,session:e.session,data:e.data??{}})}async subscribe(e,n,t){return await this.connect(),this.subscriptions.set(this.getSubscriptionKey(e,n),{domain:e,topic:n,data:t}),await this.sendRequest({op:"subscribe",id:this.nextRequestId(),domain:e,topic:n,data:t??{}})}async unsubscribe(e,n){return this.subscriptions.delete(this.getSubscriptionKey(e,n)),this.ws?.readyState!==WebSocket.OPEN?null:await this.sendRequest({op:"unsubscribe",id:this.nextRequestId(),domain:e,topic:n,data:{}})}addEventListener(e){return this.eventListeners.add(e),()=>{this.eventListeners.delete(e)}}onConnectionChange(e){return this.connectionListeners.add(e),e(this.status==="connected"),()=>{this.connectionListeners.delete(e)}}onStatusChange(e){return this.statusListeners.add(e),e(this.status),()=>{this.statusListeners.delete(e)}}onReconnect(e){return this.reconnectListeners.add(e),()=>{this.reconnectListeners.delete(e)}}getStatus(){return this.status}}const L=new b;export{l as D,I as a,y as c,A as e,C as f,c as g,R as i,T as r,u as s,L as u};