fix: correct dynamic page behavior on lifecycle events

This commit is contained in:
divocat
2025-10-18 01:45:02 +03:00
parent 4d4164ae6f
commit 7ab0384e0b
4 changed files with 231 additions and 58 deletions

View File

@@ -18,6 +18,27 @@ class SocketManager {
return SocketManager.instance; return SocketManager.instance;
} }
resetAll(): void {
for (const [url, ws] of this.sockets.entries()) {
try {
if (
ws.readyState === WebSocket.OPEN ||
ws.readyState === WebSocket.CONNECTING
) {
ws.close();
}
} catch (err) {
console.warn(`resetAll: failed to close socket ${url}`, err);
}
}
this.sockets.clear();
this.listeners.clear();
this.errorListeners.clear();
this.connected.clear();
console.info('[SocketManager] All connections and state have been reset.');
}
connect(url: string): void { connect(url: string): void {
if (this.sockets.has(url)) return; if (this.sockets.has(url)) return;

View File

@@ -38,6 +38,7 @@ async function fetchDashboardSections() {
} }
async function connectToClashSockets() { async function connectToClashSockets() {
console.log('[SOCKET] connectToClashSockets');
socket.subscribe( socket.subscribe(
`${getClashWsUrl()}/traffic?token=`, `${getClashWsUrl()}/traffic?token=`,
(msg) => { (msg) => {
@@ -388,8 +389,20 @@ async function onStoreUpdate(
} }
} }
export async function initController(): Promise<void> { async function onPageMount() {
onMount('dashboard-status').then(() => { // Cleanup before mount
onPageUnmount();
// Add new listener
store.subscribe(onStoreUpdate);
// Initial sections fetch
await fetchDashboardSections();
await fetchServicesInfo();
await connectToClashSockets();
}
function onPageUnmount() {
// Remove old listener // Remove old listener
store.unsubscribe(onStoreUpdate); store.unsubscribe(onStoreUpdate);
// Clear store // Clear store
@@ -400,13 +413,36 @@ export async function initController(): Promise<void> {
'servicesInfoWidget', 'servicesInfoWidget',
'sectionsWidget', 'sectionsWidget',
]); ]);
socket.resetAll();
}
// Add new listener function registerLifecycleListeners() {
store.subscribe(onStoreUpdate); store.subscribe((next, prev, diff) => {
if (
diff.tabService &&
next.tabService.current !== prev.tabService.current
) {
console.log(
new Date().toISOString(),
'[Active Tab on dashboard]',
diff.tabService.current,
);
const isDashboardVisible = next.tabService.current === 'dashboard';
// Initial sections fetch if (isDashboardVisible) {
fetchDashboardSections(); return onPageMount();
fetchServicesInfo(); }
connectToClashSockets();
if (!isDashboardVisible) {
onPageUnmount();
}
}
});
}
export async function initController(): Promise<void> {
onMount('dashboard-status').then(() => {
onPageMount();
registerLifecycleListeners();
}); });
} }

View File

