fix(extension): binary-safe message transport + carrier-image guard
chrome.runtime.sendMessage serializes with a JSON-like algorithm that drops ArrayBuffer/TypedArray payloads — they arrive as empty objects, so a carrier JPEG reached the service worker as 0 bytes and failed with the opaque WASM error "no SOF marker found in JPEG". This broke extension vault creation. - shared/message-binary.ts: encodeBinary/decodeBinary deep-walk a message tree and base64-envelope every binary buffer so it survives the channel (chunked base64 to avoid stack overflow on multi-MB buffers); plain number[] untouched. - Wire encode/decode at all four message boundaries: popup, service-worker onMessage listener (decode request, encode response), setup swSend, vault postToServiceWorker. - handleCreateVault: magic-byte guard classifies empty / non-JPEG carriers into actionable errors (carrier_image_empty / carrier_image_not_jpeg) instead of the opaque WASM error; error-copy.ts adds user-facing text; setup errors now route through lookupErrorCopy. - router SETUP_ALLOWED: add create_vault / attach_vault / generate_recovery_qr — the Phase-3 setup wizard sends these from the setup page and they were being rejected as unauthorized_sender. - Tests: message-binary round-trip suite (7) + carrier-guard (2) + setup allowlist (3). 435/435 vitest, build:all clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pe8qw5KePDqAEBsAxnVQuJ
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
import type { Request, Response, SessionTimeoutConfig } from '../shared/messages';
|
||||
import type { RouterState } from './router/index';
|
||||
import { route } from './router/index';
|
||||
import { encodeBinary, decodeBinary } from '../shared/message-binary';
|
||||
import * as vault from './vault';
|
||||
import { clearCurrent } from './session';
|
||||
import * as sessionTimer from './session-timer';
|
||||
@@ -74,7 +75,10 @@ chrome.commands.onCommand.addListener((command) => {
|
||||
});
|
||||
|
||||
chrome.runtime.onMessage.addListener(
|
||||
(request: Request, sender: chrome.runtime.MessageSender, sendResponse: (r: Response) => void) => {
|
||||
(rawRequest: Request, sender: chrome.runtime.MessageSender, sendResponse: (r: Response) => void) => {
|
||||
// Binary payloads (carrier/reference images, attachments, backups) cross
|
||||
// the channel as base64 envelopes — decode before routing. See message-binary.ts.
|
||||
const request = decodeBinary(rawRequest);
|
||||
(async () => {
|
||||
// Plan C Phase 5: invert the reset rule. Reset on every message
|
||||
// except a documented passive-read exclusion set, so an active
|
||||
@@ -94,7 +98,8 @@ chrome.runtime.onMessage.addListener(
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`[relicario sw] ${request.type} -> error:`, r.error);
|
||||
}
|
||||
sendResponse(r);
|
||||
// Re-encode binary in the response so it survives the channel back.
|
||||
sendResponse(encodeBinary(r));
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
Reference in New Issue
Block a user