@thru/passkey 0.2.27 → 0.2.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/add-device.cjs +11 -2
- package/dist/auth/add-device.cjs.map +1 -1
- package/dist/auth/add-device.d.cts +12 -2
- package/dist/auth/add-device.d.ts +12 -2
- package/dist/auth/add-device.js +3 -1
- package/dist/auth.cjs +12 -3
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +1 -1
- package/dist/auth.d.ts +1 -1
- package/dist/auth.js +4 -2
- package/dist/auth.js.map +1 -1
- package/dist/{chunk-ZNBMADOM.js → chunk-L53S4RKQ.js} +2 -2
- package/dist/chunk-L53S4RKQ.js.map +1 -0
- package/dist/{chunk-KASTJBBY.js → chunk-NFK5D7EI.js} +15 -5
- package/dist/chunk-NFK5D7EI.js.map +1 -0
- package/dist/{chunk-TW7HANJM.js → chunk-YV4LITL4.js} +40 -5
- package/dist/chunk-YV4LITL4.js.map +1 -0
- package/dist/index.cjs +39 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mobile.cjs +1 -1
- package/dist/mobile.cjs.map +1 -1
- package/dist/mobile.js +1 -1
- package/dist/popup.d.cts +2 -2
- package/dist/popup.d.ts +2 -2
- package/dist/server.cjs +2 -2
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -2
- package/dist/server.js.map +1 -1
- package/dist/{types-BTTlCVrw.d.cts → types-CIzH-qtR.d.cts} +2 -0
- package/dist/{types-BTTlCVrw.d.ts → types-CIzH-qtR.d.ts} +2 -0
- package/dist/web.cjs +39 -4
- package/dist/web.cjs.map +1 -1
- package/dist/web.d.cts +2 -2
- package/dist/web.d.ts +2 -2
- package/dist/web.js +1 -1
- package/package.json +3 -3
- package/src/auth/add-device.ts +31 -3
- package/src/auth/index.ts +2 -1
- package/src/mobile/passkey.ts +1 -1
- package/src/server/create-wallet.test.ts +4 -0
- package/src/server/create-wallet.ts +3 -2
- package/src/sign.test.ts +48 -0
- package/src/sign.ts +52 -4
- package/src/types.ts +2 -0
- package/dist/chunk-KASTJBBY.js.map +0 -1
- package/dist/chunk-TW7HANJM.js.map +0 -1
- package/dist/chunk-ZNBMADOM.js.map +0 -1
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/auth/execute-tx.ts","../src/auth/use-passkey-auth.ts"],"sourcesContent":["import { base64UrlToBytes, bytesToBase64, bytesToHex } from '@thru/programs/passkey-manager';\nimport { signWithPasskey } from '../mobile/passkey';\nimport { touchPasskeyLastUsedAt } from '../mobile/storage';\n\nasync function readJson(response: Response): Promise<Record<string, unknown>> {\n try {\n return (await response.json()) as Record<string, unknown>;\n } catch {\n throw new Error(`Non-JSON response (HTTP ${response.status})`);\n }\n}\n\nexport async function executePasskeyTransaction<\n P extends Record<string, unknown>,\n R\n>(opts: {\n challengeUrl: string;\n submitUrl: string;\n params: P;\n credentialId: string;\n rpId: string;\n}): Promise<R> {\n let challengeRes: Response;\n try {\n challengeRes = await fetch(opts.challengeUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(opts.params),\n });\n } catch {\n throw new Error('Network request failed (challenge)');\n }\n\n const challengeData = await readJson(challengeRes);\n if (!challengeRes.ok || challengeData.success !== true) {\n throw new Error(\n typeof challengeData.error === 'string'\n ? challengeData.error\n : 'Failed to get challenge'\n );\n }\n\n if (typeof challengeData.challenge !== 'string') {\n throw new Error('Challenge response did not include a challenge');\n }\n\n const challengeBytes = base64UrlToBytes(challengeData.challenge);\n const signature = await signWithPasskey(\n opts.credentialId,\n challengeBytes,\n opts.rpId\n );\n await touchPasskeyLastUsedAt().catch((error) => {\n console.warn('Failed to update passkey last-used timestamp:', error);\n });\n\n const { success: _success, error: _error, ...challengeFields } = challengeData;\n\n let submitRes: Response;\n try {\n submitRes = await fetch(opts.submitUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ...opts.params,\n ...challengeFields,\n signatureR: bytesToHex(signature.signatureR),\n signatureS: bytesToHex(signature.signatureS),\n authenticatorData: bytesToBase64(signature.authenticatorData),\n clientDataJSON: bytesToBase64(signature.clientDataJSON),\n }),\n });\n } catch {\n throw new Error('Network request failed (submit)');\n }\n\n const submitData = await readJson(submitRes);\n if (!submitRes.ok || submitData.success !== true) {\n throw new Error(\n typeof submitData.error === 'string'\n ? submitData.error\n : 'Failed to submit transaction'\n );\n }\n\n return submitData as R;\n}\n","import { bytesToHex } from '@thru/programs/passkey-manager';\nimport { create } from 'zustand';\nimport { classifyPasskeyError } from '../mobile/errors';\nimport {\n authenticateWithDiscoverablePasskey,\n registerPasskey,\n signWithPasskey,\n} from '../mobile/passkey';\nimport {\n clearPasskeyMetadata,\n clearSession,\n getStoredAddress,\n getStoredPasskeyMetadata,\n getStoredUserId,\n hasStoredPasskey,\n hasStoredWallet,\n storePasskeyMetadata,\n storeWalletInfo,\n touchPasskeyLastUsedAt,\n} from '../mobile/storage';\nimport type {\n PasskeyAuthApiResponse,\n PasskeyAuthBoundStore,\n PasskeyAuthConfig,\n PasskeyAuthStore,\n PasskeyUser,\n} from './types';\n\nconst storeCache = new Map<string, PasskeyAuthBoundStore<any>>();\n\nfunction createStoreKey(config: PasskeyAuthConfig): string {\n return [config.apiUrl, config.alias ?? '', config.rpId ?? '', config.rpName ?? ''].join('::');\n}\n\nfunction buildDisplayName(address: string): string {\n return `${address.slice(0, 8)}...${address.slice(-4)}`;\n}\n\nfunction toPasskeyUser<TExtra>(\n user: PasskeyAuthApiResponse<TExtra>['user']\n): PasskeyUser<TExtra> {\n return {\n id: user.id,\n displayName: buildDisplayName(user.publicKey),\n tokenAccountAddress: user.tokenAccountAddress ?? null,\n extras: user.extras,\n };\n}\n\nasync function readJson<T>(response: Response): Promise<T> {\n return (await response.json()) as T;\n}\n\nasync function postJson<T>(\n url: string,\n body: Record<string, unknown>\n): Promise<T> {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n\n const data = await readJson<Record<string, unknown>>(response);\n if (!response.ok || data.success !== true) {\n throw new Error(\n typeof data.error === 'string' ? data.error : 'Request failed'\n );\n }\n\n return data as unknown as T;\n}\n\nasync function getCurrentUser<TExtra>(\n apiUrl: string,\n address: string\n): Promise<PasskeyAuthApiResponse<TExtra> | null> {\n const response = await fetch(`${apiUrl}/auth/me`, {\n headers: {\n 'Content-Type': 'application/json',\n 'x-wallet-address': address,\n },\n });\n\n if (response.status === 404) return null;\n\n const data = await readJson<Record<string, unknown>>(response);\n if (!response.ok || data.success !== true) {\n throw new Error(\n typeof data.error === 'string' ? data.error : 'Failed to fetch current user'\n );\n }\n\n return data as unknown as PasskeyAuthApiResponse<TExtra>;\n}\n\nexport function createPasskeyAuthStore<TExtra = Record<string, never>>(\n config: PasskeyAuthConfig\n): PasskeyAuthBoundStore<TExtra> {\n const resolvedAlias = config.alias ?? 'Thru Wallet';\n\n return create<PasskeyAuthStore<TExtra>>((set, get) => ({\n isAuthenticated: false,\n isLoading: false,\n isInitialized: false,\n hasExistingPasskey: false,\n needsNewPasskey: false,\n error: null,\n user: null,\n address: null,\n activeCredentialId: null,\n\n initialize: async () => {\n try {\n const hasPasskey = await hasStoredPasskey();\n\n if (hasPasskey) {\n const storedAddress = await getStoredAddress();\n if (storedAddress) {\n const user = await Promise.race([\n getCurrentUser<TExtra>(config.apiUrl, storedAddress),\n new Promise<null>((_, reject) =>\n setTimeout(() => reject(new Error('timeout')), 2000)\n ),\n ]).catch(() => undefined);\n\n if (user === null) {\n await clearSession();\n await clearPasskeyMetadata();\n set({ isInitialized: true, hasExistingPasskey: false });\n return;\n }\n } else {\n const metadata = await getStoredPasskeyMetadata();\n if (metadata && !metadata.publicKeyX && !metadata.publicKeyY) {\n await clearPasskeyMetadata();\n set({ isInitialized: true, hasExistingPasskey: false });\n return;\n }\n }\n }\n\n set({ isInitialized: true, hasExistingPasskey: hasPasskey });\n } catch (error) {\n console.error('Failed to initialize passkey auth:', error);\n set({ isInitialized: true, error: 'Failed to initialize wallet' });\n }\n },\n\n createWallet: async () => {\n set({ isLoading: true, error: null, needsNewPasskey: false });\n\n try {\n const tempId = `user-${Date.now()}`;\n const passkeyLabel = resolvedAlias.trim() || 'Thru Wallet';\n const { credentialId, publicKeyX, publicKeyY, rpId } =\n await registerPasskey(passkeyLabel, tempId, {\n rpId: config.rpId,\n rpName: config.rpName,\n });\n\n const now = new Date().toISOString();\n const pubkeyXHex = bytesToHex(publicKeyX);\n const pubkeyYHex = bytesToHex(publicKeyY);\n\n await storePasskeyMetadata({\n credentialId,\n publicKeyX: pubkeyXHex,\n publicKeyY: pubkeyYHex,\n rpId,\n label: passkeyLabel,\n createdAt: now,\n lastUsedAt: now,\n });\n\n const response = await postJson<PasskeyAuthApiResponse<TExtra>>(\n `${config.apiUrl}/auth/register-passkey-wallet`,\n {\n pubkeyX: pubkeyXHex,\n pubkeyY: pubkeyYHex,\n credentialId,\n }\n );\n\n const walletAddress = response.user.publicKey;\n await storeWalletInfo(\n walletAddress,\n response.user.id,\n response.user.tokenAccountAddress ?? undefined\n );\n\n set({\n isLoading: false,\n isAuthenticated: true,\n hasExistingPasskey: true,\n activeCredentialId: credentialId,\n address: walletAddress,\n user: toPasskeyUser(response.user),\n });\n\n return true;\n } catch (error) {\n if (classifyPasskeyError(error) === 'USER_CANCELLED') {\n set({ isLoading: false });\n return false;\n }\n\n console.error('Failed to create passkey wallet:', error);\n set({\n isLoading: false,\n error: error instanceof Error ? error.message : 'Failed to create wallet',\n });\n return false;\n }\n },\n\n unlockWithPasskey: async () => {\n set({ isLoading: true, error: null, needsNewPasskey: false });\n\n try {\n const metadata = await getStoredPasskeyMetadata();\n if (!metadata) throw new Error('No stored passkey found');\n\n await signWithPasskey(\n metadata.credentialId,\n crypto.getRandomValues(new Uint8Array(32)),\n metadata.rpId\n );\n await touchPasskeyLastUsedAt().catch((error) => {\n console.warn('Failed to update passkey last-used timestamp:', error);\n });\n\n const hasWallet = await hasStoredWallet();\n let walletAddress: string;\n let userId: string;\n let tokenAccountAddress: string | undefined;\n let response: PasskeyAuthApiResponse<TExtra> | null = null;\n\n if (hasWallet) {\n const storedAddress = await getStoredAddress();\n const storedUserId = await getStoredUserId();\n if (!storedAddress || !storedUserId) {\n throw new Error('Incomplete wallet data');\n }\n\n walletAddress = storedAddress;\n userId = storedUserId;\n\n const current = await getCurrentUser<TExtra>(config.apiUrl, walletAddress);\n if (current) {\n response = current;\n tokenAccountAddress = current.user.tokenAccountAddress ?? undefined;\n } else if (metadata.publicKeyX && metadata.publicKeyY) {\n response = await postJson<PasskeyAuthApiResponse<TExtra>>(\n `${config.apiUrl}/auth/register-passkey-wallet`,\n {\n pubkeyX: metadata.publicKeyX,\n pubkeyY: metadata.publicKeyY,\n credentialId: metadata.credentialId,\n }\n );\n walletAddress = response.user.publicKey;\n userId = response.user.id;\n tokenAccountAddress = response.user.tokenAccountAddress ?? undefined;\n await storeWalletInfo(walletAddress, userId, tokenAccountAddress);\n } else {\n await clearSession();\n await clearPasskeyMetadata();\n set({ isLoading: false, hasExistingPasskey: false, error: null });\n return false;\n }\n } else if (metadata.publicKeyX && metadata.publicKeyY) {\n response = await postJson<PasskeyAuthApiResponse<TExtra>>(\n `${config.apiUrl}/auth/register-passkey-wallet`,\n {\n pubkeyX: metadata.publicKeyX,\n pubkeyY: metadata.publicKeyY,\n credentialId: metadata.credentialId,\n }\n );\n walletAddress = response.user.publicKey;\n userId = response.user.id;\n tokenAccountAddress = response.user.tokenAccountAddress ?? undefined;\n await storeWalletInfo(walletAddress, userId, tokenAccountAddress);\n } else {\n const recovered = await postJson<PasskeyAuthApiResponse<TExtra>>(\n `${config.apiUrl}/auth/recover-passkey-wallet`,\n { credentialId: metadata.credentialId }\n ).catch(() => null);\n\n if (!recovered) {\n await clearSession();\n await clearPasskeyMetadata();\n set({ isLoading: false, hasExistingPasskey: false, error: null });\n return false;\n }\n\n response = recovered;\n walletAddress = recovered.user.publicKey;\n userId = recovered.user.id;\n tokenAccountAddress = recovered.user.tokenAccountAddress ?? undefined;\n await storeWalletInfo(walletAddress, userId, tokenAccountAddress);\n }\n\n set({\n isLoading: false,\n isAuthenticated: true,\n activeCredentialId: metadata.credentialId,\n address: walletAddress,\n user: response ? toPasskeyUser(response.user) : get().user,\n });\n\n return true;\n } catch (error) {\n console.error('Failed to unlock with passkey:', error);\n const kind = classifyPasskeyError(error);\n\n if (kind === 'USER_CANCELLED') {\n set({ isLoading: false });\n } else if (kind === 'NOT_FOUND') {\n await clearPasskeyMetadata();\n set({ isLoading: false, hasExistingPasskey: false, error: null });\n } else {\n set({\n isLoading: false,\n error: error instanceof Error ? error.message : 'Failed to unlock',\n });\n }\n\n return false;\n }\n },\n\n recoverWithDiscoverablePasskey: async () => {\n set({ isLoading: true, error: null, needsNewPasskey: false });\n\n try {\n const discovered = await authenticateWithDiscoverablePasskey({\n rpId: config.rpId,\n });\n\n if (!discovered) {\n set({ isLoading: false });\n return false;\n }\n\n const response = await postJson<PasskeyAuthApiResponse<TExtra>>(\n `${config.apiUrl}/auth/recover-passkey-wallet`,\n { credentialId: discovered.credentialId }\n ).catch(() => null);\n\n if (!response) {\n set({ isLoading: false, needsNewPasskey: true });\n return false;\n }\n\n const walletAddress = response.user.publicKey;\n const userId = response.user.id;\n const tokenAccountAddress = response.user.tokenAccountAddress ?? undefined;\n const now = new Date().toISOString();\n\n await storePasskeyMetadata({\n credentialId: discovered.credentialId,\n publicKeyX: '',\n publicKeyY: '',\n rpId: discovered.rpId,\n createdAt: now,\n lastUsedAt: now,\n });\n await storeWalletInfo(walletAddress, userId, tokenAccountAddress);\n\n set({\n isLoading: false,\n isAuthenticated: true,\n hasExistingPasskey: true,\n activeCredentialId: discovered.credentialId,\n address: walletAddress,\n user: toPasskeyUser(response.user),\n });\n\n return true;\n } catch (error) {\n console.error('Failed to recover with discoverable passkey:', error);\n set({\n isLoading: false,\n error:\n error instanceof Error ? error.message : 'Failed to recover wallet',\n });\n return false;\n }\n },\n\n logout: async () => {\n try {\n await clearSession();\n } catch (error) {\n console.error('Failed to clear passkey session:', error);\n }\n\n set({\n isAuthenticated: false,\n needsNewPasskey: false,\n user: null,\n address: null,\n activeCredentialId: null,\n });\n },\n\n clearError: () => set({ error: null }),\n dismissNewPasskey: () => set({ needsNewPasskey: false }),\n }));\n}\n\nexport function getPasskeyAuthStore<TExtra = Record<string, never>>(\n config: PasskeyAuthConfig\n): PasskeyAuthBoundStore<TExtra> {\n const key = createStoreKey(config);\n const cached = storeCache.get(key) as PasskeyAuthBoundStore<TExtra> | undefined;\n if (cached) return cached;\n\n const store = createPasskeyAuthStore<TExtra>(config);\n storeCache.set(key, store);\n return store;\n}\n\nexport function usePasskeyAuth<TExtra = Record<string, never>>(\n config: PasskeyAuthConfig\n): PasskeyAuthStore<TExtra> {\n return getPasskeyAuthStore<TExtra>(config)();\n}\n"],"mappings":"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","names":["bytesToHex","readJson","bytesToHex"]}
|
|
1
|
+
{"version":3,"sources":["../src/auth/execute-tx.ts","../src/auth/use-passkey-auth.ts"],"sourcesContent":["import { base64UrlToBytes, bytesToBase64, bytesToHex } from '@thru/programs/passkey-manager';\nimport { signWithPasskey } from '../mobile/passkey';\nimport { touchPasskeyLastUsedAt } from '../mobile/storage';\n\nasync function readJson(response: Response): Promise<Record<string, unknown>> {\n try {\n return (await response.json()) as Record<string, unknown>;\n } catch {\n throw new Error(`Non-JSON response (HTTP ${response.status})`);\n }\n}\n\nexport async function executePasskeyTransaction<\n P extends Record<string, unknown>,\n R\n>(opts: {\n challengeUrl: string;\n submitUrl: string;\n params: P;\n credentialId: string;\n rpId: string;\n}): Promise<R> {\n let challengeRes: Response;\n try {\n challengeRes = await fetch(opts.challengeUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(opts.params),\n });\n } catch {\n throw new Error('Network request failed (challenge)');\n }\n\n const challengeData = await readJson(challengeRes);\n if (!challengeRes.ok || challengeData.success !== true) {\n throw new Error(\n typeof challengeData.error === 'string'\n ? challengeData.error\n : 'Failed to get challenge'\n );\n }\n\n if (typeof challengeData.challenge !== 'string') {\n throw new Error('Challenge response did not include a challenge');\n }\n\n const challengeBytes = base64UrlToBytes(challengeData.challenge);\n const signature = await signWithPasskey(\n opts.credentialId,\n challengeBytes,\n opts.rpId\n );\n await touchPasskeyLastUsedAt().catch((error) => {\n console.warn('Failed to update passkey last-used timestamp:', error);\n });\n\n const { success: _success, error: _error, ...challengeFields } = challengeData;\n\n let submitRes: Response;\n try {\n submitRes = await fetch(opts.submitUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ...opts.params,\n ...challengeFields,\n signatureR: bytesToHex(signature.signatureR),\n signatureS: bytesToHex(signature.signatureS),\n authenticatorData: bytesToBase64(signature.authenticatorData),\n clientDataJSON: bytesToBase64(signature.clientDataJSON),\n }),\n });\n } catch {\n throw new Error('Network request failed (submit)');\n }\n\n const submitData = await readJson(submitRes);\n if (!submitRes.ok || submitData.success !== true) {\n throw new Error(\n typeof submitData.error === 'string'\n ? submitData.error\n : 'Failed to submit transaction'\n );\n }\n\n return submitData as R;\n}\n","import { bytesToHex } from '@thru/programs/passkey-manager';\nimport { create } from 'zustand';\nimport { classifyPasskeyError } from '../mobile/errors';\nimport {\n authenticateWithDiscoverablePasskey,\n registerPasskey,\n signWithPasskey,\n} from '../mobile/passkey';\nimport {\n clearPasskeyMetadata,\n clearSession,\n getStoredAddress,\n getStoredPasskeyMetadata,\n getStoredUserId,\n hasStoredPasskey,\n hasStoredWallet,\n storePasskeyMetadata,\n storeWalletInfo,\n touchPasskeyLastUsedAt,\n} from '../mobile/storage';\nimport type {\n PasskeyAuthApiResponse,\n PasskeyAuthBoundStore,\n PasskeyAuthConfig,\n PasskeyAuthStore,\n PasskeyUser,\n} from './types';\n\nconst storeCache = new Map<string, PasskeyAuthBoundStore<any>>();\n\nfunction createStoreKey(config: PasskeyAuthConfig): string {\n return [config.apiUrl, config.alias ?? '', config.rpId ?? '', config.rpName ?? ''].join('::');\n}\n\nfunction buildDisplayName(address: string): string {\n return `${address.slice(0, 8)}...${address.slice(-4)}`;\n}\n\nfunction toPasskeyUser<TExtra>(\n user: PasskeyAuthApiResponse<TExtra>['user']\n): PasskeyUser<TExtra> {\n return {\n id: user.id,\n displayName: buildDisplayName(user.publicKey),\n tokenAccountAddress: user.tokenAccountAddress ?? null,\n extras: user.extras,\n };\n}\n\nasync function readJson<T>(response: Response): Promise<T> {\n return (await response.json()) as T;\n}\n\nasync function postJson<T>(\n url: string,\n body: Record<string, unknown>\n): Promise<T> {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n\n const data = await readJson<Record<string, unknown>>(response);\n if (!response.ok || data.success !== true) {\n throw new Error(\n typeof data.error === 'string' ? data.error : 'Request failed'\n );\n }\n\n return data as unknown as T;\n}\n\nasync function getCurrentUser<TExtra>(\n apiUrl: string,\n address: string\n): Promise<PasskeyAuthApiResponse<TExtra> | null> {\n const response = await fetch(`${apiUrl}/auth/me`, {\n headers: {\n 'Content-Type': 'application/json',\n 'x-wallet-address': address,\n },\n });\n\n if (response.status === 404) return null;\n\n const data = await readJson<Record<string, unknown>>(response);\n if (!response.ok || data.success !== true) {\n throw new Error(\n typeof data.error === 'string' ? data.error : 'Failed to fetch current user'\n );\n }\n\n return data as unknown as PasskeyAuthApiResponse<TExtra>;\n}\n\nexport function createPasskeyAuthStore<TExtra = Record<string, never>>(\n config: PasskeyAuthConfig\n): PasskeyAuthBoundStore<TExtra> {\n const resolvedAlias = config.alias ?? 'Thru Wallet';\n\n return create<PasskeyAuthStore<TExtra>>((set, get) => ({\n isAuthenticated: false,\n isLoading: false,\n isInitialized: false,\n hasExistingPasskey: false,\n needsNewPasskey: false,\n error: null,\n user: null,\n address: null,\n activeCredentialId: null,\n\n initialize: async () => {\n try {\n const hasPasskey = await hasStoredPasskey();\n\n if (hasPasskey) {\n const storedAddress = await getStoredAddress();\n if (storedAddress) {\n const user = await Promise.race([\n getCurrentUser<TExtra>(config.apiUrl, storedAddress),\n new Promise<null>((_, reject) =>\n setTimeout(() => reject(new Error('timeout')), 2000)\n ),\n ]).catch(() => undefined);\n\n if (user === null) {\n await clearSession();\n await clearPasskeyMetadata();\n set({ isInitialized: true, hasExistingPasskey: false });\n return;\n }\n } else {\n const metadata = await getStoredPasskeyMetadata();\n if (metadata && !metadata.publicKeyX && !metadata.publicKeyY) {\n await clearPasskeyMetadata();\n set({ isInitialized: true, hasExistingPasskey: false });\n return;\n }\n }\n }\n\n set({ isInitialized: true, hasExistingPasskey: hasPasskey });\n } catch (error) {\n console.error('Failed to initialize passkey auth:', error);\n set({ isInitialized: true, error: 'Failed to initialize wallet' });\n }\n },\n\n createWallet: async () => {\n set({ isLoading: true, error: null, needsNewPasskey: false });\n\n try {\n const tempId = `user-${Date.now()}`;\n const passkeyLabel = resolvedAlias.trim() || 'Thru Wallet';\n const { credentialId, publicKeyX, publicKeyY, rpId } =\n await registerPasskey(passkeyLabel, tempId, {\n rpId: config.rpId,\n rpName: config.rpName,\n });\n\n const now = new Date().toISOString();\n const pubkeyXHex = bytesToHex(publicKeyX);\n const pubkeyYHex = bytesToHex(publicKeyY);\n\n await storePasskeyMetadata({\n credentialId,\n publicKeyX: pubkeyXHex,\n publicKeyY: pubkeyYHex,\n rpId,\n label: passkeyLabel,\n createdAt: now,\n lastUsedAt: now,\n });\n\n const response = await postJson<PasskeyAuthApiResponse<TExtra>>(\n `${config.apiUrl}/auth/register-passkey-wallet`,\n {\n pubkeyX: pubkeyXHex,\n pubkeyY: pubkeyYHex,\n credentialId,\n }\n );\n\n const walletAddress = response.user.publicKey;\n await storeWalletInfo(\n walletAddress,\n response.user.id,\n response.user.tokenAccountAddress ?? undefined\n );\n\n set({\n isLoading: false,\n isAuthenticated: true,\n hasExistingPasskey: true,\n activeCredentialId: credentialId,\n address: walletAddress,\n user: toPasskeyUser(response.user),\n });\n\n return true;\n } catch (error) {\n if (classifyPasskeyError(error) === 'USER_CANCELLED') {\n set({ isLoading: false });\n return false;\n }\n\n console.error('Failed to create passkey wallet:', error);\n set({\n isLoading: false,\n error: error instanceof Error ? error.message : 'Failed to create wallet',\n });\n return false;\n }\n },\n\n unlockWithPasskey: async () => {\n set({ isLoading: true, error: null, needsNewPasskey: false });\n\n try {\n const metadata = await getStoredPasskeyMetadata();\n if (!metadata) throw new Error('No stored passkey found');\n\n await signWithPasskey(\n metadata.credentialId,\n crypto.getRandomValues(new Uint8Array(32)),\n metadata.rpId\n );\n await touchPasskeyLastUsedAt().catch((error) => {\n console.warn('Failed to update passkey last-used timestamp:', error);\n });\n\n const hasWallet = await hasStoredWallet();\n let walletAddress: string;\n let userId: string;\n let tokenAccountAddress: string | undefined;\n let response: PasskeyAuthApiResponse<TExtra> | null = null;\n\n if (hasWallet) {\n const storedAddress = await getStoredAddress();\n const storedUserId = await getStoredUserId();\n if (!storedAddress || !storedUserId) {\n throw new Error('Incomplete wallet data');\n }\n\n walletAddress = storedAddress;\n userId = storedUserId;\n\n const current = await getCurrentUser<TExtra>(config.apiUrl, walletAddress);\n if (current) {\n response = current;\n tokenAccountAddress = current.user.tokenAccountAddress ?? undefined;\n } else if (metadata.publicKeyX && metadata.publicKeyY) {\n response = await postJson<PasskeyAuthApiResponse<TExtra>>(\n `${config.apiUrl}/auth/register-passkey-wallet`,\n {\n pubkeyX: metadata.publicKeyX,\n pubkeyY: metadata.publicKeyY,\n credentialId: metadata.credentialId,\n }\n );\n walletAddress = response.user.publicKey;\n userId = response.user.id;\n tokenAccountAddress = response.user.tokenAccountAddress ?? undefined;\n await storeWalletInfo(walletAddress, userId, tokenAccountAddress);\n } else {\n await clearSession();\n await clearPasskeyMetadata();\n set({ isLoading: false, hasExistingPasskey: false, error: null });\n return false;\n }\n } else if (metadata.publicKeyX && metadata.publicKeyY) {\n response = await postJson<PasskeyAuthApiResponse<TExtra>>(\n `${config.apiUrl}/auth/register-passkey-wallet`,\n {\n pubkeyX: metadata.publicKeyX,\n pubkeyY: metadata.publicKeyY,\n credentialId: metadata.credentialId,\n }\n );\n walletAddress = response.user.publicKey;\n userId = response.user.id;\n tokenAccountAddress = response.user.tokenAccountAddress ?? undefined;\n await storeWalletInfo(walletAddress, userId, tokenAccountAddress);\n } else {\n const recovered = await postJson<PasskeyAuthApiResponse<TExtra>>(\n `${config.apiUrl}/auth/recover-passkey-wallet`,\n { credentialId: metadata.credentialId }\n ).catch(() => null);\n\n if (!recovered) {\n await clearSession();\n await clearPasskeyMetadata();\n set({ isLoading: false, hasExistingPasskey: false, error: null });\n return false;\n }\n\n response = recovered;\n walletAddress = recovered.user.publicKey;\n userId = recovered.user.id;\n tokenAccountAddress = recovered.user.tokenAccountAddress ?? undefined;\n await storeWalletInfo(walletAddress, userId, tokenAccountAddress);\n }\n\n set({\n isLoading: false,\n isAuthenticated: true,\n activeCredentialId: metadata.credentialId,\n address: walletAddress,\n user: response ? toPasskeyUser(response.user) : get().user,\n });\n\n return true;\n } catch (error) {\n console.error('Failed to unlock with passkey:', error);\n const kind = classifyPasskeyError(error);\n\n if (kind === 'USER_CANCELLED') {\n set({ isLoading: false });\n } else if (kind === 'NOT_FOUND') {\n await clearPasskeyMetadata();\n set({ isLoading: false, hasExistingPasskey: false, error: null });\n } else {\n set({\n isLoading: false,\n error: error instanceof Error ? error.message : 'Failed to unlock',\n });\n }\n\n return false;\n }\n },\n\n recoverWithDiscoverablePasskey: async () => {\n set({ isLoading: true, error: null, needsNewPasskey: false });\n\n try {\n const discovered = await authenticateWithDiscoverablePasskey({\n rpId: config.rpId,\n });\n\n if (!discovered) {\n set({ isLoading: false });\n return false;\n }\n\n const response = await postJson<PasskeyAuthApiResponse<TExtra>>(\n `${config.apiUrl}/auth/recover-passkey-wallet`,\n { credentialId: discovered.credentialId }\n ).catch(() => null);\n\n if (!response) {\n set({ isLoading: false, needsNewPasskey: true });\n return false;\n }\n\n const walletAddress = response.user.publicKey;\n const userId = response.user.id;\n const tokenAccountAddress = response.user.tokenAccountAddress ?? undefined;\n const now = new Date().toISOString();\n\n await storePasskeyMetadata({\n credentialId: discovered.credentialId,\n publicKeyX: '',\n publicKeyY: '',\n rpId: discovered.rpId,\n createdAt: now,\n lastUsedAt: now,\n });\n await storeWalletInfo(walletAddress, userId, tokenAccountAddress);\n\n set({\n isLoading: false,\n isAuthenticated: true,\n hasExistingPasskey: true,\n activeCredentialId: discovered.credentialId,\n address: walletAddress,\n user: toPasskeyUser(response.user),\n });\n\n return true;\n } catch (error) {\n console.error('Failed to recover with discoverable passkey:', error);\n set({\n isLoading: false,\n error:\n error instanceof Error ? error.message : 'Failed to recover wallet',\n });\n return false;\n }\n },\n\n logout: async () => {\n try {\n await clearSession();\n } catch (error) {\n console.error('Failed to clear passkey session:', error);\n }\n\n set({\n isAuthenticated: false,\n needsNewPasskey: false,\n user: null,\n address: null,\n activeCredentialId: null,\n });\n },\n\n clearError: () => set({ error: null }),\n dismissNewPasskey: () => set({ needsNewPasskey: false }),\n }));\n}\n\nexport function getPasskeyAuthStore<TExtra = Record<string, never>>(\n config: PasskeyAuthConfig\n): PasskeyAuthBoundStore<TExtra> {\n const key = createStoreKey(config);\n const cached = storeCache.get(key) as PasskeyAuthBoundStore<TExtra> | undefined;\n if (cached) return cached;\n\n const store = createPasskeyAuthStore<TExtra>(config);\n storeCache.set(key, store);\n return store;\n}\n\nexport function usePasskeyAuth<TExtra = Record<string, never>>(\n config: PasskeyAuthConfig\n): PasskeyAuthStore<TExtra> {\n return getPasskeyAuthStore<TExtra>(config)();\n}\n"],"mappings":"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","names":["bytesToHex","readJson","bytesToHex"]}
|
|
@@ -128,7 +128,7 @@ import {
|
|
|
128
128
|
function getDefaultConfig(config) {
|
|
129
129
|
const env = globalThis.process?.env ?? {};
|
|
130
130
|
return {
|
|
131
|
-
rpId: config?.rpId ?? env.EXPO_PUBLIC_PASSKEY_RP_ID ?? "
|
|
131
|
+
rpId: config?.rpId ?? env.EXPO_PUBLIC_PASSKEY_RP_ID ?? "app.tid.sh",
|
|
132
132
|
rpName: config?.rpName ?? env.EXPO_PUBLIC_PASSKEY_RP_NAME ?? "Thru Wallet"
|
|
133
133
|
};
|
|
134
134
|
}
|
|
@@ -247,4 +247,4 @@ export {
|
|
|
247
247
|
authenticateWithDiscoverablePasskey,
|
|
248
248
|
extractP256Coordinates
|
|
249
249
|
};
|
|
250
|
-
//# sourceMappingURL=chunk-
|
|
250
|
+
//# sourceMappingURL=chunk-L53S4RKQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mobile/errors.ts","../src/mobile/storage.ts","../src/mobile/passkey.ts"],"sourcesContent":["const PASSKEY_ERRORS = {\n USER_CANCELLED: [\n 'error 1001',\n 'UserCancelled',\n 'Passkey authentication was cancelled',\n 'Passkey registration was cancelled',\n ],\n NOT_FOUND: [\n 'not found',\n 'No credentials available',\n 'no passkey',\n 'NoCredentials',\n ],\n} as const;\n\nexport type PasskeyErrorKind = keyof typeof PASSKEY_ERRORS;\n\nexport function classifyPasskeyError(error: unknown): PasskeyErrorKind | null {\n const message =\n error instanceof Error ? error.message : typeof error === 'string' ? error : null;\n\n if (!message) return null;\n\n for (const [kind, patterns] of Object.entries(PASSKEY_ERRORS)) {\n if (patterns.some((pattern) => message.includes(pattern))) {\n return kind as PasskeyErrorKind;\n }\n }\n\n return null;\n}\n","import * as SecureStore from 'expo-secure-store';\nimport type { PasskeyMetadata } from '@thru/programs/passkey-manager';\n\nconst SECURE_STORE_OPTS = {\n keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,\n} as const;\n\nconst PASSKEY_CREDENTIAL_ID_KEY = 'thru_passkey_credential_id';\nconst PASSKEY_PUBLIC_KEY_X_KEY = 'thru_passkey_pubkey_x';\nconst PASSKEY_PUBLIC_KEY_Y_KEY = 'thru_passkey_pubkey_y';\nconst PASSKEY_RP_ID_KEY = 'thru_passkey_rp_id';\nconst PASSKEY_LABEL_KEY = 'thru_passkey_label';\nconst PASSKEY_CREATED_AT_KEY = 'thru_passkey_created_at';\nconst PASSKEY_LAST_USED_AT_KEY = 'thru_passkey_last_used_at';\n\nconst ADDRESS_KEY = 'thru_address';\nconst USER_ID_KEY = 'thru_user_id';\nconst TOKEN_ACCOUNT_KEY = 'thru_token_account';\n\nexport async function storePasskeyMetadata(metadata: PasskeyMetadata): Promise<void> {\n await Promise.all([\n SecureStore.setItemAsync(PASSKEY_CREDENTIAL_ID_KEY, metadata.credentialId, SECURE_STORE_OPTS),\n SecureStore.setItemAsync(PASSKEY_PUBLIC_KEY_X_KEY, metadata.publicKeyX, SECURE_STORE_OPTS),\n SecureStore.setItemAsync(PASSKEY_PUBLIC_KEY_Y_KEY, metadata.publicKeyY, SECURE_STORE_OPTS),\n SecureStore.setItemAsync(PASSKEY_RP_ID_KEY, metadata.rpId, SECURE_STORE_OPTS),\n SecureStore.setItemAsync(PASSKEY_LABEL_KEY, metadata.label ?? '', SECURE_STORE_OPTS),\n SecureStore.setItemAsync(PASSKEY_CREATED_AT_KEY, metadata.createdAt, SECURE_STORE_OPTS),\n SecureStore.setItemAsync(PASSKEY_LAST_USED_AT_KEY, metadata.lastUsedAt, SECURE_STORE_OPTS),\n ]);\n}\n\nexport async function getStoredPasskeyMetadata(): Promise<PasskeyMetadata | null> {\n const credentialId = await SecureStore.getItemAsync(PASSKEY_CREDENTIAL_ID_KEY);\n if (!credentialId) return null;\n\n const [publicKeyX, publicKeyY, rpId, label, createdAt, lastUsedAt] = await Promise.all([\n SecureStore.getItemAsync(PASSKEY_PUBLIC_KEY_X_KEY),\n SecureStore.getItemAsync(PASSKEY_PUBLIC_KEY_Y_KEY),\n SecureStore.getItemAsync(PASSKEY_RP_ID_KEY),\n SecureStore.getItemAsync(PASSKEY_LABEL_KEY),\n SecureStore.getItemAsync(PASSKEY_CREATED_AT_KEY),\n SecureStore.getItemAsync(PASSKEY_LAST_USED_AT_KEY),\n ]);\n\n if (!rpId || !createdAt) return null;\n\n return {\n credentialId,\n publicKeyX: publicKeyX ?? '',\n publicKeyY: publicKeyY ?? '',\n rpId,\n label: label || undefined,\n createdAt,\n lastUsedAt: lastUsedAt ?? createdAt,\n };\n}\n\nexport async function touchPasskeyLastUsedAt(lastUsedAt = new Date().toISOString()): Promise<string> {\n await SecureStore.setItemAsync(PASSKEY_LAST_USED_AT_KEY, lastUsedAt, SECURE_STORE_OPTS);\n return lastUsedAt;\n}\n\nexport async function hasStoredPasskey(): Promise<boolean> {\n return (await SecureStore.getItemAsync(PASSKEY_CREDENTIAL_ID_KEY)) !== null;\n}\n\nexport async function clearPasskeyMetadata(): Promise<void> {\n await Promise.all([\n SecureStore.deleteItemAsync(PASSKEY_CREDENTIAL_ID_KEY),\n SecureStore.deleteItemAsync(PASSKEY_PUBLIC_KEY_X_KEY),\n SecureStore.deleteItemAsync(PASSKEY_PUBLIC_KEY_Y_KEY),\n SecureStore.deleteItemAsync(PASSKEY_RP_ID_KEY),\n SecureStore.deleteItemAsync(PASSKEY_LABEL_KEY),\n SecureStore.deleteItemAsync(PASSKEY_CREATED_AT_KEY),\n SecureStore.deleteItemAsync(PASSKEY_LAST_USED_AT_KEY),\n ]);\n}\n\nexport async function storeWalletInfo(\n address: string,\n userId: string,\n tokenAccountAddress?: string\n): Promise<void> {\n await Promise.all([\n SecureStore.setItemAsync(ADDRESS_KEY, address, SECURE_STORE_OPTS),\n SecureStore.setItemAsync(USER_ID_KEY, userId, SECURE_STORE_OPTS),\n tokenAccountAddress\n ? SecureStore.setItemAsync(TOKEN_ACCOUNT_KEY, tokenAccountAddress, SECURE_STORE_OPTS)\n : SecureStore.deleteItemAsync(TOKEN_ACCOUNT_KEY),\n ]);\n}\n\nexport async function hasStoredWallet(): Promise<boolean> {\n return (await SecureStore.getItemAsync(ADDRESS_KEY)) !== null;\n}\n\nexport async function getStoredAddress(): Promise<string | null> {\n return SecureStore.getItemAsync(ADDRESS_KEY);\n}\n\nexport async function getStoredUserId(): Promise<string | null> {\n return SecureStore.getItemAsync(USER_ID_KEY);\n}\n\nexport async function getStoredTokenAccount(): Promise<string | null> {\n return SecureStore.getItemAsync(TOKEN_ACCOUNT_KEY);\n}\n\nexport async function clearSession(): Promise<void> {\n await Promise.all([\n SecureStore.deleteItemAsync(ADDRESS_KEY),\n SecureStore.deleteItemAsync(USER_ID_KEY),\n SecureStore.deleteItemAsync(TOKEN_ACCOUNT_KEY),\n ]);\n}\n","import { create as passkeyCreate, get as passkeyGet } from 'react-native-passkeys';\nimport {\n bytesToBase64Url,\n base64UrlToBytes,\n normalizeLowS,\n parseDerSignature,\n type PasskeySigningResult,\n} from '@thru/programs/passkey-manager';\nimport type {\n DiscoverablePasskeyResult,\n PasskeyMobileConfig,\n PasskeyRegistrationResult,\n} from './types';\n\ntype ProcessLike = typeof globalThis & {\n process?: {\n env?: Record<string, string | undefined>;\n };\n};\n\nfunction getDefaultConfig(config?: PasskeyMobileConfig): Required<PasskeyMobileConfig> {\n const env = (globalThis as ProcessLike).process?.env ?? {};\n\n return {\n rpId: config?.rpId ?? env.EXPO_PUBLIC_PASSKEY_RP_ID ?? 'app.tid.sh',\n rpName: config?.rpName ?? env.EXPO_PUBLIC_PASSKEY_RP_NAME ?? 'Thru Wallet',\n };\n}\n\nexport async function registerPasskey(\n alias: string,\n userId: string,\n config?: PasskeyMobileConfig\n): Promise<PasskeyRegistrationResult> {\n const { rpId, rpName } = getDefaultConfig(config);\n const challenge = bytesToBase64Url(crypto.getRandomValues(new Uint8Array(32)));\n const userIdB64 = bytesToBase64Url(new TextEncoder().encode(userId));\n\n const result = await passkeyCreate({\n challenge,\n rp: { id: rpId, name: rpName },\n user: { id: userIdB64, name: alias, displayName: alias },\n pubKeyCredParams: [{ type: 'public-key', alg: -7 }],\n authenticatorSelection: {\n authenticatorAttachment: 'platform',\n userVerification: 'required',\n residentKey: 'required',\n },\n attestation: 'none',\n timeout: 60000,\n });\n\n if (!result) {\n throw new Error('Passkey registration was cancelled');\n }\n\n const publicKeyB64 = result.response.getPublicKey?.();\n if (!publicKeyB64) {\n throw new Error('Failed to retrieve public key from registration');\n }\n\n const keyBytes = base64UrlToBytes(publicKeyB64);\n const { x, y } = extractP256Coordinates(keyBytes);\n\n return {\n credentialId: result.id,\n publicKeyX: x,\n publicKeyY: y,\n rpId,\n };\n}\n\nexport async function signWithPasskey(\n credentialId: string,\n challenge: Uint8Array,\n rpId?: string\n): Promise<PasskeySigningResult> {\n const resolvedRpId = rpId ?? getDefaultConfig().rpId;\n const challengeB64 = bytesToBase64Url(challenge);\n\n const result = await passkeyGet({\n challenge: challengeB64,\n rpId: resolvedRpId,\n allowCredentials: [{ type: 'public-key', id: credentialId }],\n userVerification: 'required',\n timeout: 60000,\n });\n\n if (!result) {\n throw new Error('Passkey authentication was cancelled');\n }\n\n const derSignature = base64UrlToBytes(result.response.signature);\n let { r, s } = parseDerSignature(derSignature);\n s = normalizeLowS(s);\n\n return {\n signature: new Uint8Array([...r, ...s]),\n authenticatorData: base64UrlToBytes(result.response.authenticatorData),\n clientDataJSON: base64UrlToBytes(result.response.clientDataJSON),\n signatureR: r,\n signatureS: s,\n };\n}\n\nexport async function authenticateWithDiscoverablePasskey(\n config?: Pick<PasskeyMobileConfig, 'rpId'>\n): Promise<DiscoverablePasskeyResult | null> {\n const { rpId } = getDefaultConfig(config);\n\n try {\n const challenge = bytesToBase64Url(crypto.getRandomValues(new Uint8Array(32)));\n const result = await passkeyGet({\n challenge,\n rpId,\n userVerification: 'required',\n timeout: 60000,\n });\n\n return result ? { credentialId: result.id, rpId } : null;\n } catch {\n return null;\n }\n}\n\nexport function extractP256Coordinates(\n keyBytes: Uint8Array\n): { x: Uint8Array; y: Uint8Array } {\n if (keyBytes.length === 64) {\n return {\n x: keyBytes.slice(0, 32),\n y: keyBytes.slice(32, 64),\n };\n }\n\n if (keyBytes.length === 65 && keyBytes[0] === 0x04) {\n return {\n x: keyBytes.slice(1, 33),\n y: keyBytes.slice(33, 65),\n };\n }\n\n const pointStart = keyBytes.length - 65;\n if (pointStart > 0 && keyBytes[pointStart] === 0x04) {\n return {\n x: keyBytes.slice(pointStart + 1, pointStart + 33),\n y: keyBytes.slice(pointStart + 33, pointStart + 65),\n };\n }\n\n throw new Error(\n `Unsupported public key format (${keyBytes.length} bytes). Expected raw X||Y (64), uncompressed point (65), or SPKI DER (91).`\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// src/auth/add-device.ts
|
|
2
2
|
import {
|
|
3
3
|
buildAccountContext,
|
|
4
|
-
|
|
4
|
+
createAuthorityRecord,
|
|
5
5
|
createCredentialLookupSeed,
|
|
6
|
+
createValidateChallenge,
|
|
6
7
|
decodeAddress,
|
|
7
8
|
deriveWalletAddress,
|
|
8
9
|
encodeAddAuthorityInstruction,
|
|
10
|
+
encodeLegacyAddAuthorityInstruction,
|
|
9
11
|
encodeRegisterCredentialInstruction,
|
|
10
12
|
encodeValidateInstruction,
|
|
11
13
|
parseWalletAuthorities
|
|
@@ -15,6 +17,12 @@ import {
|
|
|
15
17
|
buildMulticallInstruction
|
|
16
18
|
} from "@thru/programs/multicall";
|
|
17
19
|
async function addDeviceToAccount(params) {
|
|
20
|
+
return addAuthorityToAccount({
|
|
21
|
+
...params,
|
|
22
|
+
authorityRecord: params.newAuthorityRecord ?? createAuthorityRecord(params.newAuthority)
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
async function addAuthorityToAccount(params) {
|
|
18
26
|
const status = params.onStatus ?? (() => {
|
|
19
27
|
});
|
|
20
28
|
const walletAccount = await params.thru.accounts.get(params.walletAddress);
|
|
@@ -58,9 +66,10 @@ async function addDeviceToAccount(params) {
|
|
|
58
66
|
programAddress: params.programAddress
|
|
59
67
|
});
|
|
60
68
|
const passkeyProgramPubkey = decodeAddress(params.programAddress);
|
|
61
|
-
const
|
|
69
|
+
const encodeAuthorityInstruction = parsed.layout === "legacyAuthority" ? encodeLegacyAddAuthorityInstruction : encodeAddAuthorityInstruction;
|
|
70
|
+
const addAuthorityInstruction = encodeAuthorityInstruction({
|
|
62
71
|
walletAccountIdx: ctx.walletAccountIdx,
|
|
63
|
-
|
|
72
|
+
authorityRecord: params.authorityRecord
|
|
64
73
|
});
|
|
65
74
|
let targetProgramIdx = ctx.getAccountIndex(passkeyProgramPubkey);
|
|
66
75
|
let targetInstructionData = addAuthorityInstruction;
|
|
@@ -123,6 +132,7 @@ async function addDeviceToAccount(params) {
|
|
|
123
132
|
}
|
|
124
133
|
|
|
125
134
|
export {
|
|
126
|
-
addDeviceToAccount
|
|
135
|
+
addDeviceToAccount,
|
|
136
|
+
addAuthorityToAccount
|
|
127
137
|
};
|
|
128
|
-
//# sourceMappingURL=chunk-
|
|
138
|
+
//# sourceMappingURL=chunk-NFK5D7EI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/auth/add-device.ts"],"sourcesContent":["/* Add-passkey-to-account transaction builder, lifted from\n `web/wallet-auth-manager/app/page.tsx` (`runValidateThen`,\n `handleSubmitAddPasskey`) so the wallet's `/embedded` post-connect\n step can reuse the exact same flow.\n\n Builds the on-chain transaction:\n VALIDATE(existingAuthority -> ADD_AUTHORITY(newPasskey))\n or VALIDATE(existingAuthority -> multicall[ADD_AUTHORITY, REGISTER_CREDENTIAL])\n asks the caller's existing passkey to sign the challenge, then asks\n the caller's wallet signer to sign the assembled transaction, sends\n it, and returns the result. */\n\nimport {\n type Authority,\n type AuthorityRecord,\n buildAccountContext,\n createAuthorityRecord,\n createCredentialLookupSeed,\n createValidateChallenge,\n decodeAddress,\n deriveWalletAddress,\n encodeAddAuthorityInstruction,\n encodeLegacyAddAuthorityInstruction,\n encodeRegisterCredentialInstruction,\n encodeValidateInstruction,\n parseWalletAuthorities,\n type ParsedAuthority,\n type WalletSigner,\n} from \"@thru/programs/passkey-manager\";\nimport {\n MULTICALL_PROGRAM_PUBKEY,\n buildMulticallInstruction,\n} from \"@thru/programs/multicall\";\n\n/** Minimal shape required from a passkey signer. Both web's\n `signWithDiscoverablePasskey`/`signWithPasskey` and mobile's\n counterparts conform. */\nexport interface PasskeyChallengeSigner {\n signChallenge: (challenge: Uint8Array) => Promise<{\n signatureR: Uint8Array;\n signatureS: Uint8Array;\n authenticatorData: Uint8Array;\n clientDataJSON: Uint8Array;\n }>;\n}\n\n/** Minimal shape required from a Thru chain client. Loosely-typed\n because @thru/sdk's DTS emit is currently broken in this\n repo. The caller passes the real Thru and we narrow operationally. */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyThruClient = any;\n\nexport interface AddDeviceParams {\n /** Loosely-typed Thru chain client (`@thru/sdk/client`). */\n thru: AnyThruClient;\n /** Wallet (the on-chain WalletAccount) to attach the passkey to. */\n walletAddress: string;\n /** Index of the existing authority that approves this change. Must\n currently be a passkey authority. */\n authIdx: number;\n /** New passkey to attach. tag = 1 (passkey). */\n newAuthority: Authority;\n newAuthorityRecord?: AuthorityRecord;\n /** Optional credential-lookup registration so the new passkey is\n discoverable on subsequent sign-ins. */\n credentialId?: Uint8Array;\n walletName?: string;\n /** Existing-passkey challenge signer (web or mobile). */\n passkey: PasskeyChallengeSigner;\n /** Wallet transaction signer that returns base64(signed bytes). */\n walletSigner: WalletSigner;\n /** Passkey program address (base58). */\n programAddress: string;\n /** Sign-and-send executor (lifted from passkey-transaction.ts in the\n wallet-auth-manager - wallet apps own this because it depends on\n the `Thru` client's transaction builder). */\n executor: TxExecutor;\n /** Optional status callback so UIs can show progress. */\n onStatus?: (message: string) => void;\n}\n\nexport interface TxExecutorParams {\n thru: AnyThruClient;\n walletSigner: WalletSigner;\n instructionData: Uint8Array;\n readWriteAddresses: string[];\n readOnlyAddresses: string[];\n label: string;\n}\n\nexport interface TxExecutorResult {\n signature: string;\n /** Loosely-typed because @thru/sdk types aren't available. */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n execution: any;\n}\n\nexport type TxExecutor = (\n params: TxExecutorParams,\n) => Promise<TxExecutorResult>;\n\nexport interface AddDeviceResult extends TxExecutorResult {\n /** The new passkey's authority index after the transaction lands. */\n newAuthorityIdx: number;\n}\n\nexport interface AddAuthorityParams\n extends Omit<AddDeviceParams, \"newAuthority\" | \"newAuthorityRecord\" | \"credentialId\" | \"walletName\"> {\n /** Complete authority record to append. */\n authorityRecord: AuthorityRecord;\n}\n\n/**\n * Run VALIDATE + ADD_AUTHORITY [+ REGISTER_CREDENTIAL] to attach a new\n * passkey to an on-chain WalletAccount.\n */\nexport async function addDeviceToAccount(\n params: AddDeviceParams,\n): Promise<AddDeviceResult> {\n return addAuthorityToAccount({\n ...params,\n authorityRecord:\n params.newAuthorityRecord ?? createAuthorityRecord(params.newAuthority),\n });\n}\n\n/**\n * Run VALIDATE + ADD_AUTHORITY to attach a complete authority record to an\n * on-chain WalletAccount.\n */\nexport async function addAuthorityToAccount(\n params: AddAuthorityParams & Pick<AddDeviceParams, \"credentialId\" | \"walletName\">,\n): Promise<AddDeviceResult> {\n const status = params.onStatus ?? (() => {});\n\n const walletAccount = await params.thru.accounts.get(params.walletAddress);\n const walletData: Uint8Array | undefined = walletAccount?.data?.data;\n if (!walletData) {\n throw new Error(\"Wallet account data missing\");\n }\n\n const parsed = parseWalletAuthorities(walletData);\n const authorizing: ParsedAuthority | undefined =\n parsed.authorities[params.authIdx];\n if (!authorizing) {\n throw new Error(\"Authorization index out of bounds\");\n }\n if (authorizing.kind !== \"passkey\") {\n throw new Error(\n \"addDeviceToAccount currently requires a passkey authority for VALIDATE\",\n );\n }\n\n /* The new authority will land at the next free slot. */\n const newAuthorityIdx = parsed.authorities.length;\n\n let readWriteAccounts: Uint8Array[] = [];\n let lookupSeed: Uint8Array | undefined;\n let lookupAddressBytes: Uint8Array | undefined;\n let lookupProof: Uint8Array | undefined;\n\n if (params.credentialId) {\n lookupSeed = await createCredentialLookupSeed(params.credentialId);\n lookupAddressBytes = await deriveWalletAddress(\n lookupSeed,\n params.programAddress,\n );\n\n status(\"Fetching state proof for credential lookup...\");\n const proofResult = await params.thru.proofs.generate({\n proofType: 1 /* StateProofType.CREATING */,\n address: lookupAddressBytes,\n });\n lookupProof = proofResult.proof;\n readWriteAccounts = [lookupAddressBytes];\n }\n\n const ctx = buildAccountContext({\n walletAddress: params.walletAddress,\n readWriteAccounts,\n readOnlyAccounts: params.credentialId ? [MULTICALL_PROGRAM_PUBKEY] : [],\n programAddress: params.programAddress,\n });\n\n const passkeyProgramPubkey = decodeAddress(params.programAddress);\n const encodeAuthorityInstruction =\n parsed.layout === \"legacyAuthority\"\n ? encodeLegacyAddAuthorityInstruction\n : encodeAddAuthorityInstruction;\n const addAuthorityInstruction = encodeAuthorityInstruction({\n walletAccountIdx: ctx.walletAccountIdx,\n authorityRecord: params.authorityRecord,\n });\n\n let targetProgramIdx = ctx.getAccountIndex(passkeyProgramPubkey);\n let targetInstructionData = addAuthorityInstruction;\n\n if (params.credentialId) {\n if (!lookupSeed || !lookupAddressBytes || !lookupProof) {\n throw new Error(\"Credential lookup proof data missing\");\n }\n\n const registerCredentialInstruction = encodeRegisterCredentialInstruction({\n walletAccountIdx: ctx.walletAccountIdx,\n lookupAccountIdx: ctx.getAccountIndex(lookupAddressBytes),\n seed: lookupSeed,\n stateProof: lookupProof,\n });\n\n targetProgramIdx = ctx.getAccountIndex(MULTICALL_PROGRAM_PUBKEY);\n targetInstructionData = buildMulticallInstruction([\n {\n programIdx: ctx.getAccountIndex(passkeyProgramPubkey),\n instructionData: addAuthorityInstruction,\n },\n {\n programIdx: ctx.getAccountIndex(passkeyProgramPubkey),\n instructionData: registerCredentialInstruction,\n },\n ]);\n }\n\n /* Build the VALIDATE challenge over the target CPI and ask the caller's passkey to sign. */\n const challenge = await createValidateChallenge(\n parsed.nonce,\n ctx.accountAddresses,\n ctx.walletAccountIdx,\n params.authIdx,\n {\n programIdx: targetProgramIdx,\n instructionData: targetInstructionData,\n },\n );\n\n status(\"Waiting for passkey approval...\");\n const signature = await params.passkey.signChallenge(challenge);\n\n const validateInstruction = encodeValidateInstruction({\n walletAccountIdx: ctx.walletAccountIdx,\n authIdx: params.authIdx,\n targetInstruction: {\n programIdx: targetProgramIdx,\n instructionData: targetInstructionData,\n },\n signatureR: signature.signatureR,\n signatureS: signature.signatureS,\n authenticatorData: signature.authenticatorData,\n clientDataJSON: signature.clientDataJSON,\n });\n\n status(\"Sending transaction...\");\n const result = await params.executor({\n thru: params.thru,\n walletSigner: params.walletSigner,\n instructionData: validateInstruction,\n readWriteAddresses: ctx.readWriteAddresses,\n readOnlyAddresses: ctx.readOnlyAddresses,\n label: params.credentialId\n ? \"VALIDATE -> MULTICALL(ADD_AUTHORITY, REGISTER_CREDENTIAL)\"\n : \"VALIDATE -> ADD_AUTHORITY\",\n });\n\n return { ...result, newAuthorityIdx };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -363,6 +363,7 @@ import {
|
|
|
363
363
|
parseDerSignature,
|
|
364
364
|
normalizeLowS
|
|
365
365
|
} from "@thru/programs/passkey-manager";
|
|
366
|
+
var WEB_AUTHN_FOCUS_RETRY_DELAYS_MS = [150, 300];
|
|
366
367
|
async function signWithPasskey(credentialId, challenge, rpId) {
|
|
367
368
|
if (!isWebAuthnSupported()) {
|
|
368
369
|
throw new Error("WebAuthn is not supported in this browser");
|
|
@@ -378,10 +379,15 @@ async function signWithStoredPasskey(challenge, rpId, preferredPasskey, allPassk
|
|
|
378
379
|
throw new Error("WebAuthn is not supported in this browser");
|
|
379
380
|
}
|
|
380
381
|
const allowPopupFallback = options.allowPopupFallback ?? true;
|
|
382
|
+
const allowDiscoverableFallback = options.allowDiscoverableFallback ?? true;
|
|
381
383
|
const preopenedPopup = allowPopupFallback ? maybePreopenPopup("get", openPasskeyPopupWindow) : null;
|
|
382
384
|
const promptMode = allowPopupFallback ? await getPasskeyPromptMode("get") : "inline";
|
|
383
385
|
const storedPasskey = preferredPasskey;
|
|
384
386
|
const canUsePopup = allowPopupFallback && isInIframe();
|
|
387
|
+
if (!allowDiscoverableFallback && !storedPasskey) {
|
|
388
|
+
closePopup(preopenedPopup);
|
|
389
|
+
throw new Error("No stored passkey available for this wallet");
|
|
390
|
+
}
|
|
385
391
|
if (options.preferDiscoverable) {
|
|
386
392
|
closePopup(preopenedPopup);
|
|
387
393
|
return signWithDiscoverableStoredPasskey(
|
|
@@ -407,7 +413,7 @@ async function signWithStoredPasskey(challenge, rpId, preferredPasskey, allPassk
|
|
|
407
413
|
passkey: storedPasskey
|
|
408
414
|
};
|
|
409
415
|
} catch (error) {
|
|
410
|
-
if (!shouldFallbackToDiscoverable(error)) {
|
|
416
|
+
if (!allowDiscoverableFallback || !shouldFallbackToDiscoverable(error)) {
|
|
411
417
|
throw error;
|
|
412
418
|
}
|
|
413
419
|
return signWithDiscoverableStoredPasskey(
|
|
@@ -518,9 +524,7 @@ async function signWithPasskeyAssertion(challenge, rpId, credentialId) {
|
|
|
518
524
|
}
|
|
519
525
|
];
|
|
520
526
|
}
|
|
521
|
-
const assertion = await
|
|
522
|
-
publicKey: getOptions
|
|
523
|
-
});
|
|
527
|
+
const assertion = await getPasskeyAssertionWithFocusRetry(getOptions);
|
|
524
528
|
if (!assertion) {
|
|
525
529
|
throw new Error("Passkey authentication was cancelled");
|
|
526
530
|
}
|
|
@@ -539,6 +543,37 @@ async function signWithPasskeyAssertion(challenge, rpId, credentialId) {
|
|
|
539
543
|
authenticatorAttachment: rawAttachment
|
|
540
544
|
};
|
|
541
545
|
}
|
|
546
|
+
async function getPasskeyAssertionWithFocusRetry(publicKey) {
|
|
547
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
548
|
+
try {
|
|
549
|
+
return await navigator.credentials.get({
|
|
550
|
+
publicKey
|
|
551
|
+
});
|
|
552
|
+
} catch (error) {
|
|
553
|
+
const retryDelayMs = WEB_AUTHN_FOCUS_RETRY_DELAYS_MS[attempt];
|
|
554
|
+
if (retryDelayMs === void 0 || !isDocumentNotFocusedError(error)) {
|
|
555
|
+
throw error;
|
|
556
|
+
}
|
|
557
|
+
await waitForFocusRetry(retryDelayMs);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
function isDocumentNotFocusedError(error) {
|
|
562
|
+
const name = error && typeof error === "object" && "name" in error ? String(error.name) : "";
|
|
563
|
+
const message = error && typeof error === "object" && "message" in error ? String(error.message) : "";
|
|
564
|
+
const normalized = `${name} ${message}`.toLowerCase();
|
|
565
|
+
return normalized.includes("document is not focused");
|
|
566
|
+
}
|
|
567
|
+
function waitForFocusRetry(delayMs) {
|
|
568
|
+
return new Promise((resolve) => {
|
|
569
|
+
const finish = () => setTimeout(resolve, delayMs);
|
|
570
|
+
if (typeof requestAnimationFrame !== "function") {
|
|
571
|
+
finish();
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
requestAnimationFrame(() => requestAnimationFrame(finish));
|
|
575
|
+
});
|
|
576
|
+
}
|
|
542
577
|
async function signWithPasskeyViaPopup(credentialId, challenge, rpId, preopenedPopup) {
|
|
543
578
|
const result = await requestPasskeyPopup(
|
|
544
579
|
"get",
|
|
@@ -633,4 +668,4 @@ export {
|
|
|
633
668
|
compareBytes,
|
|
634
669
|
uniqueAccounts
|
|
635
670
|
};
|
|
636
|
-
//# sourceMappingURL=chunk-
|
|
671
|
+
//# sourceMappingURL=chunk-YV4LITL4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/register.ts","../src/capabilities.ts","../src/label.ts","../src/sign.ts","../src/web.ts"],"sourcesContent":["import type {\n PasskeyRegistrationOptions,\n PasskeyRegistrationResult,\n PasskeyPopupRegistrationResult,\n} from './types';\nimport { arrayBufferToBase64Url, bytesToHex } from '@thru/programs/passkey-manager';\nimport {\n isWebAuthnSupported,\n getPasskeyPromptMode,\n maybePreopenPopup,\n shouldFallbackToPopup,\n type PasskeyPromptAction,\n} from './capabilities';\nimport { requestPasskeyPopup, openPasskeyPopupWindow, closePopup } from './popup';\n\n/**\n * Register a new passkey for a profile.\n */\nexport async function registerPasskey(\n alias: string,\n userId: string,\n rpId: string,\n options: PasskeyRegistrationOptions = {}\n): Promise<PasskeyRegistrationResult> {\n if (!isWebAuthnSupported()) {\n throw new Error('WebAuthn is not supported in this browser');\n }\n\n return runWithPromptMode(\n 'create',\n () => registerPasskeyInline(alias, userId, rpId),\n (preopenedPopup) => registerPasskeyViaPopup(alias, userId, rpId, preopenedPopup),\n options\n );\n}\n\nasync function runWithPromptMode<T>(\n action: PasskeyPromptAction,\n inlineFn: () => Promise<T>,\n popupFn: (preopenedPopup?: Window | null) => Promise<T>,\n options: PasskeyRegistrationOptions = {}\n): Promise<T> {\n const allowPopupFallback = options.allowPopupFallback ?? true;\n const preopenedPopup = allowPopupFallback ? maybePreopenPopup(action, openPasskeyPopupWindow) : null;\n const promptMode = allowPopupFallback ? await getPasskeyPromptMode(action) : 'inline';\n if (promptMode === 'popup') {\n return popupFn(preopenedPopup);\n }\n\n closePopup(preopenedPopup);\n\n try {\n return await inlineFn();\n } catch (error) {\n if (allowPopupFallback && shouldFallbackToPopup(error)) {\n return popupFn();\n }\n throw error;\n }\n}\n\nasync function registerPasskeyInline(\n alias: string,\n userId: string,\n rpId: string\n): Promise<PasskeyRegistrationResult> {\n const rpName = 'Thru Wallet';\n\n const userIdBytes = new TextEncoder().encode(userId);\n const userIdBuffer = userIdBytes.slice(0, 64);\n\n const challenge = crypto.getRandomValues(new Uint8Array(32));\n\n const createOptions: PublicKeyCredentialCreationOptions = {\n challenge,\n rp: {\n id: rpId,\n name: rpName,\n },\n user: {\n id: userIdBuffer,\n name: alias,\n displayName: alias,\n },\n pubKeyCredParams: [\n { type: 'public-key', alg: -7 },\n ],\n authenticatorSelection: {\n authenticatorAttachment: 'platform',\n userVerification: 'required',\n residentKey: 'required',\n requireResidentKey: true,\n },\n attestation: 'none',\n timeout: 60000,\n };\n\n const credential = (await navigator.credentials.create({\n publicKey: createOptions,\n })) as PublicKeyCredential | null;\n\n if (!credential) {\n throw new Error('Passkey registration was cancelled');\n }\n\n const response = credential.response as AuthenticatorAttestationResponse;\n const { x, y } = extractP256PublicKey(response);\n const authenticatorAttachment =\n (\n credential as PublicKeyCredential & {\n authenticatorAttachment?: AuthenticatorAttachment | null;\n }\n ).authenticatorAttachment ?? null;\n\n return {\n credentialId: arrayBufferToBase64Url(credential.rawId),\n publicKeyX: bytesToHex(x),\n publicKeyY: bytesToHex(y),\n rpId,\n authenticatorAttachment,\n };\n}\n\nasync function registerPasskeyViaPopup(\n alias: string,\n userId: string,\n rpId: string,\n preopenedPopup?: Window | null\n): Promise<PasskeyRegistrationResult> {\n const result = await requestPasskeyPopup<PasskeyPopupRegistrationResult>(\n 'create',\n { alias, userId, rpId },\n preopenedPopup\n );\n return result;\n}\n\n// Key extraction helpers\n\nfunction extractP256PublicKey(\n response: AuthenticatorAttestationResponse\n): { x: Uint8Array; y: Uint8Array } {\n if (typeof response.getPublicKey === 'function') {\n const spkiKey = response.getPublicKey();\n if (spkiKey) {\n return extractFromSpki(new Uint8Array(spkiKey));\n }\n }\n\n if (typeof response.getAuthenticatorData === 'function') {\n const authData = new Uint8Array(response.getAuthenticatorData());\n return extractFromAuthenticatorData(authData);\n }\n\n throw new Error('Unable to extract public key: browser does not support required WebAuthn methods');\n}\n\nfunction extractFromSpki(spki: Uint8Array): { x: Uint8Array; y: Uint8Array } {\n const pointStart = spki.length - 65;\n\n if (spki[pointStart] !== 0x04) {\n throw new Error('Invalid SPKI format: expected uncompressed point');\n }\n\n const x = spki.slice(pointStart + 1, pointStart + 33);\n const y = spki.slice(pointStart + 33, pointStart + 65);\n\n if (x.length !== 32 || y.length !== 32) {\n throw new Error('Invalid SPKI format: incorrect coordinate length');\n }\n\n return { x, y };\n}\n\nfunction extractFromAuthenticatorData(authData: Uint8Array): { x: Uint8Array; y: Uint8Array } {\n const rpIdHashLength = 32;\n const flagsLength = 1;\n const counterLength = 4;\n const offset = rpIdHashLength + flagsLength + counterLength;\n const aaguidLength = 16;\n const credIdLenOffset = offset + aaguidLength;\n const credIdLength = (authData[credIdLenOffset] << 8) | authData[credIdLenOffset + 1];\n const coseKeyOffset = credIdLenOffset + 2 + credIdLength;\n const coseKey = authData.slice(coseKeyOffset);\n\n return extractFromCoseKey(coseKey);\n}\n\nfunction extractFromCoseKey(coseKey: Uint8Array): { x: Uint8Array; y: Uint8Array } {\n const mapStart = coseKey[0];\n if (mapStart !== 0xa5 && mapStart !== 0xa4) {\n throw new Error('Invalid COSE key format');\n }\n\n let offset = 1;\n let x: Uint8Array | null = null;\n let y: Uint8Array | null = null;\n\n while (offset < coseKey.length) {\n const key = coseKey[offset++];\n const valueType = coseKey[offset++];\n\n if (key === 0x21) {\n const length = valueType & 0x1f;\n x = coseKey.slice(offset, offset + length);\n offset += length;\n continue;\n }\n\n if (key === 0x22) {\n const length = valueType & 0x1f;\n y = coseKey.slice(offset, offset + length);\n offset += length;\n continue;\n }\n\n if (valueType >= 0x40 && valueType <= 0x5f) {\n const length = valueType & 0x1f;\n offset += length;\n continue;\n }\n\n if (valueType === 0x01 || valueType === 0x02 || valueType === 0x03) {\n continue;\n }\n\n if (valueType >= 0x18 && valueType <= 0x1b) {\n const size = 1 << (valueType - 0x18);\n offset += size;\n continue;\n }\n }\n\n if (!x || !y) {\n throw new Error('Failed to extract P-256 public key from COSE data');\n }\n\n if (x.length !== 32 || y.length !== 32) {\n throw new Error('Invalid COSE key: incorrect coordinate length');\n }\n\n return { x, y };\n}\n","import type { PasskeyClientCapabilities } from './types';\n\nconst globalProcess = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;\nconst DEBUG = globalProcess?.env?.NEXT_PUBLIC_PASSKEY_DEBUG === '1';\n\nlet cachedClientCapabilities: PasskeyClientCapabilities | null | undefined;\nlet clientCapabilitiesPromise: Promise<PasskeyClientCapabilities | null> | null = null;\n\nexport function isWebAuthnSupported(): boolean {\n const supported =\n typeof window !== 'undefined' &&\n typeof window.PublicKeyCredential !== 'undefined' &&\n typeof navigator.credentials !== 'undefined';\n\n if (DEBUG) {\n console.log('[Passkey] WebAuthn support check:', {\n window: typeof window !== 'undefined',\n PublicKeyCredential:\n typeof window !== 'undefined' && typeof window.PublicKeyCredential !== 'undefined',\n credentials:\n typeof window !== 'undefined' &&\n typeof navigator !== 'undefined' &&\n typeof navigator.credentials !== 'undefined',\n supported,\n });\n }\n\n return supported;\n}\n\nasync function fetchPasskeyClientCapabilities(): Promise<PasskeyClientCapabilities | null> {\n if (typeof window === 'undefined' || typeof window.PublicKeyCredential === 'undefined') {\n return null;\n }\n\n const getClientCapabilities = (window.PublicKeyCredential as {\n getClientCapabilities?: () => Promise<PasskeyClientCapabilities>;\n }).getClientCapabilities;\n\n if (typeof getClientCapabilities !== 'function') {\n return null;\n }\n\n try {\n const capabilities = await getClientCapabilities.call(window.PublicKeyCredential);\n if (DEBUG) {\n console.log('[Passkey] WebAuthn client capabilities:', capabilities);\n }\n return capabilities ?? null;\n } catch (error) {\n if (DEBUG) {\n console.warn('[Passkey] Failed to read client capabilities:', error);\n }\n return null;\n }\n}\n\nexport function preloadPasskeyClientCapabilities(): void {\n if (cachedClientCapabilities !== undefined || clientCapabilitiesPromise) {\n return;\n }\n\n clientCapabilitiesPromise = fetchPasskeyClientCapabilities().then((capabilities) => {\n cachedClientCapabilities = capabilities;\n return capabilities;\n });\n}\n\nexport async function getPasskeyClientCapabilities(): Promise<PasskeyClientCapabilities | null> {\n if (cachedClientCapabilities !== undefined) {\n return cachedClientCapabilities;\n }\n\n if (!clientCapabilitiesPromise) {\n preloadPasskeyClientCapabilities();\n }\n\n if (!clientCapabilitiesPromise) {\n cachedClientCapabilities = null;\n return null;\n }\n\n const capabilities = await clientCapabilitiesPromise;\n cachedClientCapabilities = capabilities;\n return capabilities;\n}\n\nexport function getCachedPasskeyClientCapabilities(): PasskeyClientCapabilities | null | undefined {\n return cachedClientCapabilities;\n}\n\nexport function isInIframe(): boolean {\n if (typeof window === 'undefined') {\n return false;\n }\n try {\n return window.self !== window.top;\n } catch {\n return true;\n }\n}\n\nexport type PasskeyPromptAction = 'get' | 'create';\n\nexport async function shouldUsePasskeyPopup(action: PasskeyPromptAction): Promise<boolean> {\n if (!isInIframe()) {\n return false;\n }\n const mode = await getPasskeyPromptMode(action);\n return mode === 'popup';\n}\n\ntype PasskeyPromptMode = 'inline' | 'popup';\n\nfunction getPermissionsPolicyAllowsFeature(feature: string): boolean | null {\n if (typeof document === 'undefined') {\n return null;\n }\n\n const policy = (document as { permissionsPolicy?: { allowsFeature?: (name: string) => boolean } })\n .permissionsPolicy;\n const featurePolicy = (document as { featurePolicy?: { allowsFeature?: (name: string) => boolean } })\n .featurePolicy;\n const allowsFeature = policy?.allowsFeature || featurePolicy?.allowsFeature;\n\n if (typeof allowsFeature !== 'function') {\n return null;\n }\n\n try {\n return allowsFeature(feature);\n } catch {\n return null;\n }\n}\n\nfunction getCachedPromptMode(action: PasskeyPromptAction): PasskeyPromptMode | 'unknown' {\n if (!isInIframe()) {\n return 'inline';\n }\n\n if (cachedClientCapabilities === undefined && !clientCapabilitiesPromise) {\n preloadPasskeyClientCapabilities();\n }\n\n const feature =\n action === 'create' ? 'publickey-credentials-create' : 'publickey-credentials-get';\n const policyAllows = getPermissionsPolicyAllowsFeature(feature);\n const capabilities = getCachedPasskeyClientCapabilities();\n const supportsInline =\n capabilities?.passkeyPlatformAuthenticator === true ||\n capabilities?.userVerifyingPlatformAuthenticator === true;\n\n if (policyAllows === false) {\n return 'popup';\n }\n\n if (capabilities === undefined) {\n return 'unknown';\n }\n\n if (!supportsInline) {\n return 'popup';\n }\n\n return 'inline';\n}\n\nexport async function getPasskeyPromptMode(action: PasskeyPromptAction): Promise<PasskeyPromptMode> {\n if (!isInIframe()) {\n return 'inline';\n }\n\n const feature =\n action === 'create' ? 'publickey-credentials-create' : 'publickey-credentials-get';\n const policyAllows = getPermissionsPolicyAllowsFeature(feature);\n const capabilities = await getPasskeyClientCapabilities();\n const supportsInline =\n capabilities?.passkeyPlatformAuthenticator === true ||\n capabilities?.userVerifyingPlatformAuthenticator === true;\n\n if (DEBUG) {\n console.log('[Passkey] Prompt mode check:', {\n action,\n policyAllows,\n supportsInline,\n capabilities,\n });\n }\n\n if (!supportsInline) {\n return 'popup';\n }\n\n if (policyAllows === false) {\n return 'popup';\n }\n\n return 'inline';\n}\n\nexport function maybePreopenPopup(action: PasskeyPromptAction, openPopupFn: () => Window): Window | null {\n const cachedMode = getCachedPromptMode(action);\n if (cachedMode !== 'popup') {\n return null;\n }\n\n try {\n return openPopupFn();\n } catch {\n return null;\n }\n}\n\nexport function shouldFallbackToPopup(error: unknown): boolean {\n if (!isInIframe()) {\n return false;\n }\n\n const name =\n error && typeof error === 'object' && 'name' in error ? String((error as { name?: unknown }).name) : '';\n const message =\n error && typeof error === 'object' && 'message' in error\n ? String((error as { message?: unknown }).message)\n : '';\n const normalized = `${name} ${message}`.toLowerCase();\n\n if (\n normalized.includes('cancel') ||\n normalized.includes('canceled') ||\n normalized.includes('cancelled') ||\n normalized.includes('user canceled') ||\n normalized.includes('user cancelled') ||\n normalized.includes('aborted')\n ) {\n return false;\n }\n\n if (normalized.includes('securityerror')) {\n return true;\n }\n\n if (normalized.includes('notallowederror')) {\n if (\n normalized.includes('permission') ||\n normalized.includes('policy') ||\n normalized.includes('iframe') ||\n normalized.includes('frame')\n ) {\n return true;\n }\n }\n\n return false;\n}\n","const DEFAULT_LABEL = 'Thru Wallet passkey';\n\nexport interface DistinctPasskeyLabelOptions {\n existingLabels?: Iterable<string | null | undefined>;\n maxAttempts?: number;\n suffixFactory?: () => string;\n}\n\nexport function createDistinctPasskeyLabel(\n baseLabel: string,\n _options: DistinctPasskeyLabelOptions = {}\n): string {\n return baseLabel.trim() || DEFAULT_LABEL;\n}\n","import type {\n PasskeySigningResult,\n PasskeyStoredSigningResult,\n PasskeyDiscoverableSigningResult,\n PasskeyMetadata,\n PasskeyPopupContext,\n PasskeyPopupSigningResult,\n PasskeyPopupStoredSigningResult,\n PasskeyStoredSigningOptions,\n} from './types';\nimport {\n arrayBufferToBase64Url,\n base64UrlToArrayBuffer,\n bytesToBase64Url,\n base64UrlToBytes,\n parseDerSignature,\n normalizeLowS,\n} from '@thru/programs/passkey-manager';\nimport {\n isWebAuthnSupported,\n getPasskeyPromptMode,\n isInIframe,\n maybePreopenPopup,\n shouldFallbackToPopup,\n type PasskeyPromptAction,\n} from './capabilities';\nimport {\n requestPasskeyPopup,\n openPasskeyPopupWindow,\n closePopup,\n} from './popup';\n\nconst WEB_AUTHN_FOCUS_RETRY_DELAYS_MS = [150, 300];\n\n/**\n * Sign a challenge with an existing passkey (by credential ID).\n */\nexport async function signWithPasskey(\n credentialId: string,\n challenge: Uint8Array,\n rpId: string\n): Promise<PasskeySigningResult> {\n if (!isWebAuthnSupported()) {\n throw new Error('WebAuthn is not supported in this browser');\n }\n\n return runWithPromptMode(\n 'get',\n () => signWithPasskeyInline(credentialId, challenge, rpId),\n (preopenedPopup) =>\n signWithPasskeyViaPopup(credentialId, challenge, rpId, preopenedPopup)\n );\n}\n\n/**\n * Sign with stored passkey (for embedded/popup contexts).\n */\nexport async function signWithStoredPasskey(\n challenge: Uint8Array,\n rpId: string,\n preferredPasskey: PasskeyMetadata | null,\n allPasskeys: PasskeyMetadata[],\n context?: PasskeyPopupContext,\n options: PasskeyStoredSigningOptions = {}\n): Promise<PasskeyStoredSigningResult> {\n if (!isWebAuthnSupported()) {\n throw new Error('WebAuthn is not supported in this browser');\n }\n\n const allowPopupFallback = options.allowPopupFallback ?? true;\n const allowDiscoverableFallback = options.allowDiscoverableFallback ?? true;\n const preopenedPopup = allowPopupFallback\n ? maybePreopenPopup('get', openPasskeyPopupWindow)\n : null;\n const promptMode = allowPopupFallback\n ? await getPasskeyPromptMode('get')\n : 'inline';\n const storedPasskey = preferredPasskey;\n const canUsePopup = allowPopupFallback && isInIframe();\n\n if (!allowDiscoverableFallback && !storedPasskey) {\n closePopup(preopenedPopup);\n throw new Error('No stored passkey available for this wallet');\n }\n\n if (options.preferDiscoverable) {\n closePopup(preopenedPopup);\n return signWithDiscoverableStoredPasskey(\n challenge,\n storedPasskey?.rpId ?? rpId,\n allPasskeys\n );\n }\n\n if (promptMode === 'popup' || (canUsePopup && !storedPasskey)) {\n return requestStoredPasskeyPopup(challenge, preopenedPopup, context);\n }\n\n closePopup(preopenedPopup);\n\n try {\n if (storedPasskey) {\n try {\n const result = await signWithPasskeyInline(\n storedPasskey.credentialId,\n challenge,\n storedPasskey.rpId\n );\n return {\n ...result,\n passkey: storedPasskey,\n };\n } catch (error) {\n if (!allowDiscoverableFallback || !shouldFallbackToDiscoverable(error)) {\n throw error;\n }\n return signWithDiscoverableStoredPasskey(\n challenge,\n storedPasskey.rpId,\n allPasskeys\n );\n }\n }\n\n return signWithDiscoverableStoredPasskey(challenge, rpId, allPasskeys);\n } catch (error) {\n if (canUsePopup && shouldFallbackToPopup(error)) {\n return requestStoredPasskeyPopup(challenge, undefined, context);\n }\n\n throw error;\n }\n}\n\nasync function signWithDiscoverableStoredPasskey(\n challenge: Uint8Array,\n rpId: string,\n allPasskeys: PasskeyMetadata[]\n): Promise<PasskeyStoredSigningResult> {\n const discoverable = await signWithDiscoverablePasskey(challenge, rpId);\n const matchingPasskey =\n allPasskeys.find((p) => p.credentialId === discoverable.credentialId) ??\n null;\n const now = new Date().toISOString();\n const passkey = matchingPasskey ?? {\n credentialId: discoverable.credentialId,\n publicKeyX: '',\n publicKeyY: '',\n rpId: discoverable.rpId,\n createdAt: now,\n lastUsedAt: now,\n };\n\n return {\n signature: discoverable.signature,\n authenticatorData: discoverable.authenticatorData,\n clientDataJSON: discoverable.clientDataJSON,\n signatureR: discoverable.signatureR,\n signatureS: discoverable.signatureS,\n authenticatorAttachment: discoverable.authenticatorAttachment,\n passkey,\n };\n}\n\nfunction shouldFallbackToDiscoverable(error: unknown): boolean {\n const name =\n error && typeof error === 'object' && 'name' in error\n ? String((error as { name?: unknown }).name)\n : '';\n const message =\n error && typeof error === 'object' && 'message' in error\n ? String((error as { message?: unknown }).message)\n : '';\n const normalized = `${name} ${message}`.toLowerCase();\n\n if (\n normalized.includes('user rejected') ||\n normalized.includes('user canceled') ||\n normalized.includes('user cancelled')\n ) {\n return false;\n }\n\n return (\n normalized.includes('notallowederror') ||\n normalized.includes('invalidstateerror') ||\n normalized.includes('notfounderror') ||\n normalized.includes('not found') ||\n normalized.includes('no passkey') ||\n normalized.includes('no credential') ||\n normalized.includes('saved for this app')\n );\n}\n\n/**\n * Sign with a discoverable passkey (no credential ID - browser prompts user to select).\n */\nexport async function signWithDiscoverablePasskey(\n challenge: Uint8Array,\n rpId: string\n): Promise<PasskeyDiscoverableSigningResult> {\n if (!isWebAuthnSupported()) {\n throw new Error('WebAuthn is not supported in this browser');\n }\n\n const resolvedRpId = rpId;\n const result = await signWithPasskeyAssertion(challenge, resolvedRpId);\n\n return {\n signature: result.signature,\n authenticatorData: result.authenticatorData,\n clientDataJSON: result.clientDataJSON,\n signatureR: result.signatureR,\n signatureS: result.signatureS,\n credentialId: result.credentialId,\n rpId: resolvedRpId,\n authenticatorAttachment: result.authenticatorAttachment,\n };\n}\n\n// Internal helpers\n\nasync function runWithPromptMode<T>(\n action: PasskeyPromptAction,\n inlineFn: () => Promise<T>,\n popupFn: (preopenedPopup?: Window | null) => Promise<T>\n): Promise<T> {\n const preopenedPopup = maybePreopenPopup(action, openPasskeyPopupWindow);\n const promptMode = await getPasskeyPromptMode(action);\n if (promptMode === 'popup') {\n return popupFn(preopenedPopup);\n }\n\n closePopup(preopenedPopup);\n\n try {\n return await inlineFn();\n } catch (error) {\n if (shouldFallbackToPopup(error)) {\n return popupFn();\n }\n throw error;\n }\n}\n\nasync function signWithPasskeyInline(\n credentialId: string,\n challenge: Uint8Array,\n rpId: string\n): Promise<PasskeySigningResult> {\n const result = await signWithPasskeyAssertion(challenge, rpId, credentialId);\n return {\n signature: result.signature,\n authenticatorData: result.authenticatorData,\n clientDataJSON: result.clientDataJSON,\n signatureR: result.signatureR,\n signatureS: result.signatureS,\n authenticatorAttachment: result.authenticatorAttachment,\n };\n}\n\nasync function signWithPasskeyAssertion(\n challenge: Uint8Array,\n rpId: string,\n credentialId?: string\n): Promise<PasskeySigningResult & { credentialId: string }> {\n const challengeBytes = new Uint8Array(challenge);\n const getOptions: PublicKeyCredentialRequestOptions = {\n challenge: challengeBytes,\n rpId,\n userVerification: 'required',\n timeout: 60000,\n };\n\n if (credentialId) {\n const credentialIdBuffer = base64UrlToArrayBuffer(credentialId);\n getOptions.allowCredentials = [\n {\n type: 'public-key',\n id: credentialIdBuffer,\n transports: ['internal', 'hybrid', 'usb', 'ble', 'nfc'],\n },\n ];\n }\n\n const assertion = await getPasskeyAssertionWithFocusRetry(getOptions);\n\n if (!assertion) {\n throw new Error('Passkey authentication was cancelled');\n }\n\n const response = assertion.response as AuthenticatorAssertionResponse;\n\n const signature = new Uint8Array(response.signature);\n let { r, s } = parseDerSignature(signature);\n s = normalizeLowS(s);\n\n /* `authenticatorAttachment` distinguishes a same-device passkey\n ('platform') from a cross-device one signed via QR / hybrid\n transport ('cross-platform'). Drives the wallet's add-device\n prompt. Browsers may report null. */\n const rawAttachment =\n (\n assertion as PublicKeyCredential & {\n authenticatorAttachment?: AuthenticatorAttachment | null;\n }\n ).authenticatorAttachment ?? null;\n\n return {\n signature: new Uint8Array([...r, ...s]),\n authenticatorData: new Uint8Array(response.authenticatorData),\n clientDataJSON: new Uint8Array(response.clientDataJSON),\n signatureR: r,\n signatureS: s,\n credentialId: arrayBufferToBase64Url(assertion.rawId),\n authenticatorAttachment: rawAttachment,\n };\n}\n\nasync function getPasskeyAssertionWithFocusRetry(\n publicKey: PublicKeyCredentialRequestOptions\n): Promise<PublicKeyCredential | null> {\n for (let attempt = 0; ; attempt += 1) {\n try {\n return (await navigator.credentials.get({\n publicKey,\n })) as PublicKeyCredential | null;\n } catch (error) {\n const retryDelayMs = WEB_AUTHN_FOCUS_RETRY_DELAYS_MS[attempt];\n if (retryDelayMs === undefined || !isDocumentNotFocusedError(error)) {\n throw error;\n }\n await waitForFocusRetry(retryDelayMs);\n }\n }\n}\n\nfunction isDocumentNotFocusedError(error: unknown): boolean {\n const name =\n error && typeof error === 'object' && 'name' in error\n ? String((error as { name?: unknown }).name)\n : '';\n const message =\n error && typeof error === 'object' && 'message' in error\n ? String((error as { message?: unknown }).message)\n : '';\n const normalized = `${name} ${message}`.toLowerCase();\n return normalized.includes('document is not focused');\n}\n\nfunction waitForFocusRetry(delayMs: number): Promise<void> {\n return new Promise((resolve) => {\n const finish = () => setTimeout(resolve, delayMs);\n if (typeof requestAnimationFrame !== 'function') {\n finish();\n return;\n }\n requestAnimationFrame(() => requestAnimationFrame(finish));\n });\n}\n\nasync function signWithPasskeyViaPopup(\n credentialId: string,\n challenge: Uint8Array,\n rpId: string,\n preopenedPopup?: Window | null\n): Promise<PasskeySigningResult> {\n const result = await requestPasskeyPopup<PasskeyPopupSigningResult>(\n 'get',\n {\n credentialId,\n challengeBase64Url: bytesToBase64Url(challenge),\n rpId,\n },\n preopenedPopup\n );\n\n return decodePopupSigningResult(result);\n}\n\nasync function requestStoredPasskeyPopup(\n challenge: Uint8Array,\n preopenedPopup?: Window | null,\n context?: PasskeyPopupContext\n): Promise<PasskeyStoredSigningResult> {\n const result = await requestPasskeyPopup<PasskeyPopupStoredSigningResult>(\n 'getStored',\n {\n challengeBase64Url: bytesToBase64Url(challenge),\n context,\n },\n preopenedPopup\n );\n return decodePopupStoredSigningResult(result);\n}\n\nfunction decodePopupSigningResult(\n result: PasskeyPopupSigningResult\n): PasskeySigningResult {\n return {\n signature: base64UrlToBytes(result.signatureBase64Url),\n authenticatorData: base64UrlToBytes(result.authenticatorDataBase64Url),\n clientDataJSON: base64UrlToBytes(result.clientDataJSONBase64Url),\n signatureR: base64UrlToBytes(result.signatureRBase64Url),\n signatureS: base64UrlToBytes(result.signatureSBase64Url),\n authenticatorAttachment: result.authenticatorAttachment ?? null,\n };\n}\n\nfunction decodePopupStoredSigningResult(\n result: PasskeyPopupStoredSigningResult\n): PasskeyStoredSigningResult {\n return {\n ...decodePopupSigningResult(result),\n passkey: result.passkey,\n accounts: result.accounts,\n };\n}\n","export type {\n PasskeyRegistrationResult,\n PasskeySigningResult,\n PasskeyDiscoverableSigningResult,\n PasskeyStoredSigningResult,\n PasskeyMetadata,\n PasskeyClientCapabilities,\n PasskeyPopupContext,\n PasskeyPopupAccount,\n PasskeyStoredSigningOptions,\n PasskeyRegistrationOptions,\n} from './types';\n\nexport { registerPasskey } from './register';\nexport { createDistinctPasskeyLabel } from './label';\nexport type { DistinctPasskeyLabelOptions } from './label';\n\nexport {\n signWithPasskey,\n signWithStoredPasskey,\n signWithDiscoverablePasskey,\n} from './sign';\n\nexport {\n parseDerSignature,\n normalizeLowS,\n normalizeSignatureComponent,\n P256_N,\n P256_HALF_N,\n bytesToBigIntBE,\n bigIntToBytesBE,\n} from '@thru/programs/passkey-manager';\n\nexport {\n isWebAuthnSupported,\n preloadPasskeyClientCapabilities,\n getPasskeyClientCapabilities,\n getCachedPasskeyClientCapabilities,\n shouldUsePasskeyPopup,\n isInIframe,\n type PasskeyPromptAction,\n} from './capabilities';\n\nexport {\n arrayBufferToBase64Url,\n base64UrlToArrayBuffer,\n bytesToBase64,\n bytesToBase64Url,\n base64UrlToBytes,\n bytesToHex,\n hexToBytes,\n bytesEqual,\n compareBytes,\n uniqueAccounts,\n} from '@thru/programs/passkey-manager';\n"],"mappings":"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","names":["arrayBufferToBase64Url","runWithPromptMode","arrayBufferToBase64Url","parseDerSignature","normalizeLowS","arrayBufferToBase64Url","base64UrlToArrayBuffer","bytesToBase64Url","base64UrlToBytes","bytesToHex"]}
|
package/dist/index.cjs
CHANGED
|
@@ -567,6 +567,7 @@ function createDistinctPasskeyLabel(baseLabel, _options = {}) {
|
|
|
567
567
|
|
|
568
568
|
// src/sign.ts
|
|
569
569
|
var import_passkey_manager2 = require("@thru/programs/passkey-manager");
|
|
570
|
+
var WEB_AUTHN_FOCUS_RETRY_DELAYS_MS = [150, 300];
|
|
570
571
|
async function signWithPasskey(credentialId, challenge, rpId) {
|
|
571
572
|
if (!isWebAuthnSupported()) {
|
|
572
573
|
throw new Error("WebAuthn is not supported in this browser");
|
|
@@ -582,10 +583,15 @@ async function signWithStoredPasskey(challenge, rpId, preferredPasskey, allPassk
|
|
|
582
583
|
throw new Error("WebAuthn is not supported in this browser");
|
|
583
584
|
}
|
|
584
585
|
const allowPopupFallback = options.allowPopupFallback ?? true;
|
|
586
|
+
const allowDiscoverableFallback = options.allowDiscoverableFallback ?? true;
|
|
585
587
|
const preopenedPopup = allowPopupFallback ? maybePreopenPopup("get", openPasskeyPopupWindow) : null;
|
|
586
588
|
const promptMode = allowPopupFallback ? await getPasskeyPromptMode("get") : "inline";
|
|
587
589
|
const storedPasskey = preferredPasskey;
|
|
588
590
|
const canUsePopup = allowPopupFallback && isInIframe();
|
|
591
|
+
if (!allowDiscoverableFallback && !storedPasskey) {
|
|
592
|
+
closePopup(preopenedPopup);
|
|
593
|
+
throw new Error("No stored passkey available for this wallet");
|
|
594
|
+
}
|
|
589
595
|
if (options.preferDiscoverable) {
|
|
590
596
|
closePopup(preopenedPopup);
|
|
591
597
|
return signWithDiscoverableStoredPasskey(
|
|
@@ -611,7 +617,7 @@ async function signWithStoredPasskey(challenge, rpId, preferredPasskey, allPassk
|
|
|
611
617
|
passkey: storedPasskey
|
|
612
618
|
};
|
|
613
619
|
} catch (error) {
|
|
614
|
-
if (!shouldFallbackToDiscoverable(error)) {
|
|
620
|
+
if (!allowDiscoverableFallback || !shouldFallbackToDiscoverable(error)) {
|
|
615
621
|
throw error;
|
|
616
622
|
}
|
|
617
623
|
return signWithDiscoverableStoredPasskey(
|
|
@@ -722,9 +728,7 @@ async function signWithPasskeyAssertion(challenge, rpId, credentialId) {
|
|
|
722
728
|
}
|
|
723
729
|
];
|
|
724
730
|
}
|
|
725
|
-
const assertion = await
|
|
726
|
-
publicKey: getOptions
|
|
727
|
-
});
|
|
731
|
+
const assertion = await getPasskeyAssertionWithFocusRetry(getOptions);
|
|
728
732
|
if (!assertion) {
|
|
729
733
|
throw new Error("Passkey authentication was cancelled");
|
|
730
734
|
}
|
|
@@ -743,6 +747,37 @@ async function signWithPasskeyAssertion(challenge, rpId, credentialId) {
|
|
|
743
747
|
authenticatorAttachment: rawAttachment
|
|
744
748
|
};
|
|
745
749
|
}
|
|
750
|
+
async function getPasskeyAssertionWithFocusRetry(publicKey) {
|
|
751
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
752
|
+
try {
|
|
753
|
+
return await navigator.credentials.get({
|
|
754
|
+
publicKey
|
|
755
|
+
});
|
|
756
|
+
} catch (error) {
|
|
757
|
+
const retryDelayMs = WEB_AUTHN_FOCUS_RETRY_DELAYS_MS[attempt];
|
|
758
|
+
if (retryDelayMs === void 0 || !isDocumentNotFocusedError(error)) {
|
|
759
|
+
throw error;
|
|
760
|
+
}
|
|
761
|
+
await waitForFocusRetry(retryDelayMs);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
function isDocumentNotFocusedError(error) {
|
|
766
|
+
const name = error && typeof error === "object" && "name" in error ? String(error.name) : "";
|
|
767
|
+
const message = error && typeof error === "object" && "message" in error ? String(error.message) : "";
|
|
768
|
+
const normalized = `${name} ${message}`.toLowerCase();
|
|
769
|
+
return normalized.includes("document is not focused");
|
|
770
|
+
}
|
|
771
|
+
function waitForFocusRetry(delayMs) {
|
|
772
|
+
return new Promise((resolve) => {
|
|
773
|
+
const finish = () => setTimeout(resolve, delayMs);
|
|
774
|
+
if (typeof requestAnimationFrame !== "function") {
|
|
775
|
+
finish();
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
requestAnimationFrame(() => requestAnimationFrame(finish));
|
|
779
|
+
});
|
|
780
|
+
}
|
|
746
781
|
async function signWithPasskeyViaPopup(credentialId, challenge, rpId, preopenedPopup) {
|
|
747
782
|
const result = await requestPasskeyPopup(
|
|
748
783
|
"get",
|