@@ -477,11 +477,10 @@ async function runChecks() {
} }
} }
export async function initController(): Promise<void> { function onPageMount() {
onMount('diagnostic-status').then(() => {
console.log('diagnostic controller initialized.'); console.log('diagnostic controller initialized.');
// Remove old listener // Cleanup before mount
store.unsubscribe(onStoreUpdate); onPageUnmount();
// Add new listener // Add new listener
store.subscribe(onStoreUpdate); store.subscribe(onStoreUpdate);
@@ -503,5 +502,48 @@ export async function initController(): Promise<void> {
// Initial system info fetch // Initial system info fetch
fetchSystemInfo(); fetchSystemInfo();
}
function onPageUnmount() {
// Remove old listener
store.unsubscribe(onStoreUpdate);
// Clear store
store.reset([
'diagnosticsActions',
'diagnosticsSystemInfo',
'diagnosticsChecks',
'diagnosticsRunAction',
]);
}
function registerLifecycleListeners() {
store.subscribe((next, prev, diff) => {
if (
diff.tabService &&
next.tabService.current !== prev.tabService.current
) {
console.log(
new Date().toISOString(),
'[Active Tab on diagnostics]',
diff.tabService.current,
);
const isDashboardVisible = next.tabService.current === 'diagnostic';
if (isDashboardVisible) {
return onPageMount();
}
if (!isDashboardVisible) {
onPageUnmount();
}
}
});
}
export async function initController(): Promise<void> {
onMount('diagnostic-status').then(() => {
onPageMount();
registerLifecycleListeners();
}); });
} }

View File

@@ -1192,6 +1192,22 @@ var SocketManager = class _SocketManager {
} }
return _SocketManager.instance; return _SocketManager.instance;
} }
resetAll() {
for (const [url, ws] of this.sockets.entries()) {
try {
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
ws.close();
}
} catch (err) {
console.warn(`resetAll: failed to close socket ${url}`, err);
}
}
this.sockets.clear();
this.listeners.clear();
this.errorListeners.clear();
this.connected.clear();
console.info("[SocketManager] All connections and state have been reset.");
}
connect(url) { connect(url) {
if (this.sockets.has(url)) return; if (this.sockets.has(url)) return;
let ws; let ws;
@@ -1576,6 +1592,7 @@ async function fetchDashboardSections() {
}); });
} }
async function connectToClashSockets() { async function connectToClashSockets() {
console.log("[SOCKET] connectToClashSockets");
socket.subscribe( socket.subscribe(
`${getClashWsUrl()}/traffic?token=`, `${getClashWsUrl()}/traffic?token=`,
(msg) => { (msg) => {
@@ -1866,8 +1883,14 @@ async function onStoreUpdate(next, prev, diff) {
renderServicesInfoWidget(); renderServicesInfoWidget();
} }
} }
async function initController() { async function onPageMount() {
onMount("dashboard-status").then(() => { onPageUnmount();
store.subscribe(onStoreUpdate);
await fetchDashboardSections();
await fetchServicesInfo();
await connectToClashSockets();
}
function onPageUnmount() {
store.unsubscribe(onStoreUpdate); store.unsubscribe(onStoreUpdate);
store.reset([ store.reset([
"bandwidthWidget", "bandwidthWidget",
@@ -1876,10 +1899,30 @@ async function initController() {
"servicesInfoWidget", "servicesInfoWidget",
"sectionsWidget" "sectionsWidget"
]); ]);
store.subscribe(onStoreUpdate); socket.resetAll();
fetchDashboardSections(); }
fetchServicesInfo(); function registerLifecycleListeners() {
connectToClashSockets(); store.subscribe((next, prev, diff) => {
if (diff.tabService && next.tabService.current !== prev.tabService.current) {
console.log(
(/* @__PURE__ */ new Date()).toISOString(),
"[Active Tab on dashboard]",
diff.tabService.current
);
const isDashboardVisible = next.tabService.current === "dashboard";
if (isDashboardVisible) {
return onPageMount();
}
if (!isDashboardVisible) {
onPageUnmount();
}
}
});
}
async function initController() {
onMount("dashboard-status").then(() => {
onPageMount();
registerLifecycleListeners();
}); });
} }
@@ -3744,10 +3787,9 @@ async function runChecks() {
store.set({ diagnosticsRunAction: { loading: false } }); store.set({ diagnosticsRunAction: { loading: false } });
} }
} }
async function initController2() { function onPageMount2() {
onMount("diagnostic-status").then(() => {
console.log("diagnostic controller initialized."); console.log("diagnostic controller initialized.");
store.unsubscribe(onStoreUpdate2); onPageUnmount2();
store.subscribe(onStoreUpdate2); store.subscribe(onStoreUpdate2);
renderDiagnosticsChecks(); renderDiagnosticsChecks();
renderDiagnosticRunActionWidget(); renderDiagnosticRunActionWidget();
@@ -3755,6 +3797,38 @@ async function initController2() {
renderDiagnosticSystemInfoWidget(); renderDiagnosticSystemInfoWidget();
fetchServicesInfo(); fetchServicesInfo();
fetchSystemInfo(); fetchSystemInfo();
}
function onPageUnmount2() {
store.unsubscribe(onStoreUpdate2);
store.reset([
"diagnosticsActions",
"diagnosticsSystemInfo",
"diagnosticsChecks",
"diagnosticsRunAction"
]);
}
function registerLifecycleListeners2() {
store.subscribe((next, prev, diff) => {
if (diff.tabService && next.tabService.current !== prev.tabService.current) {
console.log(
(/* @__PURE__ */ new Date()).toISOString(),
"[Active Tab on diagnostics]",
diff.tabService.current
);
const isDashboardVisible = next.tabService.current === "diagnostic";
if (isDashboardVisible) {
return onPageMount2();
}
if (!isDashboardVisible) {
onPageUnmount2();
}
}
});
}
async function initController2() {
onMount("diagnostic-status").then(() => {
onPageMount2();
registerLifecycleListeners2();
}); });
} }