better-auth
Version:
The most comprehensive authentication framework for TypeScript.
1 lines • 13.3 kB
Source Map (JSON)
{"version":3,"file":"client.mjs","names":["isRequestInProgress: AbortController | null","error: any"],"sources":["../../../src/plugins/one-tap/client.ts"],"sourcesContent":["import type {\n\tBetterAuthClientPlugin,\n\tClientFetchOption,\n} from \"@better-auth/core\";\n\ndeclare global {\n\tinterface Window {\n\t\tgoogle?:\n\t\t\t| {\n\t\t\t\t\taccounts: {\n\t\t\t\t\t\tid: {\n\t\t\t\t\t\t\tinitialize: (config: any) => void;\n\t\t\t\t\t\t\tprompt: (callback?: (notification: any) => void) => void;\n\t\t\t\t\t\t};\n\t\t\t\t\t};\n\t\t\t }\n\t\t\t| undefined;\n\t\tgoogleScriptInitialized?: boolean | undefined;\n\t}\n}\n\nexport interface GoogleOneTapOptions {\n\t/**\n\t * Google client ID\n\t */\n\tclientId: string;\n\t/**\n\t * Auto select the account if the user is already signed in\n\t */\n\tautoSelect?: boolean | undefined;\n\t/**\n\t * Cancel the flow when the user taps outside the prompt\n\t */\n\tcancelOnTapOutside?: boolean | undefined;\n\t/**\n\t * The mode to use for the Google One Tap flow\n\t *\n\t * popup: Use a popup window\n\t * redirect: Redirect the user to the Google One Tap flow\n\t *\n\t * @default \"popup\"\n\t */\n\tuxMode?: (\"popup\" | \"redirect\") | undefined;\n\t/**\n\t * The context to use for the Google One Tap flow.\n\t *\n\t * @see {@link https://developers.google.com/identity/gsi/web/reference/js-reference}\n\t * @default \"signin\"\n\t */\n\tcontext?: (\"signin\" | \"signup\" | \"use\") | undefined;\n\t/**\n\t * Additional configuration options to pass to the Google One Tap API.\n\t */\n\tadditionalOptions?: Record<string, any> | undefined;\n\t/**\n\t * Configuration options for the prompt and exponential backoff behavior.\n\t */\n\tpromptOptions?:\n\t\t| {\n\t\t\t\t/**\n\t\t\t\t * Base delay (in milliseconds) for exponential backoff.\n\t\t\t\t * @default 1000\n\t\t\t\t */\n\t\t\t\tbaseDelay?: number;\n\t\t\t\t/**\n\t\t\t\t * Maximum number of prompt attempts before calling onPromptNotification.\n\t\t\t\t * @default 5\n\t\t\t\t */\n\t\t\t\tmaxAttempts?: number;\n\t\t\t\t/**\n\t\t\t\t * Whether to support FedCM (Federated Credential Management) support.\n\t\t\t\t *\n\t\t\t\t * @see {@link https://developer.chrome.com/docs/identity/fedcm/overview}\n\t\t\t\t * @default true\n\t\t\t\t */\n\t\t\t\tfedCM?: boolean | undefined;\n\t\t }\n\t\t| undefined;\n}\n\nexport interface GoogleOneTapActionOptions\n\textends Omit<GoogleOneTapOptions, \"clientId\" | \"promptOptions\"> {\n\tfetchOptions?: ClientFetchOption | undefined;\n\t/**\n\t * Callback URL.\n\t */\n\tcallbackURL?: string | undefined;\n\t/**\n\t * Optional callback that receives the prompt notification if (or when) the prompt is dismissed or skipped.\n\t * This lets you render an alternative UI (e.g. a Google Sign-In button) to restart the process.\n\t */\n\tonPromptNotification?: ((notification?: any | undefined) => void) | undefined;\n\tnonce?: string | undefined;\n}\n\ninterface IdentityCredential {\n\treadonly configURL: string;\n\treadonly isAutoSelected: boolean;\n\ttoken: string;\n}\n\nlet isRequestInProgress: AbortController | null = null;\n\nfunction isFedCMSupported() {\n\treturn typeof window !== \"undefined\" && \"IdentityCredential\" in window;\n}\n\nexport const oneTapClient = (options: GoogleOneTapOptions) => {\n\treturn {\n\t\tid: \"one-tap\",\n\t\tfetchPlugins: [\n\t\t\t{\n\t\t\t\tid: \"fedcm-signout-handle\",\n\t\t\t\tname: \"FedCM Sign-Out Handler\",\n\t\t\t\thooks: {\n\t\t\t\t\tasync onResponse(ctx) {\n\t\t\t\t\t\tif (!ctx.request.url.toString().includes(\"/sign-out\")) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (options.promptOptions?.fedCM === false || !isFedCMSupported()) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tnavigator.credentials.preventSilentAccess();\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t\tgetActions: ($fetch, _) => {\n\t\t\treturn {\n\t\t\t\toneTap: async (\n\t\t\t\t\topts?: GoogleOneTapActionOptions | undefined,\n\t\t\t\t\tfetchOptions?: ClientFetchOption | undefined,\n\t\t\t\t) => {\n\t\t\t\t\tif (isRequestInProgress && !isRequestInProgress.signal.aborted) {\n\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\"A Google One Tap request is already in progress. Please wait.\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (typeof window === \"undefined\" || !window.document) {\n\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\"Google One Tap is only available in browser environments\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tasync function callback(idToken: string) {\n\t\t\t\t\t\tawait $fetch(\"/one-tap/callback\", {\n\t\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\t\tbody: { idToken },\n\t\t\t\t\t\t\t...opts?.fetchOptions,\n\t\t\t\t\t\t\t...fetchOptions,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tif ((!opts?.fetchOptions && !fetchOptions) || opts?.callbackURL) {\n\t\t\t\t\t\t\twindow.location.href = opts?.callbackURL ?? \"/\";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst { autoSelect, cancelOnTapOutside, context } = opts ?? {};\n\t\t\t\t\tconst contextValue = context ?? options.context ?? \"signin\";\n\t\t\t\t\tconst clients = {\n\t\t\t\t\t\tfedCM: async () => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst identityCredential = (await navigator.credentials.get({\n\t\t\t\t\t\t\t\t\tidentity: {\n\t\t\t\t\t\t\t\t\t\tcontext: contextValue,\n\t\t\t\t\t\t\t\t\t\tproviders: [\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tconfigURL: \"https://accounts.google.com/gsi/fedcm.json\",\n\t\t\t\t\t\t\t\t\t\t\t\tclientId: options.clientId,\n\t\t\t\t\t\t\t\t\t\t\t\tnonce: opts?.nonce,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tmediation: autoSelect ? \"optional\" : \"required\",\n\t\t\t\t\t\t\t\t\tsignal: isRequestInProgress?.signal,\n\t\t\t\t\t\t\t\t} as any)) as IdentityCredential | null;\n\n\t\t\t\t\t\t\t\tif (!identityCredential?.token) {\n\t\t\t\t\t\t\t\t\t// Notify the caller that the prompt resulted in no token.\n\t\t\t\t\t\t\t\t\topts?.onPromptNotification?.(undefined);\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tawait callback(identityCredential.token);\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\t\tconsole.error(\"Error during FedCM callback:\", error);\n\t\t\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} catch (error: any) {\n\t\t\t\t\t\t\t\tif (error?.code && (error.code === 19 || error.code === 20)) {\n\t\t\t\t\t\t\t\t\t// Notify the caller that the prompt was closed/dismissed.\n\t\t\t\t\t\t\t\t\topts?.onPromptNotification?.(undefined);\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\toneTap: () => {\n\t\t\t\t\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\t\t\t\t\tlet isResolved = false;\n\t\t\t\t\t\t\t\tconst baseDelay = options.promptOptions?.baseDelay ?? 1000;\n\t\t\t\t\t\t\t\tconst maxAttempts = options.promptOptions?.maxAttempts ?? 5;\n\n\t\t\t\t\t\t\t\twindow.google?.accounts.id.initialize({\n\t\t\t\t\t\t\t\t\tclient_id: options.clientId,\n\t\t\t\t\t\t\t\t\tcallback: async (response: { credential: string }) => {\n\t\t\t\t\t\t\t\t\t\tisResolved = true;\n\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\tawait callback(response.credential);\n\t\t\t\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\t\t\t\tconsole.error(\"Error during One Tap callback:\", error);\n\t\t\t\t\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tauto_select: autoSelect,\n\t\t\t\t\t\t\t\t\tcancel_on_tap_outside: cancelOnTapOutside,\n\t\t\t\t\t\t\t\t\tcontext: contextValue,\n\t\t\t\t\t\t\t\t\tux_mode: opts?.uxMode || \"popup\",\n\t\t\t\t\t\t\t\t\tnonce: opts?.nonce,\n\t\t\t\t\t\t\t\t\t/**\n\t\t\t\t\t\t\t\t\t * @see {@link https://developers.google.com/identity/gsi/web/guides/overview}\n\t\t\t\t\t\t\t\t\t */\n\t\t\t\t\t\t\t\t\titp_support: true,\n\n\t\t\t\t\t\t\t\t\t...options.additionalOptions,\n\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\tconst handlePrompt = (attempt: number) => {\n\t\t\t\t\t\t\t\t\tif (isResolved) return;\n\n\t\t\t\t\t\t\t\t\twindow.google?.accounts.id.prompt((notification: any) => {\n\t\t\t\t\t\t\t\t\t\tif (isResolved) return;\n\n\t\t\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t\t\tnotification.isDismissedMoment &&\n\t\t\t\t\t\t\t\t\t\t\tnotification.isDismissedMoment()\n\t\t\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\t\t\tif (attempt < maxAttempts) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst delay = Math.pow(2, attempt) * baseDelay;\n\t\t\t\t\t\t\t\t\t\t\t\tsetTimeout(() => handlePrompt(attempt + 1), delay);\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\topts?.onPromptNotification?.(notification);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\t\t\t\t\tnotification.isSkippedMoment &&\n\t\t\t\t\t\t\t\t\t\t\tnotification.isSkippedMoment()\n\t\t\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\t\t\tif (attempt < maxAttempts) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst delay = Math.pow(2, attempt) * baseDelay;\n\t\t\t\t\t\t\t\t\t\t\t\tsetTimeout(() => handlePrompt(attempt + 1), delay);\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\topts?.onPromptNotification?.(notification);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\thandlePrompt(0);\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\n\t\t\t\t\tif (isRequestInProgress) {\n\t\t\t\t\t\tisRequestInProgress?.abort();\n\t\t\t\t\t}\n\t\t\t\t\tisRequestInProgress = new AbortController();\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst client =\n\t\t\t\t\t\t\toptions.promptOptions?.fedCM === false || !isFedCMSupported()\n\t\t\t\t\t\t\t\t? \"oneTap\"\n\t\t\t\t\t\t\t\t: \"fedCM\";\n\t\t\t\t\t\tif (client === \"oneTap\") {\n\t\t\t\t\t\t\tawait loadGoogleScript();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tawait clients[client]();\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconsole.error(\"Error during Google One Tap flow:\", error);\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tisRequestInProgress = null;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t\tgetAtoms($fetch) {\n\t\t\treturn {};\n\t\t},\n\t} satisfies BetterAuthClientPlugin;\n};\n\nconst loadGoogleScript = (): Promise<void> => {\n\treturn new Promise((resolve) => {\n\t\tif (window.googleScriptInitialized) {\n\t\t\tresolve();\n\t\t\treturn;\n\t\t}\n\n\t\tconst script = document.createElement(\"script\");\n\t\tscript.src = \"https://accounts.google.com/gsi/client\";\n\t\tscript.async = true;\n\t\tscript.defer = true;\n\t\tscript.onload = () => {\n\t\t\twindow.googleScriptInitialized = true;\n\t\t\tresolve();\n\t\t};\n\t\tdocument.head.appendChild(script);\n\t});\n};\n"],"mappings":"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"}