9dbaabc827ac
consolidate native helpers in messages.ts: import isNativePlatform/nativePlugin from shared/native; resolve Capacitor SyncPlugin at call time via syncBridge(); fix buildIdentitySuggestions to surface real entry email per username
- time
- 2026-05-09 00:56 UTC · 5 months ago
- machine
- koh2
- parent
- d1b56b8c77de
↓ peach-browser-9dbaabc8.tar.gzatlas at this save
files changed1 file
src/background/messages.ts+48 −21src/background/messages.ts+48 −21
@@ -63,6 +63,7 @@6363 } from "../shared/types";6464 import { isPasskeyOnly } from "../shared/types";6565 import type { SyncStatus } from "../shared/sync-types";66+import { isNativePlatform, nativePlugin } from "../shared/native";6667 6768 /**6869 * The single Pro gate. Every gated handler calls this first; the broadcast@@ -933,7 +934,7 @@933934 if (!gate.allowed) return { ok: false, error: "pro_required" };934935 if (!vault.isUnlocked()) return { ok: false, error: "vault_locked" };935936 if (isNativePlatform()) {936- bridge?.ensureServerRunning?.();937+ syncBridge()?.ensureServerRunning?.();937938 return { ok: true, data: { syncedCount: 0, mode: "server" as const } };938939 }939940 // Desktop has no outbound sync path — paired devices are mobile, and@@ -972,7 +973,7 @@972973 return { ok: false, error: "android_only" };973974 }974975 const { token } = msg.payload as { token: string };975- bridge?.startPairingSession?.({ token });976+ syncBridge()?.startPairingSession?.({ token });976977 return { ok: true };977978 }978979 // Android: returns the phone's WiFi IP address.@@ -980,7 +981,7 @@980981 if (!isNativePlatform()) {981982 return { ok: false, error: "android_only" };982983 }983- const { ip } = (await bridge?.getLocalIp?.()) ?? { ip: "" };984+ const { ip } = (await syncBridge()?.getLocalIp?.()) ?? { ip: "" };984985 return { ok: true, data: { ip } };985986 }986987 case "GET_IDENTITY_SUGGESTIONS": {@@ -1011,8 +1012,20 @@10111012 10121013 // Android: populates the in-memory vault used by PeachAutofillService.10131014 async function publishInMemoryVault(entries: VaultEntry[]): Promise<void> {1014- // eslint-disable-next-line @typescript-eslint/no-explicit-any1015- const plugin = (globalThis as any).Capacitor?.Plugins?.InMemoryVaultPlugin;1015+ const plugin = nativePlugin<{1016+ setEntries?: (args: {1017+ entries: Array<{1018+ id: string;1019+ title: string;1020+ username: string;1021+ email: string;1022+ password: string;1023+ url: string;1024+ totp: string;1025+ additionalUrls: string[];1026+ }>;1027+ }) => Promise<void>;1028+ }>("InMemoryVaultPlugin");10161029 if (!plugin?.setEntries) return;10171030 await plugin.setEntries({10181031 entries: entries.map((e) => ({@@ -1030,33 +1043,47 @@10301043 10311044 // Android: clear the autofill in-memory store on lock.10321045 async function clearInMemoryVault(): Promise<void> {1033- // eslint-disable-next-line @typescript-eslint/no-explicit-any1034- const plugin = (globalThis as any).Capacitor?.Plugins?.InMemoryVaultPlugin;1046+ const plugin = nativePlugin<{ clearEntries?: () => Promise<void> }>("InMemoryVaultPlugin");10351047 if (!plugin?.clearEntries) return;10361048 await plugin.clearEntries();10371049 }10381050 1039-// Returns all emails and usernames from the vault, deduped and sorted by frequency.1051+interface SyncBridge {1052+ ensureServerRunning?: () => void;1053+ startPairingSession?: (args: { token: string }) => void;1054+ getLocalIp?: () => Promise<{ ip: string }>;1055+}1056+1057+// Resolve the Capacitor SyncPlugin at call time — module-load resolution1058+// races with Capacitor's plugin registration on the Android WebView.1059+function syncBridge(): SyncBridge | null {1060+ return nativePlugin<SyncBridge>("SyncPlugin");1061+}1062+1063+// Returns username use-counts from the vault, sorted by frequency. Each row1064+// also carries the entry's email so the popup can suggest the email/username1065+// pair the user has actually used together.10401066 function buildIdentitySuggestions(10411067 entries: VaultEntry[],10421068 ): Array<{ email: string; username: string; count: number }> {1043- const map = new Map<string, number>();1069+ const usernameCounts = new Map<string, number>();1070+ const usernameEmail = new Map<string, string>();10441071 for (const entry of entries) {1045- if (entry.username) map.set(entry.username, (map.get(entry.username) ?? 0) + 1);1072+ const username = entry.username ?? "";1073+ const email = (entry as VaultEntry & { email?: string }).email ?? "";1074+ if (!username && !email) continue;1075+ const key = username || email;1076+ usernameCounts.set(key, (usernameCounts.get(key) ?? 0) + 1);1077+ if (!usernameEmail.has(key)) usernameEmail.set(key, email);10461078 }1047- return Array.from(map.entries())1079+ return Array.from(usernameCounts.entries())10481080 .sort((a, b) => b[1] - a[1])1049- .map(([username, count]) => ({ email: username, username, count }));1081+ .map(([username, count]) => ({1082+ email: usernameEmail.get(username) ?? "",1083+ username,1084+ count,1085+ }));10501086 }1051-1052-// Mobile bridge — runtime check to avoid importing Capacitor at the top level.1053-// eslint-disable-next-line @typescript-eslint/no-explicit-any1054-const bridge = (typeof (globalThis as any).Capacitor !== "undefined")1055- ? (globalThis as any).Capacitor.Plugins.SyncPlugin1056- : null;1057-1058-// eslint-disable-next-line @typescript-eslint/no-explicit-any1059-const isNativePlatform = (): boolean => typeof (globalThis as any).Capacitor !== "undefined" && (globalThis as any).isNativePlatform();10601087 10611088 /** Notify all content scripts that the vault just became available. */10621089 function broadcastToContentScripts(message: { type: string }): void {