fix(ext/sw): type-correct session.setCurrent + simplify create/attach handlers

Fixes a TS2345 that npx tsc --noEmit missed (it cannot resolve the generated
wasm/relicario_wasm types, degrading SessionHandle) but the webpack build
catches with real types: session.setCurrent(handle) was passed a
SessionHandle|null. Capture the unlock result in a non-null `const h:
SessionHandle` for the in-scope ops; `handle` remains the ownership tracker
the finally block cleans up.

Simplify pass: extract the shared register_device + addDevice + persist-config
tail into registerDeviceAndPersistConfig (both handlers ended identically),
hoist the Argon2 params literal to DEFAULT_PARAMS_JSON, and fan out the two
independent read-only GETs in the attach path via Promise.all.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-05-31 19:39:48 -04:00
parent 8044310fba
commit eed48e2bbb

View File

@@ -21,6 +21,32 @@ function requireWasm(): any {
return wasm;
}
const DEFAULT_PARAMS_JSON = '{"argon2_m":65536,"argon2_t":3,"argon2_p":4}';
/// Register this device on the remote (devices.json) and persist the vault
/// config + reference image locally so future unlocks work. Shared by the
/// create and attach flows — both finish with this identical tail.
async function registerDeviceAndPersistConfig(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
w: any,
git: GitHost,
config: VaultConfig,
referenceImageBytes: Uint8Array,
deviceName: string,
): Promise<void> {
const keys = w.register_device(deviceName) as { signing_public_key: string };
await devices.addDevice(git, {
name: deviceName,
public_key: keys.signing_public_key,
added_at: Math.floor(Date.now() / 1000),
});
await chrome.storage.local.set({
vaultConfig: config,
imageBase64: uint8ArrayToBase64(referenceImageBytes),
device_name: deviceName,
});
}
export async function handleCreateVault(
msg: { config: VaultConfig; passphrase: string; carrierImageBytes: ArrayBuffer; deviceName: string },
state: PopupState,
@@ -35,35 +61,25 @@ export async function handleCreateVault(
const salt = new Uint8Array(32);
crypto.getRandomValues(salt);
const paramsJson = '{"argon2_m":65536,"argon2_t":3,"argon2_p":4}';
handle = w.unlock(msg.passphrase, referenceImageBytes, salt, paramsJson);
// Capture the unlock result in a non-null binding for the in-scope ops;
// `handle` stays the ownership tracker the finally block cleans up.
const h: SessionHandle = w.unlock(msg.passphrase, referenceImageBytes, salt, DEFAULT_PARAMS_JSON);
handle = h;
const encryptedManifest = new Uint8Array(w.manifest_encrypt(handle, '{"schema_version":2,"items":{}}'));
const settingsJson = w.default_vault_settings_json();
const encryptedSettings = new Uint8Array(w.settings_encrypt(handle, settingsJson));
const encryptedManifest = new Uint8Array(w.manifest_encrypt(h, '{"schema_version":2,"items":{}}'));
const encryptedSettings = new Uint8Array(w.settings_encrypt(h, w.default_vault_settings_json()));
const { config } = msg;
const git = createGitHost(config.hostType, config.hostUrl, config.repoPath, config.apiToken);
await git.writeFileCreateOnly('.relicario/salt', salt, 'init: vault salt');
await git.writeFileCreateOnly('.relicario/params.json', new TextEncoder().encode(paramsJson), 'init: KDF parameters');
await git.writeFileCreateOnly('.relicario/params.json', new TextEncoder().encode(DEFAULT_PARAMS_JSON), 'init: KDF parameters');
await git.writeFileCreateOnly('manifest.enc', encryptedManifest, 'init: encrypted manifest');
await git.writeFileCreateOnly('settings.enc', encryptedSettings, 'init: encrypted settings');
const keys = w.register_device(msg.deviceName) as { signing_public_key: string; deploy_public_key: string };
await devices.addDevice(git, {
name: msg.deviceName,
public_key: keys.signing_public_key,
added_at: Math.floor(Date.now() / 1000),
});
await chrome.storage.local.set({
vaultConfig: config,
imageBase64: uint8ArrayToBase64(referenceImageBytes),
device_name: msg.deviceName,
});
await registerDeviceAndPersistConfig(w, git, config, referenceImageBytes, msg.deviceName);
// SW now owns the unlocked session — keeps the handle alive (enables recoveryQrAvailable).
session.setCurrent(handle);
session.setCurrent(h);
state.gitHost = git;
state.manifest = { schema_version: 2, items: {} } as Manifest;
handle = null; // ownership transferred — do NOT lock-and-free in finally
@@ -92,28 +108,21 @@ export async function handleAttachVault(
const { config } = msg;
const git = createGitHost(config.hostType, config.hostUrl, config.repoPath, config.apiToken);
const meta = await fetchVaultMeta(git);
const encryptedManifest = await git.readFile('manifest.enc');
// The vault metadata and manifest are independent read-only GETs — fan out.
const [meta, encryptedManifest] = await Promise.all([
fetchVaultMeta(git),
git.readFile('manifest.enc'),
]);
handle = w.unlock(msg.passphrase, referenceImageBytes, meta.salt, meta.paramsJson);
const h: SessionHandle = w.unlock(msg.passphrase, referenceImageBytes, meta.salt, meta.paramsJson);
handle = h;
// manifest_decrypt verifies the passphrase + reference image — throws on AEAD failure.
const manifest = w.manifest_decrypt(handle, encryptedManifest) as Manifest;
const manifest = w.manifest_decrypt(h, encryptedManifest) as Manifest;
const keys = w.register_device(msg.deviceName) as { signing_public_key: string; deploy_public_key: string };
await devices.addDevice(git, {
name: msg.deviceName,
public_key: keys.signing_public_key,
added_at: Math.floor(Date.now() / 1000),
});
await chrome.storage.local.set({
vaultConfig: config,
imageBase64: uint8ArrayToBase64(referenceImageBytes),
device_name: msg.deviceName,
});
await registerDeviceAndPersistConfig(w, git, config, referenceImageBytes, msg.deviceName);
// SW now owns the unlocked session — transfer ownership to the session.
session.setCurrent(handle);
session.setCurrent(h);
state.gitHost = git;
state.manifest = manifest;
handle = null; // ownership transferred — do NOT lock-and-free in finally