feat(ext/sw): restore_backup handler
Unpacks .relbak via WASM, writes every vault artifact to the user-specified fresh remote via writeFileCreateOnly (refuses to clobber), and updates chrome.storage.local so subsequent unlocks hit the restored vault. The reference image — when bundled — is restored to imageBase64; otherwise the user keeps using their existing reference.jpg.
This commit is contained in:
@@ -418,8 +418,94 @@ export async function handle(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'restore_backup':
|
case 'restore_backup': {
|
||||||
return { ok: false, error: 'restore_backup not yet implemented' };
|
try {
|
||||||
|
const bytes = new Uint8Array(msg.bytes);
|
||||||
|
const outJson: string = state.wasm.unpack_backup_json(bytes, msg.passphrase);
|
||||||
|
const out = JSON.parse(outJson) as {
|
||||||
|
salt: string;
|
||||||
|
params_json: string;
|
||||||
|
devices_json: string;
|
||||||
|
manifest_enc: string;
|
||||||
|
settings_enc: string;
|
||||||
|
items: Array<{ id: string; ciphertext: string }>;
|
||||||
|
attachments: Array<{ item_id: string; attachment_id: string; ciphertext: string }>;
|
||||||
|
reference_jpg: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build a GitHost for the new remote.
|
||||||
|
const newHost = createGitHost(
|
||||||
|
msg.newRemote.hostType,
|
||||||
|
msg.newRemote.hostUrl,
|
||||||
|
msg.newRemote.repoPath,
|
||||||
|
msg.newRemote.apiToken,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Refuse if the remote already has a vault.
|
||||||
|
try {
|
||||||
|
const meta = await vault.fetchVaultMeta(newHost);
|
||||||
|
if (meta.salt && meta.paramsJson) {
|
||||||
|
return { ok: false, error: 'remote already contains a relicario vault' };
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// No vault present — expected for a fresh remote.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the layout via writeFileCreateOnly. Refuses to clobber.
|
||||||
|
const b64 = (s: string) => Uint8Array.from(atob(s), c => c.charCodeAt(0));
|
||||||
|
await newHost.writeFileCreateOnly('.relicario/salt', b64(out.salt), 'restore: salt');
|
||||||
|
await newHost.writeFileCreateOnly('.relicario/params.json', new TextEncoder().encode(out.params_json), 'restore: params.json');
|
||||||
|
await newHost.writeFileCreateOnly('.relicario/devices.json', new TextEncoder().encode(out.devices_json), 'restore: devices.json');
|
||||||
|
await newHost.writeFileCreateOnly('manifest.enc', b64(out.manifest_enc), 'restore: manifest.enc');
|
||||||
|
await newHost.writeFileCreateOnly('settings.enc', b64(out.settings_enc), 'restore: settings.enc');
|
||||||
|
|
||||||
|
for (const it of out.items) {
|
||||||
|
await newHost.writeFileCreateOnly(
|
||||||
|
`items/${it.id}.enc`, b64(it.ciphertext), `restore: item ${it.id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Translate canonical envelope keys (<item_id>/<aid>) back to the
|
||||||
|
// extension's flat layout (attachments/<aid>.bin). The aid is
|
||||||
|
// already content-addressed and globally unique; the item_id segment
|
||||||
|
// is recorded only in the manifest's attachment_summaries.
|
||||||
|
for (const a of out.attachments) {
|
||||||
|
await newHost.writeFileCreateOnly(
|
||||||
|
`attachments/${a.attachment_id}.bin`,
|
||||||
|
b64(a.ciphertext),
|
||||||
|
`restore: attachment ${a.attachment_id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update local config so subsequent unlocks work.
|
||||||
|
const cfg = {
|
||||||
|
hostType: msg.newRemote.hostType,
|
||||||
|
hostUrl: msg.newRemote.hostUrl,
|
||||||
|
repoPath: msg.newRemote.repoPath,
|
||||||
|
apiToken: msg.newRemote.apiToken,
|
||||||
|
};
|
||||||
|
await chrome.storage.local.set({ vaultConfig: cfg });
|
||||||
|
if (out.reference_jpg) {
|
||||||
|
await chrome.storage.local.set({ imageBase64: out.reference_jpg });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the SW's gitHost cache picks up the new config.
|
||||||
|
state.gitHost = newHost;
|
||||||
|
state.manifest = null; // user must unlock to populate
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
data: {
|
||||||
|
summary: {
|
||||||
|
itemCount: out.items.length,
|
||||||
|
attachmentCount: out.attachments.length,
|
||||||
|
hasImage: out.reference_jpg != null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false, error: (e as Error).message };
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user