Some checks failed
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
巡检模块:巡检调度器改为轮询驱动,新增每日报告存储、告警规则独立 Store、设备 Store 解耦,_executeSnapshot 补齐 DEBUG 声明,巡检中心 与告警中心页增强。 拓扑大屏:MonitoringTopologyView 扩展分组/布局控制, useMonitoringData 重构数据流。 远程 HTTP:新增 electron/remoteHttpServer.cjs,preload/api.cjs 暴露 IPC,registerBridges 注册 netcatty:http:open 桥。 portable 绿色打包: - electron-builder.config.cjs 恢复原始白名单(61MB app.asar), 收集器自动追踪运行时依赖; - portableData.cjs 重写为 .portable 标记检测; - scripts/migratePortableLocalStorage.cjs 离线脚本把 Local Storage 的 http://localhost:5173 origin 批量迁移为 app://netcatty; - scripts/scanWorkspaceDeps.cjs 扫描 workspace 运行时依赖。
114 lines
4.0 KiB
TypeScript
114 lines
4.0 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react';
|
||
|
||
export type RuntimeAppLockReason = 'startup' | 'idle' | 'manual' | 'background' | null;
|
||
|
||
export interface RuntimeAppLockState {
|
||
initialized: boolean;
|
||
locked: boolean;
|
||
reason: RuntimeAppLockReason;
|
||
version: number;
|
||
lastLockedAt: number | null;
|
||
lastUnlockedAt: number | null;
|
||
lastActivityAt: number | null;
|
||
}
|
||
|
||
export function normalizeRuntimeAppLockState(input: unknown): RuntimeAppLockState {
|
||
const record = input && typeof input === 'object' ? input as Record<string, unknown> : null;
|
||
const reason = record?.reason;
|
||
|
||
return {
|
||
initialized: record?.initialized === true,
|
||
locked: record?.locked === true,
|
||
reason:
|
||
reason === 'startup' ||
|
||
reason === 'idle' ||
|
||
reason === 'manual' ||
|
||
reason === 'background'
|
||
? reason
|
||
: null,
|
||
version: typeof record?.version === 'number' && Number.isFinite(record.version) ? record.version : 0,
|
||
lastLockedAt: typeof record?.lastLockedAt === 'number' && Number.isFinite(record.lastLockedAt) ? record.lastLockedAt : null,
|
||
lastUnlockedAt: typeof record?.lastUnlockedAt === 'number' && Number.isFinite(record.lastUnlockedAt) ? record.lastUnlockedAt : null,
|
||
lastActivityAt: typeof record?.lastActivityAt === 'number' && Number.isFinite(record.lastActivityAt) ? record.lastActivityAt : null,
|
||
};
|
||
}
|
||
|
||
const DEFAULT_RUNTIME_APP_LOCK_STATE: RuntimeAppLockState = normalizeRuntimeAppLockState(null);
|
||
|
||
export function selectPreferredRuntimeAppLockState(
|
||
current: RuntimeAppLockState,
|
||
incoming: RuntimeAppLockState,
|
||
): RuntimeAppLockState {
|
||
if (incoming.version > current.version) return incoming;
|
||
if (incoming.version < current.version) return current;
|
||
if (incoming.initialized && !current.initialized) return incoming;
|
||
if (!incoming.initialized && current.initialized) return current;
|
||
return incoming;
|
||
}
|
||
|
||
export function useAppLockRuntime(
|
||
bridge: Pick<
|
||
NonNullable<NetcattyBridge>,
|
||
'getAppLockRuntimeState' | 'onAppLockRuntimeStateChanged'
|
||
> | null | undefined,
|
||
) {
|
||
// 浏览器远程访问环境下(无 Electron bridge),直接 unlocked
|
||
const BROWSER_ONLY = typeof window !== 'undefined' && !!(window as any).__REMOTE_TOKEN__;
|
||
const [runtimeState, setRuntimeState] = useState<RuntimeAppLockState>(
|
||
BROWSER_ONLY
|
||
? { initialized: true, locked: false, reason: null, version: 0, lastLockedAt: null, lastUnlockedAt: null, lastActivityAt: null }
|
||
: DEFAULT_RUNTIME_APP_LOCK_STATE,
|
||
);
|
||
const refreshRuntimeState = useCallback(async () => {
|
||
// 浏览器远程访问:直接返回 unlocked
|
||
if (BROWSER_ONLY) {
|
||
const unlocked: RuntimeAppLockState = {
|
||
initialized: true,
|
||
locked: false,
|
||
reason: null,
|
||
version: 0,
|
||
lastLockedAt: null,
|
||
lastUnlockedAt: null,
|
||
lastActivityAt: null,
|
||
};
|
||
setRuntimeState((current) => selectPreferredRuntimeAppLockState(current, unlocked));
|
||
return unlocked;
|
||
}
|
||
const nextState = await bridge?.getAppLockRuntimeState?.();
|
||
const normalized = normalizeRuntimeAppLockState(nextState);
|
||
setRuntimeState((current) => selectPreferredRuntimeAppLockState(current, normalized));
|
||
return normalized;
|
||
}, [bridge, BROWSER_ONLY]);
|
||
|
||
useEffect(() => {
|
||
let cancelled = false;
|
||
|
||
const unsubscribe = BROWSER_ONLY
|
||
? () => {}
|
||
: (bridge?.onAppLockRuntimeStateChanged?.((nextState) => {
|
||
if (cancelled) return;
|
||
const normalized = normalizeRuntimeAppLockState(nextState);
|
||
setRuntimeState((current) => selectPreferredRuntimeAppLockState(current, normalized));
|
||
}) ?? (() => {}));
|
||
|
||
void refreshRuntimeState().catch(() => {
|
||
if (cancelled) return;
|
||
setRuntimeState((current) => selectPreferredRuntimeAppLockState(
|
||
current,
|
||
DEFAULT_RUNTIME_APP_LOCK_STATE,
|
||
));
|
||
});
|
||
|
||
return () => {
|
||
cancelled = true;
|
||
unsubscribe();
|
||
};
|
||
}, [bridge, refreshRuntimeState, BROWSER_ONLY]);
|
||
|
||
return {
|
||
runtimeState,
|
||
setRuntimeState,
|
||
refreshRuntimeState,
|
||
};
|
||
}
|