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
1 file src/background/messages.ts+48 −21
src/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 {

view file