feat(ext/sw): parse + commit handlers for LastPass import

parse_lastpass_csv is a pure pass-through to the WASM bridge.
import_lastpass_commit re-mints each item's ID via
state.wasm.new_item_id() (same pattern as add_item), encrypts
and writes per-item via git.writeFile, then writes the manifest
last. Per-item commits + a final manifest commit — extension
GitHost has no atomic-batch API, so the single-commit semantics
the CLI provides aren't replicable here.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-04-29 23:30:26 -04:00
parent fbd029e4cb
commit b29a138411

View File

@@ -507,6 +507,52 @@ export async function handle(
return { ok: false, error: (e as Error).message }; return { ok: false, error: (e as Error).message };
} }
} }
case 'parse_lastpass_csv': {
try {
const json: string = state.wasm.parse_lastpass_csv_json(new Uint8Array(msg.bytes));
const parsed = JSON.parse(json) as {
items: Item[];
warnings: Array<{ row: number; title?: string; message: string }>;
};
return { ok: true, data: parsed };
} catch (e) {
return { ok: false, error: (e as Error).message };
}
}
case 'import_lastpass_commit': {
const handle = session.getCurrent();
if (!handle || !state.gitHost || !state.manifest) return { ok: false, error: 'vault_locked' };
if (msg.items.length === 0) return { ok: false, error: 'no items to import' };
try {
const total = msg.items.length;
for (let i = 0; i < msg.items.length; i++) {
const item = msg.items[i];
// Items arrive with IDs already minted by the WASM bridge. We
// overwrite that with a fresh extension-generated ID so the SW
// remains the single ID-issuance authority for new items in the
// remote — same pattern as `add_item`.
const id = state.wasm.new_item_id();
const reIdItem: Item = { ...item, id };
await vault.encryptAndWriteItem(
state.gitHost, handle, id, reIdItem,
`import: ${reIdItem.title} (${i + 1}/${total})`,
);
state.manifest.items[id] = itemToManifestEntry(reIdItem);
}
await vault.encryptAndWriteManifest(
state.gitHost, handle, state.manifest,
`manifest: import ${total} items from LastPass`,
);
return { ok: true, data: { summary: { itemCount: total } } };
} catch (e) {
return { ok: false, error: (e as Error).message };
}
}
} }
} }