feat(ext/sw): upload_attachment + download_attachment router handlers

Both popup-only. upload_attachment encrypts via WASM, putBlobs via
GitHost (Git Data API fallback for >900 KB), persists the AttachmentRef
on the item + manifest summaries. Duplicate uploads (same content =
same id from sha256) return the existing ref without a re-upload.
download_attachment reads + decrypts and returns plaintext bytes for
the popup to wrap in a Blob. 4 new router tests (accept × 2, reject × 2).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-04-25 16:04:06 -04:00
parent 559c881dca
commit 5217d04034
3 changed files with 145 additions and 4 deletions

View File

@@ -1,6 +1,6 @@
import type {
Item, ItemId, Manifest, ManifestEntry, VaultConfig, SetupState,
DeviceSettings, GeneratorRequest, VaultSettings,
DeviceSettings, GeneratorRequest, VaultSettings, AttachmentRef,
} from './types';
// --- Messages a popup (or setup page) may send ---
@@ -28,7 +28,9 @@ export type PopupMessage =
| { type: 'get_vault_settings' }
| { type: 'update_vault_settings'; settings: VaultSettings }
| { type: 'get_blacklist' }
| { type: 'remove_blacklist'; hostname: string };
| { type: 'remove_blacklist'; hostname: string }
| { type: 'upload_attachment'; itemId: string; filename: string; mimeType: string; bytes: ArrayBuffer }
| { type: 'download_attachment'; itemId: string; attachmentId: string };
// --- Messages a content script may send ---
@@ -95,6 +97,14 @@ export interface VaultSettingsResponse extends Extract<Response, { ok: true }> {
data: { settings: VaultSettings };
}
export interface UploadAttachmentResponse extends Extract<Response, { ok: true }> {
data: { attachment: AttachmentRef };
}
export interface DownloadAttachmentResponse extends Extract<Response, { ok: true }> {
data: { bytes: ArrayBuffer; filename: string; mimeType: string };
}
// --- Capability sets (consumed by the router) ---
export const POPUP_ONLY_TYPES: ReadonlySet<PopupMessage['type']> = new Set([
@@ -104,7 +114,7 @@ export const POPUP_ONLY_TYPES: ReadonlySet<PopupMessage['type']> = new Set([
'fill_credentials',
'ack_autofill_origin', 'get_settings', 'update_settings',
'get_vault_settings', 'update_vault_settings', 'get_blacklist',
'remove_blacklist',
'remove_blacklist', 'upload_attachment', 'download_attachment',
] as PopupMessage['type'][]);
export const CONTENT_CALLABLE_TYPES: ReadonlySet<ContentMessage['type']> = new Set([