f284ee4f084d

refactor: Remove unused runtime seams (v4.10.35)

- Delete superseded vault, revocation, password-policy, passkey, and OTP helpers - Preserve legacy import fixtures and pending-vouch compatibility readers - Remove speculative relay and card-only types with no consumers

time
2026-08-29 12:39 UTC · 1 month ago
machine
koh2
parent
703d47477a7b
19 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/messages.test.ts+12 −13src/background/paired-devices.test.ts+0 −10src/background/paired-devices.ts+0 −13src/background/passkey-crypto.ts+0 −5src/background/pending-vouched-devices.test.ts+2 −9src/background/pending-vouched-devices.ts+3 −23src/background/sync-static.test.ts+0 −16src/background/vault.ts+0 −32src/content/content.detect.test.ts+2 −47src/content/detect.ts+0 −28src/content/inject.ts+1 −1src/shared/generate.empty-pool.test.ts+4 −36src/shared/generate.test.ts+1 −34src/shared/generate.ts+0 −22src/shared/sync-types.ts+0 −22src/shared/types.ts+0 −9
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "4.10.34",4+  "version": "4.10.35",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

manifest.json+1 −1
@@ -2,7 +2,7 @@22   "manifest_version": 3,33   "minimum_chrome_version": "116",44   "name": "Peach Password Manager",5-  "version": "4.10.34",5+  "version": "4.10.35",66   "description": "Zero-knowledge password manager",77   "action": {88     "default_popup": "src/popup/index.html",

view file

package.json+1 −1
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "4.10.34",3+  "version": "4.10.35",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/background/messages.test.ts+12 −13
@@ -1073,19 +1073,18 @@10731073   // --- MV-7: vouched device confirmation handlers ---10741074 10751075   async function seedPendingVouched(deviceId: string) {1076-    const { addPendingVouchedDevice } = await import(1077-      "./pending-vouched-devices"1078-    );1079-    await addPendingVouchedDevice({1080-      deviceId,1081-      deviceName: "New Pixel",1082-      deviceType: "android",1083-      longTermPublicKeyHex: "e".repeat(64),1084-      signingPublicKeyHex: "a".repeat(64),1085-      wsPort: 51234,1086-      pairedAt: 1_700_000_000_020,1087-      receivedAt: 1_700_000_000_050,1088-      vouchedByDeviceId: "extension-issuer",1076+    await chrome.storage.local.set({1077+      peach_pending_vouched_devices_v1: [{1078+        deviceId,1079+        deviceName: "New Pixel",1080+        deviceType: "android",1081+        longTermPublicKeyHex: "e".repeat(64),1082+        signingPublicKeyHex: "a".repeat(64),1083+        wsPort: 51234,1084+        pairedAt: 1_700_000_000_020,1085+        receivedAt: 1_700_000_000_050,1086+        vouchedByDeviceId: "extension-issuer",1087+      }],10891088     });10901089   }10911090 

view file

src/background/paired-devices.test.ts+0 −10
@@ -7,7 +7,6 @@77   getLastSyncAt,88   listPairedDevices,99   markLastSyncedAt,10-  clearDeviceRevocation,1110   isDeviceRevoked,1211   listDeviceRevocations,1312   mergeDeviceRevocations,@@ -258,15 +257,6 @@258257       ]);259258       const [record] = await listDeviceRevocations();260259       expect(record.revokedAt).toBeLessThanOrEqual(Date.now() + 5 * 60 * 1000);261-    });262-263-    it("clearDeviceRevocation lifts the block so a direct re-pair can readmit", async () => {264-      await upsertPairedDevice(SAMPLE);265-      await revokeDeviceById(SAMPLE.deviceId, "this-device");266-      await clearDeviceRevocation(SAMPLE.deviceId);267-268-      expect(await isDeviceRevoked(SAMPLE.deviceId)).toBe(false);269-      expect(await listDeviceRevocations()).toHaveLength(0);270260     });271261 272262     it("startup repair keeps a newer direct pair and clears its older revocation", async () => {

view file

src/background/paired-devices.ts+0 −13
@@ -236,19 +236,6 @@236236 }237237 238238 /**239- * Clear a revocation. The ONLY legitimate caller is a direct, SAS-confirmed240- * re-pair: a physically present user on both devices is a stronger consent241- * signal than whatever created the original pairing. Never call this from a242- * path a remote peer can reach.243- */244-export async function clearDeviceRevocation(deviceId: string): Promise<void> {245-  const existing = await listDeviceRevocations();246-  const filtered = existing.filter((r) => r.deviceId !== deviceId);247-  if (filtered.length === existing.length) return;248-  await storeRevocations(filtered);249-}250-251-/**252239  * Repair the invalid paired+revoked overlap produced by older direct-pair253240  * handlers.254241  *

view file

src/background/passkey-crypto.ts+0 −5
@@ -352,10 +352,6 @@352352   ]);353353 }354354 355-export async function sha256(data: Uint8Array): Promise<Uint8Array> {356-  return new Uint8Array(await crypto.subtle.digest("SHA-256", toArrayBuffer(data)));357-}358-359355 /**360356  * Generate a fresh credential ID. WebAuthn requires the value be unguessable361357  * and unique per credential; 32 bytes from the CSPRNG satisfies both.@@ -367,4 +363,3 @@367363   const bytes = crypto.getRandomValues(new Uint8Array(32));368364   return { bytes, base64url: base64urlEncode(bytes) };369365 }370-

view file

src/background/pending-vouched-devices.test.ts+2 −9
@@ -1,6 +1,5 @@11 import { beforeEach, describe, expect, it } from "vitest";22 import {3-  addPendingVouchedDevice,43   listPendingVouchedDevices,54   removePendingVouchedDevice,65 } from "./pending-vouched-devices";@@ -46,16 +45,10 @@4645 describe("pending vouched devices", () => {4746   beforeEach(() => makeStorageStub());4847 49-  it("add + list + remove", async () => {50-    await addPendingVouchedDevice(SAMPLE);48+  it("lists and removes a pending record created by an older build", async () => {49+    await chrome.storage.local.set({ peach_pending_vouched_devices_v1: [SAMPLE] });5150     expect(await listPendingVouchedDevices()).toEqual([SAMPLE]);5251     await removePendingVouchedDevice(SAMPLE.deviceId);5352     expect(await listPendingVouchedDevices()).toEqual([]);54-  });55-56-  it("caps oversized peer device names", async () => {57-    await addPendingVouchedDevice({ ...SAMPLE, deviceName: "V".repeat(100) });58-    const [d] = await listPendingVouchedDevices();59-    expect(d.deviceName).toBe("V".repeat(64));6053   });6154 });

view file

src/background/pending-vouched-devices.ts+3 −23
@@ -1,19 +1,13 @@11 // Pending vouched devices — audit fix MV-7.22 //3-// A verified extension-signed device manifest may introduce a device this4-// install has never seen. Previously such devices were upserted straight5-// into the paired-devices registry (trustSource:"vouched") with no user6-// confirmation — making the extension a single point of total compromise7-// for the device graph. Now new vouched devices land HERE, sync-inert,8-// until the user confirms them in the popup (CONFIRM_VOUCHED_DEVICE) or9-// rejects them (REJECT_VOUCHED_DEVICE).3+// Compatibility reader for pending devices created by older builds. Current4+// same-vault manifests are admitted directly, but stored requests remain5+// actionable until the user confirms or rejects them in Settings.106 //117 // Storage: chrome.storage.local, same locality rules as paired-devices.ts —128 // these records contain public keys and must not roam through a third-party139 // sync back-end.1410 15-import { capDeviceName } from "../shared/sync-limits";16-1711 export interface PendingVouchedDevice {1812   deviceId: string;1913   deviceName: string;@@ -29,7 +23,6 @@2923 }3024 3125 const PENDING_KEY = "peach_pending_vouched_devices_v1";32-const MAX_PENDING = 50;3326 3427 export async function listPendingVouchedDevices(): Promise<PendingVouchedDevice[]> {3528   try {@@ -40,19 +33,6 @@4033   } catch {4134     return [];4235   }43-}44-45-/** Add or refresh a pending device (keyed by deviceId). Bounded queue. */46-export async function addPendingVouchedDevice(47-  device: PendingVouchedDevice,48-): Promise<void> {49-  // Vouch requests carry a peer-chosen name; bound it like every other50-  // inbound name so the pending queue cannot be grown without limit.51-  device = { ...device, deviceName: capDeviceName(device.deviceName) };52-  const pending = await listPendingVouchedDevices();53-  const next = pending.filter((d) => d.deviceId !== device.deviceId);54-  next.push(device);55-  await chrome.storage.local.set({ [PENDING_KEY]: next.slice(-MAX_PENDING) });5636 }5737 5838 export async function getPendingVouchedDevice(

view file

src/background/sync-static.test.ts+0 −16
@@ -34,22 +34,6 @@3434     expect(fnBody).not.toMatch(/triggerSyncIfReachable|TRIGGER_SYNC|fireSyncTrigger/);3535   });3636 37-  it("addEntryDirect / updateEntryDirect / deleteEntryDirect do not call fireSyncTrigger", () => {38-    const vaultSrc = src("./vault.ts");39-    for (const fn of ["addEntryDirect", "updateEntryDirect", "deleteEntryDirect"]) {40-      const start = vaultSrc.indexOf(`function ${fn}`);41-      if (start === -1) continue;42-      // Slice up to the start of the very next export declaration so we don't43-      // bleed into adjacent function bodies or their JSDoc comments.44-      const nextExport = vaultSrc.indexOf("\nexport ", start + fn.length);45-      const end = nextExport > start ? nextExport : start + 600;46-      const body = vaultSrc.slice(start, end);47-      expect(body, `${fn} must not call fireSyncTrigger`).not.toMatch(48-        /triggerSyncIfReachable|TRIGGER_SYNC|fireSyncTrigger/,49-      );50-    }51-  });52-5337   it("device registry is stored in chrome.storage.local, not chrome.storage.sync", () => {5438     const registrySrc = src("./paired-devices.ts");5539     expect(registrySrc).toContain("storage.local");

view file

src/background/vault.ts+0 −32
@@ -2306,38 +2306,6 @@23062306 }23072307 23082308 /**2309- * Internal write variants — bypass `fireSyncTrigger` so the sync layer can2310- * apply entries without cascading into another outbound sync round.2311- *2312- * INVARIANT: callers must not pass entries whose IDs collide with existing2313- * active (non-deleted) entries unless the intent is an explicit overwrite.2314- * These functions do not validate field-level constraints (password required,2315- * card number required, etc.) — they are trust-the-caller paths for sync and2316- * test harness use only.2317- */2318-export async function addEntryDirect(entry: VaultEntry): Promise<void> {2319-  if (!derivedKey) throw new Error("vault_locked");2320-  entries = [...entries, entry];2321-  await persistEntries("sync_received");2322-}2323-2324-export async function updateEntryDirect(patch: VaultEntry): Promise<void> {2325-  if (!derivedKey) throw new Error("vault_locked");2326-  const idx = entries.findIndex((e) => e.id === patch.id);2327-  if (idx === -1) throw new Error("entry_not_found");2328-  entries[idx] = { ...entries[idx], ...patch };2329-  await persistEntries("sync_received");2330-}2331-2332-export async function deleteEntryDirect(id: string, deletedAt: number): Promise<void> {2333-  if (!derivedKey) throw new Error("vault_locked");2334-  const idx = entries.findIndex((e) => e.id === id);2335-  if (idx === -1) throw new Error("entry_not_found");2336-  entries[idx] = { ...entries[idx], deletedAt, modifiedAt: deletedAt };2337-  await persistEntries("sync_received");2338-}2339-2340-/**23412309  * Apply a batch of remote entries + tombstones from a sync peer.23422310  * Last-write-wins by `modifiedAt`; a tombstone supersedes a live entry when23432311  * `deletedAt > modifiedAt` on the local side. Mutates `entries` directly and

view file

src/content/content.detect.test.ts+2 −47
@@ -1,7 +1,6 @@11 // @vitest-environment jsdom22 //33 // Test File 7 — detect.ts field detection (T-DT01–T-DT21).4-// Plus Addendum 1 — split-OTP grouping.54 //65 // detect.ts is the foundation of the whole autofill layer: every gate in76 // index.ts asks it "is this a login/signup/email-first/card/totp form?". A@@ -307,7 +306,7 @@307306   });308307 });309308 310-// ── Addendum 1 — split-OTP grouping ─────────────────────────────────────────309+// ── Split-OTP grouping ──────────────────────────────────────────────────────311310 // The existing detector is findOTPDigitBoxGroup: it returns the ordered set of312311 // single-character boxes (4–10 of them) or null. index.ts uses it to open ONE313312 // TOTP selector anchored on the first box instead of icon-spamming all six.@@ -335,9 +334,7 @@335334     expect(isTOTPField(box)).toBe(false);336335   });337336 338-  // T-DT21: a group below the 4-box floor is not an OTP group (existing339-  // threshold). Addendum 1 proposes a 3+ threshold for isSplitOtpGroup — see the340-  // skipped block below.337+  // T-DT21: a group below the 4-box floor is not an OTP group.341338   it("T-DT21: only 2 single-char boxes → not an OTP group (null)", () => {342339     const f = form();343340     const boxes = [@@ -407,47 +404,5 @@407404     expect(isTOTPField(boxes[0]!)).toBe(false);408405     expect(findOtpSignalingGroup(boxes[0]!)).toBeNull();409406     expect(findOTPDigitBoxGroup(boxes[0]!)).not.toBeNull();410-  });411-});412-413-// Addendum 1 PROPOSED API — isSplitOtpGroup(field) with a 3+ threshold and a414-// numeric-only guard so an initials form (3 single-char *alpha* inputs) is not415-// treated as OTP. This function does not exist yet; these tests activate the416-// moment it is exported from detect.ts. They are the executable spec for the417-// addendum. See INTEGRATION-CONTRACT.md.418-import * as detectModule from "./detect";419-const hasIsSplitOtpGroup =420-  typeof (detectModule as unknown as { isSplitOtpGroup?: unknown }).isSplitOtpGroup === "function";421-422-describe.skipIf(!hasIsSplitOtpGroup)("Addendum 1 — proposed isSplitOtpGroup (requires detect.ts change)", () => {423-  const isSplitOtpGroup = (detectModule as unknown as {424-    isSplitOtpGroup: (f: HTMLInputElement) => boolean;425-  }).isSplitOtpGroup;426-427-  it("T-DT18 (proposed): 6 adjacent numeric single-char inputs → true", () => {428-    const f = form();429-    let first: HTMLInputElement | null = null;430-    for (let i = 0; i < 6; i++) {431-      const b = makeInput({ type: "text", maxlength: "1", inputmode: "numeric" }, f);432-      first ??= b;433-    }434-    expect(isSplitOtpGroup(first!)).toBe(true);435-  });436-437-  it("T-DT20 (proposed): 3 single-char ALPHA inputs (initials) → false", () => {438-    const f = form();439-    let first: HTMLInputElement | null = null;440-    for (let i = 0; i < 3; i++) {441-      const b = makeInput({ type: "text", maxlength: "1" }, f); // no numeric mode442-      first ??= b;443-    }444-    expect(isSplitOtpGroup(first!)).toBe(false);445-  });446-447-  it("T-DT21 (proposed): a 2-box group is below the 3+ threshold → false", () => {448-    const f = form();449-    const a = makeInput({ type: "text", maxlength: "1", inputmode: "numeric" }, f);450-    makeInput({ type: "text", maxlength: "1", inputmode: "numeric" }, f);451-    expect(isSplitOtpGroup(a)).toBe(false);452407   });453408 });

view file

src/content/detect.ts+0 −28
@@ -306,34 +306,6 @@306306   return digitBoxes;307307 }308308 309-// Addendum 1: true when `field` belongs to a split single-character OTP group —310-// 3+ maxlength=1 inputs in the same form that each carry a numeric signal311-// (inputmode=numeric, type=tel, pattern=[0-9]*/\d*, or autocomplete=one-time-code).312-// Distinct from findOTPDigitBoxGroup, which gates on a 4–10 box count and accepts313-// any text/number/tel box: this drops the floor to 3 but adds the numeric guard,314-// so a 3-letter "initials" form is never mistaken for OTP entry.315-//316-// No icon-suppression guard is wired at the injection sites: every injectFieldIcon317-// path already filters maxLength===1 fields upstream (detectLoginFields skips them,318-// isTOTPField rejects them), so a split-OTP box never reaches icon injection. This319-// export is the numeric-guarded classifier the T-DT18–21 spec pins and a shared320-// seam for any future gate that needs it.321-export function isSplitOtpGroup(field: HTMLInputElement): boolean {322-  if (!isSingleDigitBox(field)) return false;323-  const form = field.form;324-  if (!form) return false;325-  const siblings = Array.from(form.querySelectorAll<HTMLInputElement>("input")).filter(326-    (el) =>327-      isSingleDigitBox(el) &&328-      (el.inputMode === "numeric" ||329-        el.type === "tel" ||330-        el.pattern === "[0-9]*" ||331-        el.pattern === "\\d*" ||332-        el.autocomplete === "one-time-code")333-  );334-  return siblings.length >= 3 && siblings.includes(field);335-}336-337309 // Returns the ordered OTP-signaling inputs sharing `field`'s form when there are338310 // 4–10 of them — the split-digit-box pattern used by sites whose individual339311 // boxes lack maxlength="1" (so isSingleDigitBox / findOTPDigitBoxGroup miss them

view file

src/content/inject.ts+1 −1
@@ -3655,7 +3655,7 @@36553655 36563656 interface CardSelectorOptions {36573657   domain?: string;3658-  /** Caller-provided fill — receives the full CardEntry payload from the SW. */3658+  /** Caller-provided fill — receives the full card payload from the SW. */36593659   onFill: (entry: import("../shared/types").VaultEntry) => void;36603660   /** Called when the user explicitly dismisses (Escape, click-outside, ✕). */36613661   onDismiss?: () => void;

view file

src/shared/generate.empty-pool.test.ts+4 −36
@@ -1,7 +1,7 @@1-// T-025: generate.ts empty-pool guard, 128-char boundary, minLength > maxLength1+// T-025: generate.ts empty-pool guard and 128-char boundary22 //3-// Why these tests exist: generatePassword and generateSmartPassword have three4-// edge-case behaviors that are not tested:3+// Why these tests exist: generatePassword has two edge-case behaviors that are4+// not tested elsewhere:55 //66 //   1. All character classes forbidden → the pool falls back to lowercase77 //      ("abcdefghijklmnopqrstuvwxyz") rather than looping forever or throwing.@@ -11,16 +11,12 @@1111 //      exactly 128 characters when length=128 and must handle the boundary1212 //      without a performance cliff or off-by-one.1313 //14-//   3. minLength > maxLength in a site policy — generate.ts clamps in15-//      declaration order: raise to minLength first, then cap to maxLength.16-//      When minLength=50 and maxLength=16, the result must be exactly 16 chars.17-//1814 // These tests also serve as a regression guard against any future refactor of1915 // the rejection-sampling loop: if the loop condition changes, the 128-char2016 // case catches a potential performance regression (loop would run for 30+s).2117 2218 import { describe, expect, it } from "vitest";23-import { generatePassword, generateSmartPassword } from "./generate";19+import { generatePassword } from "./generate";2420 2521 describe("T-025: generatePassword edge cases", () => {2622   // T-025a: all character classes excluded via forbiddenChars → falls back to@@ -98,33 +94,5 @@9894     const b = generatePassword({ length: 32 });9995     // Collision probability: 1/(pool^32) ≈ 0 for any reasonable pool10096     expect(a).not.toBe(b);101-  });102-});103-104-describe("T-025: generateSmartPassword policy clamping", () => {105-  // T-025f: minLength > maxLength — generateSmartPassword clamps to maxLength.106-  // The evaluation order is: raise to minLength first (→ 50), then cap to107-  // maxLength (→ 16). Site policy maxLength must win.108-  //109-  // Bug this catches: swapping the clamp order so minLength wins over maxLength110-  // would produce a password that violates the site's stated maximum, causing111-  // sign-up failure on strict sites.112-  it("T-025f: minLength > maxLength → maxLength wins (site maximum clamped)", () => {113-    const password = generateSmartPassword({ minLength: 50, maxLength: 16 });114-    expect(password.length).toBeLessThanOrEqual(16);115-  });116-117-  // T-025g: maxLength below the safety floor of 8 → floor applies.118-  // generateSmartPassword has `if (length < 8) length = 8` to prevent119-  // trivially weak passwords being auto-generated for restrictive sites.120-  it("T-025g: maxLength=4 → safety floor raises output to at least 8 chars", () => {121-    const password = generateSmartPassword({ maxLength: 4 });122-    expect(password.length).toBeGreaterThanOrEqual(8);123-  });124-125-  // T-025h: forbidsSymbols=true → no symbol characters in output.126-  it("T-025h: forbidsSymbols=true → no !@#$%^&* in password", () => {127-    const password = generateSmartPassword({ forbidsSymbols: true, minLength: 20 });128-    expect(password).not.toMatch(/[!@#$%^&*]/);12997   });13098 });

view file

src/shared/generate.test.ts+1 −34
@@ -1,5 +1,5 @@11 import { describe, expect, it } from "vitest";2-import { generatePassword, generateSmartPassword } from "./generate";2+import { generatePassword } from "./generate";33 44 describe("generatePassword", () => {55   it("produces a password of the requested length", () => {@@ -39,39 +39,6 @@3939       includeSymbols: false,4040     });4141     expect(pw).toHaveLength(16);42-  });43-});44-45-describe("generateSmartPassword", () => {46-  it("clamps to policy.maxLength", () => {47-    const pw = generateSmartPassword({ maxLength: 12 });48-    expect(pw.length).toBeLessThanOrEqual(12);49-  });50-51-  it("raises to policy.minLength", () => {52-    const pw = generateSmartPassword({ minLength: 32 });53-    expect(pw.length).toBeGreaterThanOrEqual(32);54-  });55-56-  it("excludes forbidden chars (Oracle-style)", () => {57-    const pw = generateSmartPassword({58-      maxLength: 40,59-      forbiddenChars: "`~<>\\",60-    });61-    expect(pw.length).toBeLessThanOrEqual(40);62-    for (const c of ["`", "~", "<", ">", "\\"]) {63-      expect(pw).not.toContain(c);64-    }65-  });66-67-  it("honors forbidsSymbols", () => {68-    const pw = generateSmartPassword({ forbidsSymbols: true });69-    expect(pw).toMatch(/^[A-Za-z0-9]+$/);70-  });71-72-  it("defaults to length 20 when policy is empty", () => {73-    const pw = generateSmartPassword({});74-    expect(pw).toHaveLength(20);7542   });7643 7744   it("rejection sampling still yields exact length and only pool chars (audit INFO — modulo bias)", () => {

view file

src/shared/generate.ts+0 −22
@@ -1,5 +1,3 @@1-import type { PasswordPolicy } from "./types";2-31 export interface GeneratorOptions {42   length: number;53   includeUppercase: boolean;@@ -52,24 +50,4 @@5250     }5351   }5452   return password;55-}56-57-/**58- * Generate a password that satisfies a detected site policy — clamps length59- * to maxLength, raises to minLength, honors forbidsSymbols, and excludes60- * forbiddenChars. Used by the inline popup when the user clicks "regen" on a61- * signup form with parseable requirements.62- */63-export function generateSmartPassword(policy: PasswordPolicy = {}): string {64-  const defaultLength = 20;65-  let length = defaultLength;66-  if (policy.minLength) length = Math.max(length, policy.minLength);67-  if (policy.maxLength) length = Math.min(length, policy.maxLength);68-  if (length < 8) length = 8;69-70-  return generatePassword({71-    length,72-    includeSymbols: !policy.forbidsSymbols,73-    forbiddenChars: policy.forbiddenChars,74-  });7553 }

view file

src/shared/sync-types.ts+0 −22
@@ -101,28 +101,6 @@101101   | { type: "SYNC_ERROR"; error: string };102102 103103 // ---------------------------------------------------------------------------104-// Browser-to-browser relay frames (phone forwards; see peach-android's105-// RelayFrames.kt / RelayHub.kt, the source of truth this mirrors — NOT the106-// older 4-frame PEACH_SYNC_RELAY_DESIGN.md sketch, which is stale).107-//108-// Type-only for now: nothing in this repo sends or handles these yet — no109-// browser can listen, so relaying laptop-to-laptop traffic through a shared110-// phone is unbuilt browser-side work, tracked separately. This exists so111-// that work starts from the real wire contract instead of the stale doc.112-// ---------------------------------------------------------------------------113-114-/** RelayMessage.targetDeviceId/peerDeviceId are the destination's Noise115- *  static public key as lowercase hex — NOT `PairedDevice.deviceId`. */116-export type RelayMessage =117-  | { type: "RELAY_REQUEST"; targetDeviceId: string }118-  | { type: "RELAY_ACCEPT" }119-  | { type: "RELAY_UNAVAILABLE" }120-  | { type: "RELAY_DISABLED" }121-  | { type: "RELAY_OPEN"; peerDeviceId: string }122-  | { type: "RELAY_FRAME"; payload: string }123-  | { type: "RELAY_CLOSE" };124-125-// ---------------------------------------------------------------------------126104 // v3 device manifest — propagates trust between mobile devices (v2 brief §4.3)127105 // ---------------------------------------------------------------------------128106 

view file

src/shared/types.ts+0 −9
@@ -195,15 +195,6 @@195195   deletedAt?: number;196196 }197197 198-export interface CardEntry extends VaultEntry {199-  type: "card";200-  cardholderName: string;201-  cardNumber: string;202-  expMonth: string;203-  expYear: string;204-  cvc: string;205-}206-207198 export interface PendingPasskeyRequest {208199   requestId: number;209200   type: "create" | "get";

view file