3872f604f17d
add shared/native.ts (isNativePlatform, nativePlugin); App.tsx subscribes to Capacitor App appStateChange and locks vault when background elapsed >= autoLockMinutes
- time
- 2026-05-07 02:19 UTC · 5 months ago
- machine
- koh2
- parent
- 16db45928fa3
↓ peach-browser-3872f604.tar.gzatlas at this save
files changed2 files
src/popup/App.tsx+34 −0src/shared/native.ts+23 −0src/popup/App.tsx+34 −0
@@ -25,6 +25,7 @@2525 type ShortcutAction,2626 } from "../shared/shortcuts";2727 import { generateTOTP } from "../crypto/totp";28+import { isNativePlatform, nativePlugin } from "../shared/native";2829 2930 export { sendMessage } from "../shared/bridge";3031 @@ -413,6 +414,39 @@413414 popupPort = chrome.runtime.connect({ name: "popup" });414415 } catch { /* extension context invalidated — ignore */ }415416 onCleanup(() => { try { popupPort?.disconnect(); } catch {} });417+418+ // Android autolock. chrome.alarms is no-op'd on Capacitor, so the only419+ // reliable signal is appStateChange — setTimeout is killed when the OS420+ // backgrounds the app. We measure elapsed background time on resume.421+ if (isNativePlatform()) {422+ let backgroundedAt: number | null = null;423+ const CapApp = nativePlugin<{424+ addListener: (425+ ev: string,426+ cb: (s: { isActive: boolean }) => void,427+ ) => Promise<{ remove: () => Promise<void> }> | { remove: () => Promise<void> };428+ }>("App");429+ if (CapApp?.addListener) {430+ const sub = CapApp.addListener(431+ "appStateChange",432+ ({ isActive }: { isActive: boolean }) => {433+ if (!isActive) {434+ backgroundedAt = Date.now();435+ return;436+ }437+ if (backgroundedAt !== null && autoLockMinutes() > 0) {438+ const elapsed = Date.now() - backgroundedAt;439+ if (elapsed >= autoLockMinutes() * 60_000) handleHeaderLock();440+ }441+ backgroundedAt = null;442+ }443+ );444+ // Capacitor 6 listeners return a Promise<{ remove: () => Promise<void> }>.445+ onCleanup(() => {446+ Promise.resolve(sub).then((s) => s?.remove?.()).catch(() => {});447+ });448+ }449+ }416450 417451 // Background auto-lock broadcasts VAULT_LOCKED so the popup drops stale418452 // entries instead of waiting for the next GET_ENTRIES round-trip.src/shared/native.ts+23 −0
@@ -0,0 +1,23 @@1+// Mobile platform detection helpers used by popup views to gate Capacitor-only paths.2+// Kept tiny and runtime-only — never import @capacitor/core at module scope, since3+// desktop builds must not pull it.4+5+// eslint-disable-next-line @typescript-eslint/no-explicit-any6+type Cap = { isNativePlatform?: () => boolean; Plugins?: Record<string, any> };7+8+function cap(): Cap | null {9+ const g = globalThis as unknown as { Capacitor?: Cap };10+ return g.Capacitor ?? null;11+}12+13+export function isNativePlatform(): boolean {14+ const c = cap();15+ if (!c) return false;16+ if (typeof c.isNativePlatform === "function") return c.isNativePlatform();17+ return false;18+}19+20+// eslint-disable-next-line @typescript-eslint/no-explicit-any21+export function nativePlugin<T = any>(name: string): T | null {22+ return (cap()?.Plugins?.[name] as T | undefined) ?? null;23+}