import { ClerkWebAuthnError } from '@clerk/shared/error'; import { Buffer } from 'buffer'; export { ClerkWebAuthnError }; export function encodeBase64(data: ArrayBufferLike) { return btoa(String.fromCharCode(...new Uint8Array(data))); } export function encodeBase64Url(data: ArrayBufferLike) { return encodeBase64(data).replaceAll('=', '').replaceAll('+', '-').replaceAll('/', '_'); } export function decodeBase64Url(data: string) { return decodeBase64(data.replaceAll('-', '+').replaceAll('_', '/')); } export function decodeToken(data: string) { const base64 = data.replace(/-/g, '+').replace(/_/g, '/'); const jsonPayload = decodeURIComponent( atob(base64) .split('') .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)) .join(''), ); return JSON.parse(jsonPayload); } export function decodeBase64(data: string) { return Uint8Array.from(atob(data).split(''), x => x.charCodeAt(0)); } export function utf8Decode(buffer: BufferSource) { const textDecoder = new TextDecoder(); return textDecoder.decode(buffer); } export function toArrayBuffer(bufferSource: BufferSource) { if (bufferSource instanceof ArrayBuffer) { return bufferSource; // It's already an ArrayBuffer } else if (ArrayBuffer.isView(bufferSource)) { return bufferSource.buffer; // Extract the ArrayBuffer from the typed array } else { throw new TypeError('Expected a BufferSource, but received an incompatible type.'); } } export function base64urlToArrayBuffer(base64url: string) { const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/'); const binaryString = Buffer.from(base64, 'base64').toString('binary'); const len = binaryString.length; const buffer = new ArrayBuffer(len); const uintArray = new Uint8Array(buffer); for (let i = 0; i < len; i++) { uintArray[i] = binaryString.charCodeAt(i); } return buffer; } export function arrayBufferToBase64Url(buffer: ArrayBufferLike) { const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.length; i++) { binary += String.fromCharCode(bytes[i]); } const base64String = btoa(binary); const base64Url = base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); return base64Url; } export function mapNativeErrorToClerkWebAuthnErrorCode( code: string, message: string, action: 'get' | 'create', ): ClerkWebAuthnError { if (code === '1000' || code === '1004' || code === 'CreatePublicKeyCredentialDomException') { return new ClerkWebAuthnError(message, { code: action === 'create' ? 'passkey_registration_failed' : 'passkey_retrieval_failed', }); } if ( code === '1001' || code === 'CreateCredentialCancellationException' || code === 'GetCredentialCancellationException' ) { return new ClerkWebAuthnError(message, { code: 'passkey_registration_cancelled' }); } if (code === '1002') { return new ClerkWebAuthnError(message, { code: 'passkey_invalid_rpID_or_domain', docsUrl: 'https://clerk.com/docs/deployments/overview#authentication-across-subdomains', }); } if (code === '1003' || code === 'CreateCredentialInterruptedException') { return new ClerkWebAuthnError(message, { code: 'passkey_operation_aborted' }); } return new ClerkWebAuthnError(message, { code: action === 'create' ? 'passkey_registration_failed' : 'passkey_retrieval_failed', }); }