add Touch ID wallet unlock via Swift biometric helper + Bun spawn

This commit is contained in:
2026-05-18 03:57:00 +08:00
parent d0648d62ef
commit b130e92294
9 changed files with 152 additions and 7 deletions
+23
View File
@@ -27,6 +27,7 @@ import {
getBinaryDir,
MoneroWalletManager,
} from './lib/monero'
import { canPromptTouchID, promptTouchID } from './lib/biometric'
const env = getENV()
@@ -133,6 +134,14 @@ type TxEntry = {
type RPC = {
bun: RPCSchema<{
requests: {
biometricCanAuth: {
params: {}
response: boolean
}
biometricAuth: {
params: { reason: string }
response: boolean
}
getSecret: {
params: {
service: string
@@ -357,6 +366,20 @@ const rpc = BrowserView.defineRPC<RPC>({
maxRequestTime: 120000,
handlers: {
requests: {
biometricCanAuth: async () => {
try {
return canPromptTouchID()
} catch {
return false
}
},
biometricAuth: async ({ reason }) => {
try {
return promptTouchID(reason)
} catch {
return false
}
},
getSecret: async ({ name, service }) => {
return await Bun.secrets.get({ name, service })
},
+31
View File
@@ -0,0 +1,31 @@
import { existsSync } from 'fs'
import { join } from 'path'
import { fileURLToPath } from 'url'
import { PATHS } from 'electrobun/bun'
const __dirname = fileURLToPath(new URL('.', import.meta.url))
function findHelper(): string {
const candidates = [
join(__dirname, '..', 'native', 'biometric-helper'),
join(PATHS.RESOURCES_FOLDER, 'native', 'biometric-helper'),
]
for (const p of candidates) {
if (existsSync(p)) return p
}
throw new Error(
`biometric-helper not found at any of:\n${candidates.join('\n')}`,
)
}
const HELPER_PATH = findHelper()
export function canPromptTouchID(): boolean {
const result = Bun.spawnSync([HELPER_PATH, '--check'])
return result.exitCode === 0
}
export function promptTouchID(reason: string): boolean {
const result = Bun.spawnSync([HELPER_PATH, reason])
return result.exitCode === 0
}
Binary file not shown.
@@ -0,0 +1,32 @@
import Foundation
import LocalAuthentication
let context = LAContext()
var error: NSError?
let available = context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
let isCheck = CommandLine.arguments.contains("--check")
if isCheck {
print(available)
exit(available ? 0 : 1)
}
guard available else {
print(error!.localizedDescription)
exit(1)
}
let reason = CommandLine.arguments.last ?? "Authenticate"
let sem = DispatchSemaphore(value: 0)
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authError in
if success {
exit(0)
}
if let err = authError {
print(err.localizedDescription)
}
exit(1)
}
_ = sem.wait(timeout: .distantFuture)