feat(ext/setup): unified device registration in Step 5; fixes silent dropped pubkey

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-04-27 18:34:35 -04:00
parent e79e80b000
commit 2756033bf9

View File

@@ -7,6 +7,7 @@
/// Step 5: Finish (download reference image, push config to extension or copy JSON) /// Step 5: Finish (download reference image, push config to extension or copy JSON)
import { createGitHost, uint8ArrayToBase64 } from '../service-worker/git-host'; import { createGitHost, uint8ArrayToBase64 } from '../service-worker/git-host';
import { addDevice } from '../service-worker/devices';
import { probeVault } from './probe'; import { probeVault } from './probe';
import type { VaultConfig } from '../shared/types'; import type { VaultConfig } from '../shared/types';
import type { SessionHandle } from 'relicario-wasm'; import type { SessionHandle } from 'relicario-wasm';
@@ -1042,16 +1043,21 @@ function renderStep5(): string {
repoPath: state.repoPath, repoPath: state.repoPath,
apiToken: state.apiToken, apiToken: state.apiToken,
}; };
const configJson = JSON.stringify(config, null, 2); const configJson = JSON.stringify(config, null, 2);
const isAttach = state.mode === 'attach';
return ` return `
<div class="wizard-step"> <div class="wizard-step">
<div class="success-box"> <div class="success-box">
<h3>vault created</h3> <h3>${isAttach ? 'device verified' : 'vault created'}</h3>
<p class="secondary">Your vault has been initialized and pushed to the repository.</p> <p class="secondary">
${isAttach
? 'Your passphrase and reference image decrypt the vault successfully.'
: 'Your vault has been initialized and pushed to the repository.'}
</p>
</div> </div>
${isAttach ? '' : `
<div class="form-group"> <div class="form-group">
<label class="label">reference image</label> <label class="label">reference image</label>
<p class="muted" style="margin-bottom:8px;"> <p class="muted" style="margin-bottom:8px;">
@@ -1060,14 +1066,15 @@ function renderStep5(): string {
</p> </p>
<button class="btn btn-primary" id="download-ref-btn">download reference.jpg</button> <button class="btn btn-primary" id="download-ref-btn">download reference.jpg</button>
</div> </div>
`}
${state.extensionDetected ? ` ${state.extensionDetected ? `
<div class="form-group" style="margin-top:16px;"> <div class="form-group" style="margin-top:16px;">
<label class="label">extension configuration</label> <label class="label">register this device</label>
<button class="btn btn-primary" id="push-config-btn" ${state.configPushed ? 'disabled' : ''}> <button class="btn btn-primary" id="push-config-btn" ${state.configPushed ? 'disabled' : ''}>
${state.configPushed ? 'config saved to extension' : 'save config to extension'} ${state.configPushed ? 'device registered' : (isAttach ? 'attach this device' : 'register this device')}
</button> </button>
${state.configPushed ? '<span class="test-result pass" style="margin-left:8px;">saved</span>' : ''} ${state.configPushed ? '<span class="test-result pass" style="margin-left:8px;">done</span>' : ''}
</div> </div>
` : ` ` : `
<div class="form-group" style="margin-top:16px;"> <div class="form-group" style="margin-top:16px;">
@@ -1098,7 +1105,8 @@ function attachStep5(): void {
}); });
document.getElementById('push-config-btn')?.addEventListener('click', async () => { document.getElementById('push-config-btn')?.addEventListener('click', async () => {
if (!state.referenceImageBytes) return; state.error = null;
render();
const config: VaultConfig = { const config: VaultConfig = {
hostType: state.hostType, hostType: state.hostType,
@@ -1107,39 +1115,55 @@ function attachStep5(): void {
apiToken: state.apiToken, apiToken: state.apiToken,
}; };
const imageBase64 = uint8ArrayToBase64(state.referenceImageBytes);
try { try {
chrome.runtime.sendMessage(
{ type: 'save_setup', config, imageBase64 },
async (response: { ok: boolean; error?: string }) => {
if (response?.ok) {
state.configPushed = true;
// Generate device keypair and register
const w = await loadWasm(); const w = await loadWasm();
const keypair = JSON.parse(w.generate_device_keypair()) as { public_key_hex: string; private_key_base64: string }; const keypair = JSON.parse(w.generate_device_keypair()) as {
public_key_hex: string; private_key_base64: string;
};
// Store private key locally // 1) Save private key + name locally.
await chrome.storage.local.set({ await chrome.storage.local.set({
device_name: state.deviceName, device_name: state.deviceName,
device_private_key: keypair.private_key_base64, device_private_key: keypair.private_key_base64,
}); });
// Register device with vault // 2) Save vault config + reference image to extension storage.
chrome.runtime.sendMessage({ const imageBytes = state.referenceImageBytes ?? state.referenceImageBytesAttach;
type: 'add_device', const imageBase64 = imageBytes ? uint8ArrayToBase64(imageBytes) : '';
name: state.deviceName, const saveOk = await new Promise<boolean>((resolve) => {
public_key: keypair.public_key_hex, chrome.runtime.sendMessage(
}); { type: 'save_setup', config, imageBase64 },
} else { (response: { ok: boolean; error?: string }) => {
if (!response?.ok) {
state.error = response?.error ?? 'Failed to save config to extension'; state.error = response?.error ?? 'Failed to save config to extension';
resolve(false); return;
} }
render(); resolve(true);
}, },
); );
});
if (!saveOk) { render(); return; }
// 3) Register device on the remote (read-modify-write devices.json).
const hostUrl = state.hostType === 'github' ? 'https://api.github.com' : state.hostUrl;
const host = createGitHost(state.hostType, hostUrl, state.repoPath, state.apiToken);
await addDevice(host, {
name: state.deviceName,
public_key: keypair.public_key_hex,
added_at: Math.floor(Date.now() / 1000),
});
// 4) Release any attach-mode WASM handle.
if (state.verifiedHandle !== null) {
try { w.lock(state.verifiedHandle); } catch { /* best effort */ }
state.verifiedHandle = null;
}
state.configPushed = true;
render();
} catch (err: unknown) { } catch (err: unknown) {
state.error = `Failed to communicate with extension: ${err instanceof Error ? err.message : String(err)}`; console.error('[relicario setup] register device failed:', err);
state.error = `Failed to register device: ${err instanceof Error ? err.message : String(err)}`;
render(); render();
} }
}); });