frontend/src/skins/withVault.tsx
Rama main · réplica de lectura del repositorio original.
/* eslint-disable */
// @ts-nocheck
/**
* The device-local password vault, as a mixin over a generated skin component.
*
* Composes with withAccounts — each wraps the previous class and extends
* renderVals() rather than replacing it, so ordering does not matter and both
* sets of bindings survive.
*
* This drives the *real* vault (lib/localStore/vault): AES-GCM under a
* non-extractable key. The imported designs all ship a simulated one backed by
* plain localStorage, complete with a "do not enter real passwords" notice —
* that notice is not reproduced here, because it would no longer be true.
*/
import { mergeNavItem } from "./navInject";
import { vaultApi, describeEntry, initialVaultState } from "./vaultLogic";
export function withVault(Base, config) {
return class WithVault extends Base {
constructor(props) {
super(props);
this.state = { ...this.state, vlt: { ...initialVaultState } };
this._onVaultNav = () => this.forceUpdate();
}
componentDidMount() {
if (super.componentDidMount) super.componentDidMount();
window.addEventListener("hashchange", this._onVaultNav);
window.addEventListener("popstate", this._onVaultNav);
this._loadVault();
}
componentWillUnmount() {
if (super.componentWillUnmount) super.componentWillUnmount();
window.removeEventListener("hashchange", this._onVaultNav);
window.removeEventListener("popstate", this._onVaultNav);
}
_setVault(patch) {
this.setState((s) => ({ vlt: { ...s.vlt, ...patch } }));
}
_loadVault() {
vaultApi.load()
.then(({ entries, unreadable }) => this._setVault({ entries, unreadable }))
.catch((err) => this._setVault({ error: err.message }));
}
_vaultAdd = (event) => {
if (event && event.preventDefault) event.preventDefault();
const { label, password } = this.state.vlt;
if (!label.trim()) { this._setVault({ error: "Give it a name so you can find it again." }); return; }
if (!password) { this._setVault({ error: "Enter the password to store." }); return; }
this._setVault({ busy: true, error: "" });
vaultApi.add(label.trim(), password)
.then(() => { this._setVault({ busy: false, label: "", password: "" }); this._loadVault(); })
.catch((err) => this._setVault({ busy: false, error: err.message }));
};
_vaultReveal = (id) => {
if (this.state.vlt.revealedId === id) {
this._setVault({ revealedId: "", revealedValue: "" });
return;
}
vaultApi.reveal(id)
.then((value) => this._setVault({ revealedId: id, revealedValue: value }))
.catch((err) => this._setVault({ error: err.message }));
};
_vaultCopy = (id) => {
vaultApi.reveal(id)
.then((value) => navigator.clipboard.writeText(value))
.catch((err) => this._setVault({ error: err.message }));
};
_vaultDelete = (id) => {
vaultApi.remove(id)
.then(() => { this._setVault({ revealedId: "", revealedValue: "" }); this._loadVault(); })
.catch((err) => this._setVault({ error: err.message }));
};
_vaultClear = () => {
if (!this.state.vlt.confirmingClear) { this._setVault({ confirmingClear: true }); return; }
vaultApi.clear()
.then(() => { this._setVault({ ...initialVaultState }); this._loadVault(); })
.catch((err) => this._setVault({ error: err.message, confirmingClear: false }));
};
_vaultGo = (e) => { if (e && e.preventDefault) e.preventDefault(); config.navigate(); };
_vaultTitle() { return "Vault — PrivaTools"; }
titleFor(route, param) {
if (config.isActive()) return this._vaultTitle();
return super.titleFor ? super.titleFor(route, param) : document.title;
}
setTitle(route, sub) {
if (config.isActive()) {
try { document.title = this._vaultTitle(); } catch (e) { /* SSR */ }
return;
}
if (super.setTitle) super.setTitle(route, sub);
}
renderVals() {
const v = super.renderVals();
const s = this.state.vlt;
const active = config.isActive();
const p = config.palette;
const item = config.navItem({
label: "Vault", icon: "lock", onClick: this._vaultGo, active,
});
const nav = config.injectNav
? config.injectNav(v, item)
: { [config.navKey]: mergeNavItem(v[config.navKey] ?? [], item) };
return {
...v,
...nav,
...(active
? Object.fromEntries((config.suppressFlags ?? ["is404"]).map((f) => [f, false]))
: {}),
isVaultReal: active,
vltCount: String(s.entries.length),
vltEmptyD: s.entries.length === 0 ? "block" : "none",
vltUnreadableD: s.unreadable > 0 ? "block" : "none",
vltUnreadable: s.unreadable
? `${s.unreadable} entr${s.unreadable === 1 ? "y" : "ies"} cannot be read with this browser's key.`
: "",
vltLabel: s.label,
vltPassword: s.password,
vltSetLabel: (e) => this._setVault({ label: e.target.value, error: "" }),
vltSetPassword: (e) => this._setVault({ password: e.target.value, error: "" }),
vltAdd: this._vaultAdd,
vltBusy: s.busy,
vltBusyOpacity: s.busy ? ".6" : "1",
vltAddLabel: s.busy ? "Saving…" : "Save password",
vltError: s.error,
vltErrD: s.error ? "block" : "none",
vltClear: this._vaultClear,
// Hidden while the vault is empty — there is nothing to erase,
// and a destructive control with no target is just noise.
vltClearD: s.entries.length ? "inline-flex" : "none",
vltClearLabel: s.confirmingClear ? "Press again to erase every entry" : "Erase the vault",
vltEntries: s.entries.map((row) => {
const shown = s.revealedId === row.id;
return {
label: row.label,
labelColor: p.text,
meta: describeEntry(row),
secret: shown ? s.revealedValue : "••••••••••••",
secretColor: shown ? p.text : p.faint,
revealIcon: shown ? "visibility_off" : "visibility",
revealLabel: (shown ? "Hide" : "Reveal") + " password for " + row.label,
reveal: () => this._vaultReveal(row.id),
copy: () => this._vaultCopy(row.id),
remove: () => this._vaultDelete(row.id),
};
}),
};
}
};
}