mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-11-04 02:40:20 +00:00
`npx tsc -p webapp/tsconfig.json --noEmit`, one of the checks recommended in CONTRIBUTING.md, currently fails on main with three errors. All three are declaration defects with correct runtime behavior; none changes observable behavior. - api/backup.ts: downloadAdminBackupAttachmentBlob declared a bare Uint8Array return. Since TypeScript made typed arrays generic, that widens to Uint8Array<ArrayBufferLike> and no longer satisfies fflate's Uint8Array<ArrayBuffer>. The body already returns an ArrayBuffer-backed value, so this only annotates what it produces. - backup-center.ts: invalidateRemoteBrowserCacheForDestination declared a full PersistedRemoteBrowserState but builds four of its five fields. The sole caller reads .cache only, so the return type is narrowed to match what the function actually returns. - password-security-cache.ts: getPasswordSecurityState declared the public PasswordSecurityState, but startPasswordSecurityScan needs `controller`, which lives on InternalPasswordSecurityState. An internal accessor keeps `controller` off the exported type rather than widening the public API. No change to backup payload shape, archive/import whitelists, or any persisted format. Verified with tsc 5.9.3, 6.0.3, and 7.0.2 (all exit 0 for both webapp/tsconfig.json and tsconfig.json), plus npm run build and npm run i18n:validate.
79 lines
3.0 KiB
TypeScript
79 lines
3.0 KiB
TypeScript
import { inspectVaultPasswordSecurity, type PasswordSecurityReport } from '@/lib/password-security';
|
|
import type { Cipher } from '@/lib/types';
|
|
|
|
export interface PasswordSecurityState {
|
|
fingerprint: string;
|
|
report: PasswordSecurityReport | null;
|
|
scannedAt: number | null;
|
|
scanning: boolean;
|
|
progress: { checked: number; total: number };
|
|
scanError: boolean;
|
|
}
|
|
|
|
type InternalPasswordSecurityState = PasswordSecurityState & { controller: AbortController | null };
|
|
|
|
let state: InternalPasswordSecurityState | null = null;
|
|
const listeners = new Set<() => void>();
|
|
|
|
function notify(): void {
|
|
listeners.forEach((listener) => listener());
|
|
}
|
|
|
|
function createState(fingerprint: string): InternalPasswordSecurityState {
|
|
return { fingerprint, report: null, scannedAt: null, scanning: false, progress: { checked: 0, total: 0 }, scanError: false, controller: null };
|
|
}
|
|
|
|
function ensurePasswordSecurityState(fingerprint: string): InternalPasswordSecurityState {
|
|
if (state?.fingerprint !== fingerprint) {
|
|
state?.controller?.abort();
|
|
state = createState(fingerprint);
|
|
}
|
|
return state;
|
|
}
|
|
|
|
export function getPasswordSecurityState(fingerprint: string): PasswordSecurityState {
|
|
return ensurePasswordSecurityState(fingerprint);
|
|
}
|
|
|
|
export function readPasswordSecurityState(fingerprint: string): PasswordSecurityState | null {
|
|
return state?.fingerprint === fingerprint ? state : null;
|
|
}
|
|
|
|
export function subscribePasswordSecurityState(listener: () => void): () => void {
|
|
listeners.add(listener);
|
|
return () => listeners.delete(listener);
|
|
}
|
|
|
|
export function startPasswordSecurityScan(fingerprint: string, ciphers: Cipher[]): void {
|
|
const current = ensurePasswordSecurityState(fingerprint);
|
|
current.controller?.abort();
|
|
const controller = new AbortController();
|
|
const total = ciphers.filter((cipher) => Number(cipher.type) === 1 && !cipher.deletedDate && !(cipher as { deletedAt?: string | null }).deletedAt && !!cipher.login?.decPassword).length;
|
|
state = { ...current, report: null, scannedAt: null, scanning: true, progress: { checked: 0, total }, scanError: false, controller };
|
|
notify();
|
|
|
|
void (async () => {
|
|
try {
|
|
const report = await inspectVaultPasswordSecurity(ciphers, (checked, total) => {
|
|
if (controller.signal.aborted || state?.controller !== controller) return;
|
|
state = { ...state, progress: { checked, total } };
|
|
notify();
|
|
}, fetch, controller.signal);
|
|
if (controller.signal.aborted || state?.controller !== controller) return;
|
|
state = { ...state, report, scannedAt: Date.now() };
|
|
} catch (error) {
|
|
if (controller.signal.aborted || (error as { name?: string } | null)?.name === 'AbortError') return;
|
|
if (state?.controller === controller) state = { ...state, scanError: true };
|
|
} finally {
|
|
if (state?.controller === controller) state = { ...state, controller: null, scanning: false };
|
|
notify();
|
|
}
|
|
})();
|
|
}
|
|
|
|
export function clearPasswordSecurityCache(): void {
|
|
state?.controller?.abort();
|
|
state = null;
|
|
notify();
|
|
}
|