Files
NetMesh/components/test-support/createAppLockBridgeHarness.ts
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
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
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

210 lines
6.1 KiB
TypeScript

import type { RuntimeAppLockState } from "../../application/state/useAppLockRuntime";
type UnlockResult =
| { ok: true }
| { ok: false; error: "empty" | "incorrect" };
type HarnessOptions = {
runtimeState: RuntimeAppLockState;
unlockPassword?: string;
systemUnlockStatus?: {
supported: boolean;
available: boolean;
enabled: boolean;
platform: "darwin" | "win32" | "unsupported";
label: "Touch ID" | "Windows Hello" | null;
reason: string | null;
};
systemUnlockResult?: { ok: true } | { ok: false; error: "disabled" | "not-locked" | "unsupported" | "unavailable" | "cancelled" | "failed" };
};
const TEST_PASSWORD_VERIFIER = {
version: 1 as const,
algorithm: "PBKDF2-SHA256" as const,
iterations: 210000,
salt: "AAAAAAAAAAAAAAAAAAAAAA==",
hash: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
};
function cloneRuntimeState(input: RuntimeAppLockState): RuntimeAppLockState {
return {
...input,
};
}
export function createAppLockBridgeHarness(options: HarnessOptions) {
let runtimeState = cloneRuntimeState(options.runtimeState);
let nextVersion = runtimeState.version + 1;
let unlockPassword = options.unlockPassword ?? "secret";
const runtimeListeners = new Set<(state: RuntimeAppLockState) => void>();
const reopenListeners = new Set<() => void>();
const rendererReadyCalls: number[] = [];
const unlockAttempts: string[] = [];
const activityReports: number[] = [];
let systemUnlockStatus = options.systemUnlockStatus ?? {
supported: false,
available: false,
enabled: false,
platform: "unsupported" as const,
label: null,
reason: null,
};
let systemUnlockResult = options.systemUnlockResult ?? { ok: true as const };
let systemUnlockCount = 0;
let resetCount = 0;
const resetAttempts: string[] = [];
let runtimeFetchCount = 0;
const emitRuntimeState = () => {
const snapshot = cloneRuntimeState(runtimeState);
for (const listener of runtimeListeners) {
listener(snapshot);
}
};
const setRuntimeState = (nextState: Partial<RuntimeAppLockState>, { notify = true } = {}) => {
runtimeState = {
...runtimeState,
...nextState,
version: nextVersion++,
};
if (notify) emitRuntimeState();
};
const bridge: NetcattyBridge = {
getAppLockRuntimeState: async () => {
runtimeFetchCount += 1;
return cloneRuntimeState(runtimeState);
},
onAppLockRuntimeStateChanged: (listener) => {
runtimeListeners.add(listener);
return () => runtimeListeners.delete(listener);
},
requestAppLockUnlock: async (password) => {
unlockAttempts.push(password);
if (!password) return { ok: false, error: "empty" } satisfies UnlockResult;
if (password !== unlockPassword) return { ok: false, error: "incorrect" } satisfies UnlockResult;
setRuntimeState({
initialized: true,
locked: false,
reason: null,
lastUnlockedAt: Date.now(),
lastActivityAt: Date.now(),
});
return { ok: true } satisfies UnlockResult;
},
requestAppLockReset: async (currentPassword) => {
resetCount += 1;
resetAttempts.push(currentPassword);
if (!currentPassword) return { ok: false, error: "empty-current" };
if (currentPassword !== unlockPassword) return { ok: false, error: "incorrect" };
setRuntimeState({
initialized: true,
locked: false,
reason: null,
lastUnlockedAt: Date.now(),
lastActivityAt: Date.now(),
});
return {
enabled: false,
timeoutMinutes: 15,
systemUnlockEnabled: false,
systemUnlockAutoPromptEnabled: false,
passwordVerifier: null,
};
},
getAppLockSystemUnlockStatus: async () => ({ ...systemUnlockStatus }),
setAppLockSystemUnlockEnabled: async (input) => {
systemUnlockStatus = {
...systemUnlockStatus,
enabled: input.enabled,
};
return {
enabled: true,
timeoutMinutes: 15,
systemUnlockEnabled: input.enabled,
systemUnlockAutoPromptEnabled: input.enabled && input.autoPromptEnabled === true,
passwordVerifier: TEST_PASSWORD_VERIFIER,
};
},
requestAppLockSystemUnlock: async () => {
systemUnlockCount += 1;
if (!systemUnlockResult.ok) return systemUnlockResult;
setRuntimeState({
initialized: true,
locked: false,
reason: null,
lastUnlockedAt: Date.now(),
lastActivityAt: Date.now(),
});
return { ok: true };
},
setAppLockRuntimeLocked: async (reason) => {
setRuntimeState({
initialized: true,
locked: true,
reason,
lastLockedAt: Date.now(),
});
return cloneRuntimeState(runtimeState);
},
reportAppLockActivity: async () => {
activityReports.push(Date.now());
setRuntimeState({
lastActivityAt: Date.now(),
}, { notify: false });
return cloneRuntimeState(runtimeState);
},
onAppLockReopen: (listener) => {
reopenListeners.add(listener);
return () => reopenListeners.delete(listener);
},
rendererReady: () => {
rendererReadyCalls.push(Date.now());
},
};
return {
bridge,
getRuntimeState() {
return cloneRuntimeState(runtimeState);
},
getRuntimeFetchCount() {
return runtimeFetchCount;
},
setRuntimeState,
setUnlockPassword(nextPassword: string) {
unlockPassword = nextPassword;
},
emitReopen() {
for (const listener of reopenListeners) {
listener();
}
},
getUnlockAttempts() {
return [...unlockAttempts];
},
getRendererReadyCallCount() {
return rendererReadyCalls.length;
},
getActivityReportCount() {
return activityReports.length;
},
getResetCount() {
return resetCount;
},
getResetAttempts() {
return [...resetAttempts];
},
getSystemUnlockCount() {
return systemUnlockCount;
},
setSystemUnlockResult(nextResult: typeof systemUnlockResult) {
systemUnlockResult = nextResult;
},
setSystemUnlockStatus(nextStatus: typeof systemUnlockStatus) {
systemUnlockStatus = nextStatus;
},
};
}