@cedros/login-react 0.0.30 → 0.0.32
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/{GoogleLoginButton-DEbiQngr.js → GoogleLoginButton-6ip-vudk.js} +111 -112
- package/dist/GoogleLoginButton-6ip-vudk.js.map +1 -0
- package/dist/GoogleLoginButton-BDCbxoCB.cjs +1 -0
- package/dist/GoogleLoginButton-BDCbxoCB.cjs.map +1 -0
- package/dist/SolanaLoginButton-B04dib6X.js +231 -0
- package/dist/SolanaLoginButton-B04dib6X.js.map +1 -0
- package/dist/SolanaLoginButton-nSJHVFpZ.cjs +1 -0
- package/dist/SolanaLoginButton-nSJHVFpZ.cjs.map +1 -0
- package/dist/google-only.cjs +1 -1
- package/dist/google-only.js +1 -1
- package/dist/index.cjs +12 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1352 -1390
- package/dist/index.js.map +1 -1
- package/dist/solana-only.cjs +1 -1
- package/dist/solana-only.d.ts +2 -2
- package/dist/solana-only.js +1 -1
- package/package.json +1 -1
- package/dist/GoogleLoginButton-CjBO3Rf1.cjs +0 -1
- package/dist/GoogleLoginButton-CjBO3Rf1.cjs.map +0 -1
- package/dist/GoogleLoginButton-DEbiQngr.js.map +0 -1
- package/dist/SolanaLoginButton-DAV3r4oB.cjs +0 -1
- package/dist/SolanaLoginButton-DAV3r4oB.cjs.map +0 -1
- package/dist/SolanaLoginButton-DFOoLqoj.js +0 -192
- package/dist/SolanaLoginButton-DFOoLqoj.js.map +0 -1
package/dist/solana-only.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("./useAuth-XZaciuLg.cjs"),r=require("./useCedrosLogin-DtJorrE7.cjs"),e=require("./SolanaLoginButton-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("./useAuth-XZaciuLg.cjs"),r=require("./useCedrosLogin-DtJorrE7.cjs"),e=require("./SolanaLoginButton-nSJHVFpZ.cjs"),n=require("./LoadingSpinner-d6sSxgQN.cjs"),i=require("./ErrorMessage-CHbYbVi2.cjs");exports.CedrosLoginProvider=o.CedrosLoginProvider;exports.useAuth=o.useAuth;exports.useCedrosLogin=r.useCedrosLogin;exports.SolanaLoginButton=e.SolanaLoginButton;exports.registerMobileWallet=e.registerMobileWallet;exports.useSolanaAuth=e.useSolanaAuth;exports.LoadingSpinner=n.LoadingSpinner;exports.ErrorMessage=i.ErrorMessage;
|
package/dist/solana-only.d.ts
CHANGED
|
@@ -410,9 +410,9 @@ declare interface PostLoginAction {
|
|
|
410
410
|
* }
|
|
411
411
|
* ```
|
|
412
412
|
*
|
|
413
|
-
* @returns true if registration succeeded, false
|
|
413
|
+
* @returns Promise resolving to true if registration succeeded, false otherwise
|
|
414
414
|
*/
|
|
415
|
-
export declare function registerMobileWallet(config?: MobileWalletConfig): boolean
|
|
415
|
+
export declare function registerMobileWallet(config?: MobileWalletConfig): Promise<boolean>;
|
|
416
416
|
|
|
417
417
|
/**
|
|
418
418
|
* Session handling configuration
|
package/dist/solana-only.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { C as e, u as s } from "./useAuth-CVLv2oKA.js";
|
|
2
2
|
import { u as t } from "./useCedrosLogin-CFfID-0i.js";
|
|
3
|
-
import { S as u, r as i, u as g } from "./SolanaLoginButton-
|
|
3
|
+
import { S as u, r as i, u as g } from "./SolanaLoginButton-B04dib6X.js";
|
|
4
4
|
import { L as f } from "./LoadingSpinner-6vml-zwr.js";
|
|
5
5
|
import { E as m } from "./ErrorMessage-CcEK0pYO.js";
|
|
6
6
|
export {
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";const u=require("react/jsx-runtime"),l=require("react"),k=require("./useCedrosLogin-DtJorrE7.cjs"),S=require("./LoadingSpinner-d6sSxgQN.cjs"),A={loading:!1,loaded:!1,error:null,callbacks:[],load(){return typeof window>"u"||typeof document>"u"?Promise.reject(new Error("Google Sign-In script loader cannot run in SSR")):this.loaded?Promise.resolve():this.loading?new Promise((e,d)=>{this.callbacks.push({resolve:e,reject:d})}):(this.loading=!0,new Promise((e,d)=>{this.callbacks.push({resolve:e,reject:d});const g=document.getElementById("google-gsi-script");if(g){window.google?.accounts?.id?(this.loaded=!0,this.loading=!1,this.callbacks.forEach(n=>n.resolve()),this.callbacks=[]):g.addEventListener("load",()=>{this.loaded=!0,this.loading=!1,this.callbacks.forEach(n=>n.resolve()),this.callbacks=[]});return}const t=document.createElement("script");t.src="https://accounts.google.com/gsi/client",t.async=!0,t.defer=!0,t.id="google-gsi-script",t.onload=()=>{this.loaded=!0,this.loading=!1,this.callbacks.forEach(n=>n.resolve()),this.callbacks=[]},t.onerror=()=>{this.loading=!1,t.remove();const n=new Error("Failed to load Google Sign-In script");this.callbacks.forEach(h=>h.reject(n)),this.callbacks=[]},document.head.appendChild(t)}))},_reset(){this.loading=!1,this.loaded=!1,this.error=null,this.callbacks=[]}};function w(){const{config:e,_internal:d}=k.useCedrosLogin(),[g,t]=l.useState(!1),[n,h]=l.useState(!1),[b,o]=l.useState(null),[E,p]=l.useState(null),c=l.useRef(null),m=l.useRef(e),f=l.useMemo(()=>new k.ApiClient({baseUrl:e.serverUrl,timeoutMs:e.requestTimeout,retryAttempts:e.retryAttempts}),[e.serverUrl,e.requestTimeout,e.retryAttempts]);l.useEffect(()=>{m.current=e},[e]);const R=l.useCallback(async s=>{const r=c.current;if(r)try{const a=await f.post("/google",{idToken:s.credential});m.current.callbacks?.onLoginSuccess?.(a.user,"google"),d?.handleLoginSuccess(a.user,a.tokens),t(!1),r.resolve(a)}catch(a){const i=k.handleApiError(a,"Unable to sign in with Google. Please try again.");i.code==="ACCOUNT_LINK_REQUIRED"&&p(s.credential),o(i),t(!1),r.reject(i)}finally{c.current=null}},[f,d]);l.useEffect(()=>{if(!e.googleClientId)return;let s=!0;const r=()=>{s&&(window.google?.accounts?.id?.initialize({client_id:e.googleClientId,callback:R,auto_select:!1,cancel_on_tap_outside:!0}),s&&h(!0))};return A.load().then(()=>{s&&r()}).catch(()=>{s&&o({code:"SERVER_ERROR",message:"Unable to load Google sign-in. Please refresh and try again."})}),()=>{s=!1}},[e.googleClientId,R]);const I=l.useCallback(async()=>{if(!e.googleClientId){const s={code:"VALIDATION_ERROR",message:"Google Client ID not configured"};throw o(s),s}if(!n){const s={code:"VALIDATION_ERROR",message:"Google sign-in is not ready yet. Please wait a moment and try again."};throw o(s),s}if(c.current){const s={code:"VALIDATION_ERROR",message:"Google sign-in is already in progress."};throw o(s),s}return t(!0),o(null),new Promise((s,r)=>{c.current={resolve:s,reject:r},window.google?.accounts?.id?.prompt(a=>{if(a.isNotDisplayed()){const i={code:"SERVER_ERROR",message:"Google sign-in popup was blocked. Please allow popups for this site and try again."};o(i),t(!1),c.current=null,r(i)}else if(a.isSkippedMoment()){const i={code:"SERVER_ERROR",message:"Google sign-in was cancelled."};o(i),t(!1),c.current=null,r(i)}else if(a.isDismissedMoment()){const i={code:"SERVER_ERROR",message:"Google sign-in was cancelled."};o(i),t(!1),c.current=null,r(i)}})})},[e.googleClientId,n]),C=l.useCallback(()=>o(null),[]),y=l.useCallback(()=>p(null),[]);return{signIn:I,isLoading:g,isInitialized:n,error:b,clearError:C,pendingLinkIdToken:E,clearPendingLink:y}}function L({onSuccess:e,onError:d,className:g="",variant:t="default",size:n="md",disabled:h=!1}){const{signIn:b,isLoading:o,isInitialized:E}=w(),p=async()=>{try{await b(),e?.()}catch(f){const R=f instanceof Error?f:new Error(String(f));d?.(R)}},c={sm:"cedros-button-sm",md:"cedros-button-md",lg:"cedros-button-lg"},m={default:"cedros-button-social",outline:"cedros-button-social-outline"};return u.jsxs("button",{type:"button",className:`cedros-button ${m[t]} ${c[n]} ${g}`,onClick:p,disabled:h||!E||o,"aria-label":"Sign in with Google",children:[o?u.jsx(S.LoadingSpinner,{size:"sm"}):u.jsxs("svg",{className:"cedros-button-icon",width:"18",height:"18",viewBox:"0 0 18 18",fill:"none","aria-hidden":"true",children:[u.jsx("path",{d:"M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.874 2.684-6.615z",fill:"#4285F4"}),u.jsx("path",{d:"M9.003 18c2.43 0 4.467-.806 5.956-2.18l-2.909-2.26c-.806.54-1.836.86-3.047.86-2.344 0-4.328-1.584-5.036-3.711H.96v2.332A8.997 8.997 0 0 0 9.003 18z",fill:"#34A853"}),u.jsx("path",{d:"M3.964 10.712A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.96A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.96 4.042l3.004-2.33z",fill:"#FBBC05"}),u.jsx("path",{d:"M9.003 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.464.891 11.428 0 9.002 0A8.997 8.997 0 0 0 .96 4.958l3.005 2.332c.708-2.127 2.692-3.71 5.036-3.71z",fill:"#EA4335"})]}),u.jsx("span",{children:"Continue with Google"})]})}exports.GoogleLoginButton=L;exports.useGoogleAuth=w;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleLoginButton-CjBO3Rf1.cjs","sources":["../src/hooks/useGoogleAuth.ts","../src/components/google/GoogleLoginButton.tsx"],"sourcesContent":["import { useState, useCallback, useEffect, useRef, useMemo } from 'react';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { ApiClient, handleApiError } from '../utils/apiClient';\nimport type { AuthResponse, AuthError } from '../types';\n\n/**\n * Module-level singleton for Google script loading (P-01)\n *\n * Prevents race conditions when multiple components mount simultaneously.\n * Uses a promise queue pattern to ensure the script is only loaded once.\n *\n * ## SSR Limitations (F-08)\n *\n * This singleton persists across the module lifecycle. In SSR environments:\n * - Module state persists between requests (potential cross-request leakage)\n * - Google Sign-In requires browser APIs (document, window)\n * - The hook guards against SSR with `googleClientId` checks\n *\n * For SSR frameworks (Next.js, Remix), ensure this hook is only used\n * in client-side components.\n *\n * ## Test Isolation\n *\n * For test isolation, call `scriptLoader._reset()` in test setup/teardown.\n *\n * @internal\n */\nconst scriptLoader = {\n loading: false,\n loaded: false,\n error: null as Error | null,\n callbacks: [] as Array<{ resolve: () => void; reject: (err: Error) => void }>,\n\n load(): Promise<void> {\n // SSR guard: avoid touching browser globals when running in Node/SSR.\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return Promise.reject(new Error('Google Sign-In script loader cannot run in SSR'));\n }\n\n // Already loaded\n if (this.loaded) {\n return Promise.resolve();\n }\n\n // Loading in progress - queue callback\n if (this.loading) {\n return new Promise((resolve, reject) => {\n this.callbacks.push({ resolve, reject });\n });\n }\n\n // Start loading\n this.loading = true;\n return new Promise((resolve, reject) => {\n this.callbacks.push({ resolve, reject });\n\n // Check if script already exists (from previous session or SSR)\n const existingScript = document.getElementById('google-gsi-script');\n if (existingScript) {\n if (window.google?.accounts?.id) {\n this.loaded = true;\n this.loading = false;\n this.callbacks.forEach((cb) => cb.resolve());\n this.callbacks = [];\n } else {\n existingScript.addEventListener('load', () => {\n this.loaded = true;\n this.loading = false;\n this.callbacks.forEach((cb) => cb.resolve());\n this.callbacks = [];\n });\n }\n return;\n }\n\n const script = document.createElement('script');\n script.src = 'https://accounts.google.com/gsi/client';\n script.async = true;\n script.defer = true;\n script.id = 'google-gsi-script';\n\n script.onload = () => {\n this.loaded = true;\n this.loading = false;\n this.callbacks.forEach((cb) => cb.resolve());\n this.callbacks = [];\n };\n\n script.onerror = () => {\n this.loading = false;\n // M-02: Remove failed script from DOM to allow retry on next load() call.\n // Without removal, retry finds existingScript and waits for a load event\n // that will never fire on an already-failed script.\n script.remove();\n const error = new Error('Failed to load Google Sign-In script');\n this.callbacks.forEach((cb) => cb.reject(error));\n this.callbacks = [];\n };\n\n document.head.appendChild(script);\n });\n },\n\n /**\n * Reset singleton state for test isolation (F-08)\n * @internal - Only use in test setup/teardown\n */\n _reset(): void {\n this.loading = false;\n this.loaded = false;\n this.error = null;\n this.callbacks = [];\n },\n};\n\n/** @internal */\nexport const _internalGoogleScriptLoader = scriptLoader;\n\nexport interface UseGoogleAuthReturn {\n signIn: () => Promise<AuthResponse>;\n isLoading: boolean;\n isInitialized: boolean;\n error: AuthError | null;\n clearError: () => void;\n /** ID token saved when ACCOUNT_LINK_REQUIRED is returned. Pass to POST /auth/link-oauth with the user's password. */\n pendingLinkIdToken: string | null;\n /** Clear the pending link state */\n clearPendingLink: () => void;\n}\n\ninterface PromiseCallbacks {\n resolve: (value: AuthResponse) => void;\n reject: (error: AuthError) => void;\n}\n\n/**\n * Hook for Google OAuth authentication.\n *\n * @example\n * ```tsx\n * function GoogleButton() {\n * const { signIn, isLoading, isInitialized, error } = useGoogleAuth();\n *\n * return (\n * <button onClick={signIn} disabled={!isInitialized || isLoading}>\n * {isLoading ? 'Signing in...' : 'Sign in with Google'}\n * </button>\n * );\n * }\n * ```\n */\nexport function useGoogleAuth(): UseGoogleAuthReturn {\n const { config, _internal } = useCedrosLogin();\n const [isLoading, setIsLoading] = useState(false);\n const [isInitialized, setIsInitialized] = useState(false);\n const [error, setError] = useState<AuthError | null>(null);\n\n const [pendingLinkIdToken, setPendingLinkIdToken] = useState<string | null>(null);\n\n const promiseCallbacksRef = useRef<PromiseCallbacks | null>(null);\n const configRef = useRef(config);\n\n const apiClient = useMemo(\n () =>\n new ApiClient({\n baseUrl: config.serverUrl,\n timeoutMs: config.requestTimeout,\n retryAttempts: config.retryAttempts,\n }),\n [config.serverUrl, config.requestTimeout, config.retryAttempts]\n );\n\n // Keep config ref in sync\n useEffect(() => {\n configRef.current = config;\n }, [config]);\n\n // Handle credential response from Google\n const handleCredentialResponse = useCallback(\n async (response: { credential: string }) => {\n const callbacks = promiseCallbacksRef.current;\n if (!callbacks) return;\n\n try {\n const data = await apiClient.post<AuthResponse>('/google', {\n idToken: response.credential,\n });\n configRef.current.callbacks?.onLoginSuccess?.(data.user, 'google');\n _internal?.handleLoginSuccess(data.user, data.tokens);\n setIsLoading(false);\n callbacks.resolve(data);\n } catch (err) {\n const authError = handleApiError(err, 'Unable to sign in with Google. Please try again.');\n if (authError.code === 'ACCOUNT_LINK_REQUIRED') {\n setPendingLinkIdToken(response.credential);\n }\n setError(authError);\n setIsLoading(false);\n callbacks.reject(authError);\n } finally {\n promiseCallbacksRef.current = null;\n }\n },\n [apiClient, _internal]\n );\n\n // P-01: Initialize Google Sign-In SDK using singleton loader\n useEffect(() => {\n // Early return if Google auth is not enabled\n if (!config.googleClientId) {\n return;\n }\n\n // Track mounted state to prevent state updates after unmount\n let isMounted = true;\n\n const initializeGoogleSignIn = () => {\n if (!isMounted) return;\n\n window.google?.accounts?.id?.initialize({\n client_id: config.googleClientId!,\n callback: handleCredentialResponse,\n auto_select: false,\n cancel_on_tap_outside: true,\n });\n\n if (isMounted) {\n setIsInitialized(true);\n }\n };\n\n // Use singleton loader to handle script loading\n scriptLoader\n .load()\n .then(() => {\n if (isMounted) {\n initializeGoogleSignIn();\n }\n })\n .catch(() => {\n if (isMounted) {\n setError({\n code: 'SERVER_ERROR',\n message: 'Unable to load Google sign-in. Please refresh and try again.',\n });\n }\n });\n\n return () => {\n isMounted = false;\n };\n }, [config.googleClientId, handleCredentialResponse]);\n\n const signIn = useCallback(async (): Promise<AuthResponse> => {\n if (!config.googleClientId) {\n const err: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Google Client ID not configured',\n };\n setError(err);\n throw err;\n }\n\n if (!isInitialized) {\n const err: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Google sign-in is not ready yet. Please wait a moment and try again.',\n };\n setError(err);\n throw err;\n }\n\n if (promiseCallbacksRef.current) {\n const err: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Google sign-in is already in progress.',\n };\n setError(err);\n throw err;\n }\n\n setIsLoading(true);\n setError(null);\n\n return new Promise<AuthResponse>((resolve, reject) => {\n promiseCallbacksRef.current = { resolve, reject };\n\n // Show Google One Tap prompt\n window.google?.accounts?.id?.prompt((notification) => {\n if (notification.isNotDisplayed()) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: 'Google sign-in popup was blocked. Please allow popups for this site and try again.',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n reject(err);\n } else if (notification.isSkippedMoment()) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: 'Google sign-in was cancelled.',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n reject(err);\n } else if (notification.isDismissedMoment()) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: 'Google sign-in was cancelled.',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n reject(err);\n }\n });\n });\n }, [config.googleClientId, isInitialized]);\n\n const clearError = useCallback(() => setError(null), []);\n const clearPendingLink = useCallback(() => setPendingLinkIdToken(null), []);\n\n return {\n signIn,\n isLoading,\n isInitialized,\n error,\n clearError,\n pendingLinkIdToken,\n clearPendingLink,\n };\n}\n\n// Type declaration for Google Identity Services\ndeclare global {\n interface Window {\n google?: {\n accounts?: {\n id?: {\n initialize: (config: {\n client_id: string;\n callback: (response: { credential: string }) => void;\n auto_select?: boolean;\n cancel_on_tap_outside?: boolean;\n }) => void;\n prompt: (\n callback: (notification: {\n isNotDisplayed: () => boolean;\n isSkippedMoment: () => boolean;\n isDismissedMoment: () => boolean;\n getMomentType: () => string;\n }) => void\n ) => void;\n renderButton: (element: HTMLElement, config: object) => void;\n disableAutoSelect: () => void;\n };\n };\n };\n }\n}\n","import { useGoogleAuth } from '../../hooks/useGoogleAuth';\nimport { LoadingSpinner } from '../shared/LoadingSpinner';\n\nexport interface GoogleLoginButtonProps {\n onSuccess?: () => void;\n onError?: (error: Error) => void;\n className?: string;\n variant?: 'default' | 'outline';\n size?: 'sm' | 'md' | 'lg';\n disabled?: boolean;\n}\n\n/**\n * Google OAuth login button\n */\nexport function GoogleLoginButton({\n onSuccess,\n onError,\n className = '',\n variant = 'default',\n size = 'md',\n disabled = false,\n}: GoogleLoginButtonProps) {\n const { signIn, isLoading, isInitialized } = useGoogleAuth();\n\n const handleClick = async () => {\n try {\n await signIn();\n onSuccess?.();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n onError?.(error);\n }\n };\n\n const sizeClasses = {\n sm: 'cedros-button-sm',\n md: 'cedros-button-md',\n lg: 'cedros-button-lg',\n };\n\n const variantClasses = {\n default: 'cedros-button-social',\n outline: 'cedros-button-social-outline',\n };\n\n return (\n <button\n type=\"button\"\n className={`cedros-button ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}\n onClick={handleClick}\n disabled={disabled || !isInitialized || isLoading}\n aria-label=\"Sign in with Google\"\n >\n {isLoading ? (\n <LoadingSpinner size=\"sm\" />\n ) : (\n <svg\n className=\"cedros-button-icon\"\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.874 2.684-6.615z\"\n fill=\"#4285F4\"\n />\n <path\n d=\"M9.003 18c2.43 0 4.467-.806 5.956-2.18l-2.909-2.26c-.806.54-1.836.86-3.047.86-2.344 0-4.328-1.584-5.036-3.711H.96v2.332A8.997 8.997 0 0 0 9.003 18z\"\n fill=\"#34A853\"\n />\n <path\n d=\"M3.964 10.712A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.96A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.96 4.042l3.004-2.33z\"\n fill=\"#FBBC05\"\n />\n <path\n d=\"M9.003 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.464.891 11.428 0 9.002 0A8.997 8.997 0 0 0 .96 4.958l3.005 2.332c.708-2.127 2.692-3.71 5.036-3.71z\"\n fill=\"#EA4335\"\n />\n </svg>\n )}\n <span>Continue with Google</span>\n </button>\n );\n}\n"],"names":["scriptLoader","resolve","reject","existingScript","cb","script","error","useGoogleAuth","config","_internal","useCedrosLogin","isLoading","setIsLoading","useState","isInitialized","setIsInitialized","setError","pendingLinkIdToken","setPendingLinkIdToken","promiseCallbacksRef","useRef","configRef","apiClient","useMemo","ApiClient","useEffect","handleCredentialResponse","useCallback","response","callbacks","data","err","authError","handleApiError","isMounted","initializeGoogleSignIn","signIn","notification","clearError","clearPendingLink","GoogleLoginButton","onSuccess","onError","className","variant","size","disabled","handleClick","sizeClasses","variantClasses","jsxs","jsx","LoadingSpinner"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleLoginButton-DEbiQngr.js","sources":["../src/hooks/useGoogleAuth.ts","../src/components/google/GoogleLoginButton.tsx"],"sourcesContent":["import { useState, useCallback, useEffect, useRef, useMemo } from 'react';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { ApiClient, handleApiError } from '../utils/apiClient';\nimport type { AuthResponse, AuthError } from '../types';\n\n/**\n * Module-level singleton for Google script loading (P-01)\n *\n * Prevents race conditions when multiple components mount simultaneously.\n * Uses a promise queue pattern to ensure the script is only loaded once.\n *\n * ## SSR Limitations (F-08)\n *\n * This singleton persists across the module lifecycle. In SSR environments:\n * - Module state persists between requests (potential cross-request leakage)\n * - Google Sign-In requires browser APIs (document, window)\n * - The hook guards against SSR with `googleClientId` checks\n *\n * For SSR frameworks (Next.js, Remix), ensure this hook is only used\n * in client-side components.\n *\n * ## Test Isolation\n *\n * For test isolation, call `scriptLoader._reset()` in test setup/teardown.\n *\n * @internal\n */\nconst scriptLoader = {\n loading: false,\n loaded: false,\n error: null as Error | null,\n callbacks: [] as Array<{ resolve: () => void; reject: (err: Error) => void }>,\n\n load(): Promise<void> {\n // SSR guard: avoid touching browser globals when running in Node/SSR.\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return Promise.reject(new Error('Google Sign-In script loader cannot run in SSR'));\n }\n\n // Already loaded\n if (this.loaded) {\n return Promise.resolve();\n }\n\n // Loading in progress - queue callback\n if (this.loading) {\n return new Promise((resolve, reject) => {\n this.callbacks.push({ resolve, reject });\n });\n }\n\n // Start loading\n this.loading = true;\n return new Promise((resolve, reject) => {\n this.callbacks.push({ resolve, reject });\n\n // Check if script already exists (from previous session or SSR)\n const existingScript = document.getElementById('google-gsi-script');\n if (existingScript) {\n if (window.google?.accounts?.id) {\n this.loaded = true;\n this.loading = false;\n this.callbacks.forEach((cb) => cb.resolve());\n this.callbacks = [];\n } else {\n existingScript.addEventListener('load', () => {\n this.loaded = true;\n this.loading = false;\n this.callbacks.forEach((cb) => cb.resolve());\n this.callbacks = [];\n });\n }\n return;\n }\n\n const script = document.createElement('script');\n script.src = 'https://accounts.google.com/gsi/client';\n script.async = true;\n script.defer = true;\n script.id = 'google-gsi-script';\n\n script.onload = () => {\n this.loaded = true;\n this.loading = false;\n this.callbacks.forEach((cb) => cb.resolve());\n this.callbacks = [];\n };\n\n script.onerror = () => {\n this.loading = false;\n // M-02: Remove failed script from DOM to allow retry on next load() call.\n // Without removal, retry finds existingScript and waits for a load event\n // that will never fire on an already-failed script.\n script.remove();\n const error = new Error('Failed to load Google Sign-In script');\n this.callbacks.forEach((cb) => cb.reject(error));\n this.callbacks = [];\n };\n\n document.head.appendChild(script);\n });\n },\n\n /**\n * Reset singleton state for test isolation (F-08)\n * @internal - Only use in test setup/teardown\n */\n _reset(): void {\n this.loading = false;\n this.loaded = false;\n this.error = null;\n this.callbacks = [];\n },\n};\n\n/** @internal */\nexport const _internalGoogleScriptLoader = scriptLoader;\n\nexport interface UseGoogleAuthReturn {\n signIn: () => Promise<AuthResponse>;\n isLoading: boolean;\n isInitialized: boolean;\n error: AuthError | null;\n clearError: () => void;\n /** ID token saved when ACCOUNT_LINK_REQUIRED is returned. Pass to POST /auth/link-oauth with the user's password. */\n pendingLinkIdToken: string | null;\n /** Clear the pending link state */\n clearPendingLink: () => void;\n}\n\ninterface PromiseCallbacks {\n resolve: (value: AuthResponse) => void;\n reject: (error: AuthError) => void;\n}\n\n/**\n * Hook for Google OAuth authentication.\n *\n * @example\n * ```tsx\n * function GoogleButton() {\n * const { signIn, isLoading, isInitialized, error } = useGoogleAuth();\n *\n * return (\n * <button onClick={signIn} disabled={!isInitialized || isLoading}>\n * {isLoading ? 'Signing in...' : 'Sign in with Google'}\n * </button>\n * );\n * }\n * ```\n */\nexport function useGoogleAuth(): UseGoogleAuthReturn {\n const { config, _internal } = useCedrosLogin();\n const [isLoading, setIsLoading] = useState(false);\n const [isInitialized, setIsInitialized] = useState(false);\n const [error, setError] = useState<AuthError | null>(null);\n\n const [pendingLinkIdToken, setPendingLinkIdToken] = useState<string | null>(null);\n\n const promiseCallbacksRef = useRef<PromiseCallbacks | null>(null);\n const configRef = useRef(config);\n\n const apiClient = useMemo(\n () =>\n new ApiClient({\n baseUrl: config.serverUrl,\n timeoutMs: config.requestTimeout,\n retryAttempts: config.retryAttempts,\n }),\n [config.serverUrl, config.requestTimeout, config.retryAttempts]\n );\n\n // Keep config ref in sync\n useEffect(() => {\n configRef.current = config;\n }, [config]);\n\n // Handle credential response from Google\n const handleCredentialResponse = useCallback(\n async (response: { credential: string }) => {\n const callbacks = promiseCallbacksRef.current;\n if (!callbacks) return;\n\n try {\n const data = await apiClient.post<AuthResponse>('/google', {\n idToken: response.credential,\n });\n configRef.current.callbacks?.onLoginSuccess?.(data.user, 'google');\n _internal?.handleLoginSuccess(data.user, data.tokens);\n setIsLoading(false);\n callbacks.resolve(data);\n } catch (err) {\n const authError = handleApiError(err, 'Unable to sign in with Google. Please try again.');\n if (authError.code === 'ACCOUNT_LINK_REQUIRED') {\n setPendingLinkIdToken(response.credential);\n }\n setError(authError);\n setIsLoading(false);\n callbacks.reject(authError);\n } finally {\n promiseCallbacksRef.current = null;\n }\n },\n [apiClient, _internal]\n );\n\n // P-01: Initialize Google Sign-In SDK using singleton loader\n useEffect(() => {\n // Early return if Google auth is not enabled\n if (!config.googleClientId) {\n return;\n }\n\n // Track mounted state to prevent state updates after unmount\n let isMounted = true;\n\n const initializeGoogleSignIn = () => {\n if (!isMounted) return;\n\n window.google?.accounts?.id?.initialize({\n client_id: config.googleClientId!,\n callback: handleCredentialResponse,\n auto_select: false,\n cancel_on_tap_outside: true,\n });\n\n if (isMounted) {\n setIsInitialized(true);\n }\n };\n\n // Use singleton loader to handle script loading\n scriptLoader\n .load()\n .then(() => {\n if (isMounted) {\n initializeGoogleSignIn();\n }\n })\n .catch(() => {\n if (isMounted) {\n setError({\n code: 'SERVER_ERROR',\n message: 'Unable to load Google sign-in. Please refresh and try again.',\n });\n }\n });\n\n return () => {\n isMounted = false;\n };\n }, [config.googleClientId, handleCredentialResponse]);\n\n const signIn = useCallback(async (): Promise<AuthResponse> => {\n if (!config.googleClientId) {\n const err: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Google Client ID not configured',\n };\n setError(err);\n throw err;\n }\n\n if (!isInitialized) {\n const err: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Google sign-in is not ready yet. Please wait a moment and try again.',\n };\n setError(err);\n throw err;\n }\n\n if (promiseCallbacksRef.current) {\n const err: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Google sign-in is already in progress.',\n };\n setError(err);\n throw err;\n }\n\n setIsLoading(true);\n setError(null);\n\n return new Promise<AuthResponse>((resolve, reject) => {\n promiseCallbacksRef.current = { resolve, reject };\n\n // Show Google One Tap prompt\n window.google?.accounts?.id?.prompt((notification) => {\n if (notification.isNotDisplayed()) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: 'Google sign-in popup was blocked. Please allow popups for this site and try again.',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n reject(err);\n } else if (notification.isSkippedMoment()) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: 'Google sign-in was cancelled.',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n reject(err);\n } else if (notification.isDismissedMoment()) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: 'Google sign-in was cancelled.',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n reject(err);\n }\n });\n });\n }, [config.googleClientId, isInitialized]);\n\n const clearError = useCallback(() => setError(null), []);\n const clearPendingLink = useCallback(() => setPendingLinkIdToken(null), []);\n\n return {\n signIn,\n isLoading,\n isInitialized,\n error,\n clearError,\n pendingLinkIdToken,\n clearPendingLink,\n };\n}\n\n// Type declaration for Google Identity Services\ndeclare global {\n interface Window {\n google?: {\n accounts?: {\n id?: {\n initialize: (config: {\n client_id: string;\n callback: (response: { credential: string }) => void;\n auto_select?: boolean;\n cancel_on_tap_outside?: boolean;\n }) => void;\n prompt: (\n callback: (notification: {\n isNotDisplayed: () => boolean;\n isSkippedMoment: () => boolean;\n isDismissedMoment: () => boolean;\n getMomentType: () => string;\n }) => void\n ) => void;\n renderButton: (element: HTMLElement, config: object) => void;\n disableAutoSelect: () => void;\n };\n };\n };\n }\n}\n","import { useGoogleAuth } from '../../hooks/useGoogleAuth';\nimport { LoadingSpinner } from '../shared/LoadingSpinner';\n\nexport interface GoogleLoginButtonProps {\n onSuccess?: () => void;\n onError?: (error: Error) => void;\n className?: string;\n variant?: 'default' | 'outline';\n size?: 'sm' | 'md' | 'lg';\n disabled?: boolean;\n}\n\n/**\n * Google OAuth login button\n */\nexport function GoogleLoginButton({\n onSuccess,\n onError,\n className = '',\n variant = 'default',\n size = 'md',\n disabled = false,\n}: GoogleLoginButtonProps) {\n const { signIn, isLoading, isInitialized } = useGoogleAuth();\n\n const handleClick = async () => {\n try {\n await signIn();\n onSuccess?.();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n onError?.(error);\n }\n };\n\n const sizeClasses = {\n sm: 'cedros-button-sm',\n md: 'cedros-button-md',\n lg: 'cedros-button-lg',\n };\n\n const variantClasses = {\n default: 'cedros-button-social',\n outline: 'cedros-button-social-outline',\n };\n\n return (\n <button\n type=\"button\"\n className={`cedros-button ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}\n onClick={handleClick}\n disabled={disabled || !isInitialized || isLoading}\n aria-label=\"Sign in with Google\"\n >\n {isLoading ? (\n <LoadingSpinner size=\"sm\" />\n ) : (\n <svg\n className=\"cedros-button-icon\"\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.874 2.684-6.615z\"\n fill=\"#4285F4\"\n />\n <path\n d=\"M9.003 18c2.43 0 4.467-.806 5.956-2.18l-2.909-2.26c-.806.54-1.836.86-3.047.86-2.344 0-4.328-1.584-5.036-3.711H.96v2.332A8.997 8.997 0 0 0 9.003 18z\"\n fill=\"#34A853\"\n />\n <path\n d=\"M3.964 10.712A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.96A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.96 4.042l3.004-2.33z\"\n fill=\"#FBBC05\"\n />\n <path\n d=\"M9.003 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.464.891 11.428 0 9.002 0A8.997 8.997 0 0 0 .96 4.958l3.005 2.332c.708-2.127 2.692-3.71 5.036-3.71z\"\n fill=\"#EA4335\"\n />\n </svg>\n )}\n <span>Continue with Google</span>\n </button>\n );\n}\n"],"names":["scriptLoader","resolve","reject","existingScript","cb","script","error","useGoogleAuth","config","_internal","useCedrosLogin","isLoading","setIsLoading","useState","isInitialized","setIsInitialized","setError","pendingLinkIdToken","setPendingLinkIdToken","promiseCallbacksRef","useRef","configRef","apiClient","useMemo","ApiClient","useEffect","handleCredentialResponse","useCallback","response","callbacks","data","err","authError","handleApiError","isMounted","initializeGoogleSignIn","signIn","notification","clearError","clearPendingLink","GoogleLoginButton","onSuccess","onError","className","variant","size","disabled","handleClick","sizeClasses","jsxs","jsx","LoadingSpinner"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";const l=require("react/jsx-runtime"),n=require("react"),T=require("@solana/wallet-adapter-react"),D=require("@solana/wallet-adapter-react-ui"),W=require("./useCedrosLogin-DtJorrE7.cjs"),N=require("./validation-BuGQrA-K.cjs"),J=require("./LoadingSpinner-d6sSxgQN.cjs");function _(){const{config:e,_internal:t}=W.useCedrosLogin(),[S,o]=n.useState(!1),[j,c]=n.useState(null),g=n.useMemo(()=>new W.ApiClient({baseUrl:e.serverUrl,timeoutMs:e.requestTimeout,retryAttempts:e.retryAttempts}),[e.serverUrl,e.requestTimeout,e.retryAttempts]),L=n.useCallback(async f=>{if(!N.validateSolanaPublicKey(f)){const u={code:"INVALID_PUBLIC_KEY",message:"Invalid Solana public key format"};throw c(u),u}o(!0),c(null);try{return await g.post("/solana/challenge",{publicKey:f},{credentials:"omit"})}catch(u){const r=W.handleApiError(u,"Unable to start wallet verification. Please try again.");throw c(r),r}finally{o(!1)}},[g]),i=n.useCallback(async(f,u,r)=>{if(!N.validateSolanaPublicKey(f)){const s={code:"INVALID_PUBLIC_KEY",message:"Invalid Solana public key format"};throw c(s),s}o(!0),c(null);try{const s=await g.post("/solana",{publicKey:f,signature:u,message:r});return e.callbacks?.onLoginSuccess?.(s.user,"solana"),t?.handleLoginSuccess(s.user,s.tokens),s}catch(s){const A=W.handleApiError(s,"Unable to sign in with your wallet. Please try again.");throw c(A),A}finally{o(!1)}},[g,e.callbacks,t]),C=n.useCallback(()=>c(null),[]);return{requestChallenge:L,signIn:i,isLoading:S,error:j,clearError:C}}function H(e){if(typeof window>"u")return!1;try{const t=require("@solana-mobile/wallet-standard-mobile"),S=e?.chains??["solana:mainnet"],o={appIdentity:{name:e?.name,uri:e?.uri,icon:e?.icon},chains:S};return typeof t.createDefaultAuthorizationCache=="function"&&(o.authorizationCache=t.createDefaultAuthorizationCache()),typeof t.createDefaultChainSelector=="function"&&(o.chainSelector=t.createDefaultChainSelector()),typeof t.createDefaultWalletNotFoundHandler=="function"&&(o.onWalletNotFound=t.createDefaultWalletNotFoundHandler()),t.registerMwa(o),!0}catch{return!1}}typeof navigator<"u"&&/Android/i.test(navigator.userAgent)&&H();const Q=[];function X(e){return e.walletContext?l.jsx(D.WalletModalProvider,{children:l.jsx(K,{...e})}):l.jsx(T.WalletProvider,{wallets:Q,localStorageKey:"cedros-walletName",children:l.jsx(D.WalletModalProvider,{children:l.jsx(K,{...e})})})}function K({onSuccess:e,onError:t,className:S="",variant:o="default",size:j="md",disabled:c=!1,hideIfNoWallet:g=!0,onLoadingChange:L,walletContext:i}){const{requestChallenge:C,signIn:f,isLoading:u}=_(),r=T.useWallet(),{visible:s,setVisible:A}=D.useWalletModal(),[h,y]=n.useState(!1),[U,P]=n.useState(!1),E=n.useRef(!1),q=n.useRef(!1),v=n.useRef(!1),d=i?.connected??r.connected,b=i?.connecting??r.connecting,m=i?.publicKey??r.publicKey,w=i?.signMessage??r.signMessage,p=i?.wallet??r.wallet,V=i?.wallets??r.wallets,F=i?i.select:a=>r.select(a),z=i?.connect??r.connect,k=V.filter(a=>a.adapter.readyState==="Installed"||a.adapter.readyState==="Loadable"),R=n.useCallback(async()=>{if(!E.current){if(!m||!w){t?.(new Error("Wallet not ready"));return}E.current=!0;try{const a=m.toBase58(),I=await C(a),G=new TextEncoder().encode(I.message),x=await w(G);if(!(x instanceof Uint8Array)||x.length===0)throw new Error("Wallet returned invalid signature");let B;try{B=btoa(String.fromCharCode(...x))}catch{throw new Error("Failed to encode signature")}await f(a,B,I.message),v.current=!1,e?.()}catch(a){const I=a instanceof Error?a:new Error(String(a));v.current=!0,t?.(I)}finally{E.current=!1,y(!1)}}},[m,w,C,f,e,t]);if(n.useEffect(()=>{U&&p&&!d&&!b&&(P(!1),z().catch(a=>{t?.(a instanceof Error?a:new Error(String(a))),y(!1)}))},[U,p,d,b,z,t]),n.useEffect(()=>{h&&d&&m&&w&&!E.current&&R().catch(()=>{})},[h,d,m,w,R]),n.useEffect(()=>{s?q.current=!0:q.current&&(q.current=!1,h&&!d&&p&&!b?P(!0):h&&!d&&y(!1))},[s,h,d,p,b]),g&&k.length===0)return null;const Y=async()=>{c||u||b||(d&&m&&w&&!v.current?(y(!0),await R()):k.length===1&&!p?(F(k[0].adapter.name),y(!0),P(!0)):(v.current=!1,p&&r.select(null),A(!0),y(!0)))},$={sm:"cedros-button-sm",md:"cedros-button-md",lg:"cedros-button-lg"},O={default:"cedros-button-social",outline:"cedros-button-social-outline"},M=u||b||h&&!d;return n.useEffect(()=>{L?.(M)},[M,L]),l.jsxs("button",{type:"button",className:`cedros-button ${O[o]} ${$[j]} ${S}`,onClick:Y,disabled:c||M,"aria-label":"Continue with Solana",children:[M?l.jsx(J.LoadingSpinner,{size:"sm"}):l.jsxs("svg",{className:"cedros-button-icon",width:"18",height:"18",viewBox:"0 0 128 128",fill:"currentColor","aria-hidden":"true",children:[l.jsx("path",{d:"M25.38 96.04a4.35 4.35 0 0 1 3.07-1.27h91.68c1.93 0 2.9 2.34 1.54 3.7l-17.71 17.72a4.35 4.35 0 0 1-3.07 1.27H9.21c-1.93 0-2.9-2.34-1.54-3.7l17.71-17.72z"}),l.jsx("path",{d:"M25.38 11.81a4.47 4.47 0 0 1 3.07-1.27h91.68c1.93 0 2.9 2.34 1.54 3.7L103.96 31.96a4.35 4.35 0 0 1-3.07 1.27H9.21c-1.93 0-2.9-2.34-1.54-3.7L25.38 11.81z"}),l.jsx("path",{d:"M102.62 53.76a4.35 4.35 0 0 0-3.07-1.27H7.87c-1.93 0-2.9 2.34-1.54 3.7l17.71 17.72a4.35 4.35 0 0 0 3.07 1.27h91.68c1.93 0 2.9-2.34 1.54-3.7L102.62 53.76z"})]}),l.jsx("span",{children:"Continue with Solana"})]})}exports.SolanaLoginButton=X;exports.registerMobileWallet=H;exports.useSolanaAuth=_;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SolanaLoginButton-DAV3r4oB.cjs","sources":["../src/hooks/useSolanaAuth.ts","../src/utils/mobileWalletAdapter.ts","../src/components/solana/SolanaLoginButton.tsx"],"sourcesContent":["import { useState, useCallback, useMemo } from 'react';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { ApiClient, handleApiError } from '../utils/apiClient';\nimport { validateSolanaPublicKey } from '../utils/validation';\nimport type { AuthResponse, AuthError, ChallengeResponse } from '../types';\n\nexport interface UseSolanaAuthReturn {\n requestChallenge: (publicKey: string) => Promise<ChallengeResponse>;\n signIn: (publicKey: string, signature: string, message: string) => Promise<AuthResponse>;\n isLoading: boolean;\n error: AuthError | null;\n clearError: () => void;\n}\n\n/**\n * Hook for Solana wallet authentication.\n *\n * @example\n * ```tsx\n * function SolanaLogin() {\n * const { requestChallenge, signIn, isLoading } = useSolanaAuth();\n * const { publicKey, signMessage } = useWallet();\n *\n * const handleLogin = async () => {\n * const challenge = await requestChallenge(publicKey.toBase58());\n * const signature = await signMessage(new TextEncoder().encode(challenge.message));\n * const result = await signIn(\n * publicKey.toBase58(),\n * Buffer.from(signature).toString('base64'),\n * challenge.message\n * );\n * };\n * }\n * ```\n */\nexport function useSolanaAuth(): UseSolanaAuthReturn {\n const { config, _internal } = useCedrosLogin();\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<AuthError | null>(null);\n\n const apiClient = useMemo(\n () =>\n new ApiClient({\n baseUrl: config.serverUrl,\n timeoutMs: config.requestTimeout,\n retryAttempts: config.retryAttempts,\n }),\n [config.serverUrl, config.requestTimeout, config.retryAttempts]\n );\n\n const requestChallenge = useCallback(\n async (publicKey: string): Promise<ChallengeResponse> => {\n // Validate public key format before making API call\n if (!validateSolanaPublicKey(publicKey)) {\n const authError: AuthError = {\n code: 'INVALID_PUBLIC_KEY',\n message: 'Invalid Solana public key format',\n };\n setError(authError);\n throw authError;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const data = await apiClient.post<ChallengeResponse>(\n '/solana/challenge',\n { publicKey },\n { credentials: 'omit' }\n );\n return data;\n } catch (err) {\n const authError = handleApiError(err, 'Unable to start wallet verification. Please try again.');\n setError(authError);\n throw authError;\n } finally {\n setIsLoading(false);\n }\n },\n [apiClient]\n );\n\n const signIn = useCallback(\n async (publicKey: string, signature: string, message: string): Promise<AuthResponse> => {\n // Validate public key format before making API call\n if (!validateSolanaPublicKey(publicKey)) {\n const authError: AuthError = {\n code: 'INVALID_PUBLIC_KEY',\n message: 'Invalid Solana public key format',\n };\n setError(authError);\n throw authError;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const data = await apiClient.post<AuthResponse>('/solana', {\n publicKey,\n signature,\n message,\n });\n config.callbacks?.onLoginSuccess?.(data.user, 'solana');\n _internal?.handleLoginSuccess(data.user, data.tokens);\n return data;\n } catch (err) {\n const authError = handleApiError(err, 'Unable to sign in with your wallet. Please try again.');\n setError(authError);\n throw authError;\n } finally {\n setIsLoading(false);\n }\n },\n [apiClient, config.callbacks, _internal]\n );\n\n const clearError = useCallback(() => setError(null), []);\n\n return {\n requestChallenge,\n signIn,\n isLoading,\n error,\n clearError,\n };\n}\n","/**\n * Mobile Wallet Adapter (MWA) registration for web.\n *\n * On Android Chrome, MWA lets users authenticate with their installed Solana\n * wallet app (e.g., Phantom, Solflare) via Android Intents — no browser\n * extension needed.\n *\n * Once registered, MWA appears as a wallet option in the wallet adapter's\n * wallet list (alongside browser extension wallets). Users see it as\n * \"Use Installed Wallet\" in the wallet selector.\n *\n * Requires the optional peer dependency: @solana-mobile/wallet-standard-mobile\n *\n * @see https://docs.solanamobile.com/get-started/web/installation\n */\n\nexport interface MobileWalletConfig {\n /** App name shown in the wallet's authorization dialog */\n name?: string;\n /** App URI for identity verification */\n uri?: string;\n /** App icon path/URL shown in the wallet dialog */\n icon?: string;\n /** Solana cluster(s) to support. Default: ['solana:mainnet'] */\n chains?: string[];\n}\n\n/**\n * Register Mobile Wallet Adapter as a wallet-standard wallet.\n *\n * Call this once at your application root (before rendering). After registration,\n * MWA automatically appears as \"Use Installed Wallet\" for users browsing on\n * Android Chrome with a Solana wallet app installed.\n *\n * Must be called in a non-SSR context (browser only). For Next.js, call in a\n * Client Component with `'use client'`.\n *\n * @example\n * ```tsx\n * import { registerMobileWallet, CedrosLoginProvider } from '@cedros/login-react';\n *\n * // Register before provider mounts\n * registerMobileWallet({ name: 'My App', uri: 'https://myapp.com' });\n *\n * function App() {\n * return (\n * <CedrosLoginProvider config={{ serverUrl: '...' }}>\n * <LoginForm />\n * </CedrosLoginProvider>\n * );\n * }\n * ```\n *\n * @returns true if registration succeeded, false if package not installed or SSR\n */\nexport function registerMobileWallet(config?: MobileWalletConfig): boolean {\n if (typeof window === 'undefined') {\n return false;\n }\n\n try {\n // Dynamic import to avoid bundling the optional peer dep\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const mwa = require('@solana-mobile/wallet-standard-mobile');\n\n const chains = config?.chains ?? ['solana:mainnet'];\n\n const registrationConfig: Record<string, unknown> = {\n appIdentity: {\n name: config?.name,\n uri: config?.uri,\n icon: config?.icon,\n },\n chains,\n };\n\n // Use built-in defaults for optional config if available\n if (typeof mwa.createDefaultAuthorizationCache === 'function') {\n registrationConfig.authorizationCache = mwa.createDefaultAuthorizationCache();\n }\n if (typeof mwa.createDefaultChainSelector === 'function') {\n registrationConfig.chainSelector = mwa.createDefaultChainSelector();\n }\n if (typeof mwa.createDefaultWalletNotFoundHandler === 'function') {\n registrationConfig.onWalletNotFound = mwa.createDefaultWalletNotFoundHandler();\n }\n\n mwa.registerMwa(registrationConfig);\n return true;\n } catch {\n // @solana-mobile/wallet-standard-mobile not installed\n return false;\n }\n}\n","import { useState, useEffect, useCallback, useRef } from 'react';\nimport { WalletProvider, useWallet } from '@solana/wallet-adapter-react';\nimport { WalletModalProvider, useWalletModal } from '@solana/wallet-adapter-react-ui';\nimport type { WalletName } from '@solana/wallet-adapter-base';\nimport { useSolanaAuth } from '../../hooks/useSolanaAuth';\nimport { registerMobileWallet } from '../../utils/mobileWalletAdapter';\nimport { LoadingSpinner } from '../shared/LoadingSpinner';\n\n// Auto-register Mobile Wallet Adapter on Android for Solana phone (Seeker/Seed Vault) support.\n// Runs at import time so detectSolanaWallets() in LoginForm picks up MWA before first render.\nif (typeof navigator !== 'undefined' && /Android/i.test(navigator.userAgent)) {\n registerMobileWallet();\n}\n\nexport interface SolanaLoginButtonProps {\n onSuccess?: () => void;\n onError?: (error: Error) => void;\n className?: string;\n variant?: 'default' | 'outline';\n size?: 'sm' | 'md' | 'lg';\n disabled?: boolean;\n /**\n * Hide the button if no Solana wallets are detected.\n * When true (default), button renders nothing if no wallets are installed.\n * When false, button always renders (useful for showing \"install wallet\" prompts).\n * @default true\n */\n hideIfNoWallet?: boolean;\n /** Called when the button's loading state changes (connecting, signing, etc.). */\n onLoadingChange?: (loading: boolean) => void;\n /**\n * Solana wallet adapter context. Pass this from @solana/wallet-adapter-react's useWallet().\n * When provided, the component assumes a WalletProvider exists in the React tree and\n * uses the consumer's wallet context for wallet discovery and connection.\n * When omitted, the component provides its own WalletProvider with wallet-standard discovery.\n */\n walletContext?: {\n publicKey: { toBase58: () => string } | null;\n signMessage: ((message: Uint8Array) => Promise<Uint8Array>) | null;\n connected: boolean;\n connecting: boolean;\n connect: () => Promise<void>;\n wallet: { adapter: { name: string } } | null;\n select: (walletName: string) => void;\n wallets: Array<{\n adapter: {\n name: string;\n icon: string;\n readyState: string;\n };\n }>;\n };\n}\n\n/** Stable empty array to avoid re-renders in self-contained WalletProvider. */\nconst EMPTY_ADAPTERS: [] = [];\n\n/**\n * Solana wallet login button with one-click authentication.\n *\n * Uses the standard wallet adapter modal for wallet selection, which provides\n * real brand icons and discovers all wallet-standard-compliant wallets.\n *\n * When `walletContext` is provided, assumes a WalletProvider exists in the tree.\n * Otherwise, wraps itself with WalletProvider for self-contained operation.\n */\nexport function SolanaLoginButton(props: SolanaLoginButtonProps) {\n if (props.walletContext) {\n // Consumer has their own WalletProvider; just add modal capability\n return (\n <WalletModalProvider>\n <SolanaLoginInner {...props} />\n </WalletModalProvider>\n );\n }\n\n // Self-contained: provide wallet-standard discovery + modal\n return (\n <WalletProvider wallets={EMPTY_ADAPTERS} localStorageKey=\"cedros-walletName\">\n <WalletModalProvider>\n <SolanaLoginInner {...props} />\n </WalletModalProvider>\n </WalletProvider>\n );\n}\n\nfunction SolanaLoginInner({\n onSuccess,\n onError,\n className = '',\n variant = 'default',\n size = 'md',\n disabled = false,\n hideIfNoWallet = true,\n onLoadingChange,\n walletContext,\n}: SolanaLoginButtonProps) {\n const { requestChallenge, signIn, isLoading: isAuthLoading } = useSolanaAuth();\n const adapterWallet = useWallet();\n const { visible: modalVisible, setVisible: setModalVisible } = useWalletModal();\n const [pendingLogin, setPendingLogin] = useState(false);\n const [triggerConnect, setTriggerConnect] = useState(false);\n const isProcessingRef = useRef(false);\n const modalWasOpen = useRef(false);\n const signRejectedRef = useRef(false);\n\n // Use walletContext if provided, otherwise use adapter's useWallet()\n const connected = walletContext?.connected ?? adapterWallet.connected;\n const connecting = walletContext?.connecting ?? adapterWallet.connecting;\n const publicKey = walletContext?.publicKey ?? adapterWallet.publicKey;\n const signMessage = walletContext?.signMessage ?? adapterWallet.signMessage;\n const wallet = walletContext?.wallet ?? adapterWallet.wallet;\n const wallets = walletContext?.wallets ?? adapterWallet.wallets;\n const select = walletContext\n ? walletContext.select\n : (name: string) => adapterWallet.select(name as WalletName);\n const connect = walletContext?.connect ?? adapterWallet.connect;\n\n // Get installed/ready wallets\n const installedWallets = wallets.filter(\n (w) => w.adapter.readyState === 'Installed' || w.adapter.readyState === 'Loadable'\n );\n\n // Execute the sign-in flow (challenge → sign → verify)\n const executeSignIn = useCallback(async () => {\n if (isProcessingRef.current) return;\n if (!publicKey || !signMessage) {\n onError?.(new Error('Wallet not ready'));\n return;\n }\n\n isProcessingRef.current = true;\n try {\n const pubKeyString = publicKey.toBase58();\n\n const challenge = await requestChallenge(pubKeyString);\n\n const messageBytes = new TextEncoder().encode(challenge.message);\n const signatureBytes = await signMessage(messageBytes);\n\n if (!(signatureBytes instanceof Uint8Array) || signatureBytes.length === 0) {\n throw new Error('Wallet returned invalid signature');\n }\n\n let signature: string;\n try {\n signature = btoa(String.fromCharCode(...signatureBytes));\n } catch {\n throw new Error('Failed to encode signature');\n }\n\n await signIn(pubKeyString, signature, challenge.message);\n signRejectedRef.current = false;\n onSuccess?.();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n signRejectedRef.current = true;\n onError?.(error);\n } finally {\n isProcessingRef.current = false;\n setPendingLogin(false);\n }\n }, [publicKey, signMessage, requestChallenge, signIn, onSuccess, onError]);\n\n // Auto-connect when wallet is selected and triggerConnect is set\n useEffect(() => {\n if (triggerConnect && wallet && !connected && !connecting) {\n setTriggerConnect(false);\n connect().catch((err) => {\n onError?.(err instanceof Error ? err : new Error(String(err)));\n setPendingLogin(false);\n });\n }\n }, [triggerConnect, wallet, connected, connecting, connect, onError]);\n\n // Auto-execute sign-in when connected with pending login\n useEffect(() => {\n if (pendingLogin && connected && publicKey && signMessage && !isProcessingRef.current) {\n executeSignIn().catch(() => {\n /* Errors already passed to onError callback inside executeSignIn */\n });\n }\n }, [pendingLogin, connected, publicKey, signMessage, executeSignIn]);\n\n // When modal closes: connect if a wallet was selected, else reset.\n // Wallet is always deselected before opening, so any non-null wallet = user chose one.\n useEffect(() => {\n if (modalVisible) {\n modalWasOpen.current = true;\n } else if (modalWasOpen.current) {\n modalWasOpen.current = false;\n if (pendingLogin && !connected && wallet && !connecting) {\n setTriggerConnect(true);\n } else if (pendingLogin && !connected) {\n setPendingLogin(false);\n }\n }\n }, [modalVisible, pendingLogin, connected, wallet, connecting]);\n\n // Hide button if no wallets detected\n if (hideIfNoWallet && installedWallets.length === 0) {\n return null;\n }\n\n const handleClick = async () => {\n if (disabled || isAuthLoading || connecting) return;\n\n if (connected && publicKey && signMessage && !signRejectedRef.current) {\n // Already connected, previous attempt wasn't rejected — sign immediately\n setPendingLogin(true);\n await executeSignIn();\n } else if (installedWallets.length === 1 && !wallet) {\n // Single installed wallet, nothing remembered — auto-select + connect\n select(installedWallets[0].adapter.name);\n setPendingLogin(true);\n setTriggerConnect(true);\n } else {\n // Show wallet picker — deselect any remembered wallet first so that\n // every selection in the modal registers as \"new\" and dismissing\n // leaves wallet as null (no accidental auto-connect).\n signRejectedRef.current = false;\n if (wallet) {\n adapterWallet.select(null as unknown as WalletName);\n }\n setModalVisible(true);\n setPendingLogin(true);\n }\n };\n\n const sizeClasses = {\n sm: 'cedros-button-sm',\n md: 'cedros-button-md',\n lg: 'cedros-button-lg',\n };\n\n const variantClasses = {\n default: 'cedros-button-social',\n outline: 'cedros-button-social-outline',\n };\n\n const isLoading = isAuthLoading || connecting || (pendingLogin && !connected);\n\n // Notify parent of loading state changes\n useEffect(() => {\n onLoadingChange?.(isLoading);\n }, [isLoading, onLoadingChange]);\n\n return (\n <button\n type=\"button\"\n className={`cedros-button ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}\n onClick={handleClick}\n disabled={disabled || isLoading}\n aria-label=\"Continue with Solana\"\n >\n {isLoading ? (\n <LoadingSpinner size=\"sm\" />\n ) : (\n <svg\n className=\"cedros-button-icon\"\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 128 128\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path d=\"M25.38 96.04a4.35 4.35 0 0 1 3.07-1.27h91.68c1.93 0 2.9 2.34 1.54 3.7l-17.71 17.72a4.35 4.35 0 0 1-3.07 1.27H9.21c-1.93 0-2.9-2.34-1.54-3.7l17.71-17.72z\" />\n <path d=\"M25.38 11.81a4.47 4.47 0 0 1 3.07-1.27h91.68c1.93 0 2.9 2.34 1.54 3.7L103.96 31.96a4.35 4.35 0 0 1-3.07 1.27H9.21c-1.93 0-2.9-2.34-1.54-3.7L25.38 11.81z\" />\n <path d=\"M102.62 53.76a4.35 4.35 0 0 0-3.07-1.27H7.87c-1.93 0-2.9 2.34-1.54 3.7l17.71 17.72a4.35 4.35 0 0 0 3.07 1.27h91.68c1.93 0 2.9-2.34 1.54-3.7L102.62 53.76z\" />\n </svg>\n )}\n <span>Continue with Solana</span>\n </button>\n );\n}\n"],"names":["useSolanaAuth","config","_internal","useCedrosLogin","isLoading","setIsLoading","useState","error","setError","apiClient","useMemo","ApiClient","requestChallenge","useCallback","publicKey","validateSolanaPublicKey","authError","err","handleApiError","signIn","signature","message","data","clearError","registerMobileWallet","mwa","chains","registrationConfig","EMPTY_ADAPTERS","SolanaLoginButton","props","WalletModalProvider","jsx","SolanaLoginInner","WalletProvider","onSuccess","onError","className","variant","size","disabled","hideIfNoWallet","onLoadingChange","walletContext","isAuthLoading","adapterWallet","useWallet","modalVisible","setModalVisible","useWalletModal","pendingLogin","setPendingLogin","triggerConnect","setTriggerConnect","isProcessingRef","useRef","modalWasOpen","signRejectedRef","connected","connecting","signMessage","wallet","wallets","select","name","connect","installedWallets","w","executeSignIn","pubKeyString","challenge","messageBytes","signatureBytes","useEffect","handleClick","sizeClasses","variantClasses","jsxs","LoadingSpinner"],"mappings":"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"}
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import { jsx as c, jsxs as H } from "react/jsx-runtime";
|
|
2
|
-
import { useState as P, useMemo as X, useCallback as W, useRef as K, useEffect as M } from "react";
|
|
3
|
-
import { WalletProvider as Z, useWallet as x } from "@solana/wallet-adapter-react";
|
|
4
|
-
import { WalletModalProvider as R, useWalletModal as ee } from "@solana/wallet-adapter-react-ui";
|
|
5
|
-
import { u as te, A as ne, h as V } from "./useCedrosLogin-CFfID-0i.js";
|
|
6
|
-
import { a as F } from "./validation-B8kMV3BL.js";
|
|
7
|
-
import { L as ae } from "./LoadingSpinner-6vml-zwr.js";
|
|
8
|
-
function re() {
|
|
9
|
-
const { config: e, _internal: t } = te(), [b, s] = P(!1), [D, i] = P(null), f = X(
|
|
10
|
-
() => new ne({
|
|
11
|
-
baseUrl: e.serverUrl,
|
|
12
|
-
timeoutMs: e.requestTimeout,
|
|
13
|
-
retryAttempts: e.retryAttempts
|
|
14
|
-
}),
|
|
15
|
-
[e.serverUrl, e.requestTimeout, e.retryAttempts]
|
|
16
|
-
), S = W(
|
|
17
|
-
async (d) => {
|
|
18
|
-
if (!F(d)) {
|
|
19
|
-
const l = {
|
|
20
|
-
code: "INVALID_PUBLIC_KEY",
|
|
21
|
-
message: "Invalid Solana public key format"
|
|
22
|
-
};
|
|
23
|
-
throw i(l), l;
|
|
24
|
-
}
|
|
25
|
-
s(!0), i(null);
|
|
26
|
-
try {
|
|
27
|
-
return await f.post(
|
|
28
|
-
"/solana/challenge",
|
|
29
|
-
{ publicKey: d },
|
|
30
|
-
{ credentials: "omit" }
|
|
31
|
-
);
|
|
32
|
-
} catch (l) {
|
|
33
|
-
const a = V(l, "Unable to start wallet verification. Please try again.");
|
|
34
|
-
throw i(a), a;
|
|
35
|
-
} finally {
|
|
36
|
-
s(!1);
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
[f]
|
|
40
|
-
), o = W(
|
|
41
|
-
async (d, l, a) => {
|
|
42
|
-
if (!F(d)) {
|
|
43
|
-
const r = {
|
|
44
|
-
code: "INVALID_PUBLIC_KEY",
|
|
45
|
-
message: "Invalid Solana public key format"
|
|
46
|
-
};
|
|
47
|
-
throw i(r), r;
|
|
48
|
-
}
|
|
49
|
-
s(!0), i(null);
|
|
50
|
-
try {
|
|
51
|
-
const r = await f.post("/solana", {
|
|
52
|
-
publicKey: d,
|
|
53
|
-
signature: l,
|
|
54
|
-
message: a
|
|
55
|
-
});
|
|
56
|
-
return e.callbacks?.onLoginSuccess?.(r.user, "solana"), t?.handleLoginSuccess(r.user, r.tokens), r;
|
|
57
|
-
} catch (r) {
|
|
58
|
-
const A = V(r, "Unable to sign in with your wallet. Please try again.");
|
|
59
|
-
throw i(A), A;
|
|
60
|
-
} finally {
|
|
61
|
-
s(!1);
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
[f, e.callbacks, t]
|
|
65
|
-
), L = W(() => i(null), []);
|
|
66
|
-
return {
|
|
67
|
-
requestChallenge: S,
|
|
68
|
-
signIn: o,
|
|
69
|
-
isLoading: b,
|
|
70
|
-
error: D,
|
|
71
|
-
clearError: L
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
function se(e) {
|
|
75
|
-
if (typeof window > "u")
|
|
76
|
-
return !1;
|
|
77
|
-
try {
|
|
78
|
-
const t = require("@solana-mobile/wallet-standard-mobile"), b = e?.chains ?? ["solana:mainnet"], s = {
|
|
79
|
-
appIdentity: {
|
|
80
|
-
name: e?.name,
|
|
81
|
-
uri: e?.uri,
|
|
82
|
-
icon: e?.icon
|
|
83
|
-
},
|
|
84
|
-
chains: b
|
|
85
|
-
};
|
|
86
|
-
return typeof t.createDefaultAuthorizationCache == "function" && (s.authorizationCache = t.createDefaultAuthorizationCache()), typeof t.createDefaultChainSelector == "function" && (s.chainSelector = t.createDefaultChainSelector()), typeof t.createDefaultWalletNotFoundHandler == "function" && (s.onWalletNotFound = t.createDefaultWalletNotFoundHandler()), t.registerMwa(s), !0;
|
|
87
|
-
} catch {
|
|
88
|
-
return !1;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
typeof navigator < "u" && /Android/i.test(navigator.userAgent) && se();
|
|
92
|
-
const oe = [];
|
|
93
|
-
function he(e) {
|
|
94
|
-
return e.walletContext ? /* @__PURE__ */ c(R, { children: /* @__PURE__ */ c(j, { ...e }) }) : /* @__PURE__ */ c(Z, { wallets: oe, localStorageKey: "cedros-walletName", children: /* @__PURE__ */ c(R, { children: /* @__PURE__ */ c(j, { ...e }) }) });
|
|
95
|
-
}
|
|
96
|
-
function j({
|
|
97
|
-
onSuccess: e,
|
|
98
|
-
onError: t,
|
|
99
|
-
className: b = "",
|
|
100
|
-
variant: s = "default",
|
|
101
|
-
size: D = "md",
|
|
102
|
-
disabled: i = !1,
|
|
103
|
-
hideIfNoWallet: f = !0,
|
|
104
|
-
onLoadingChange: S,
|
|
105
|
-
walletContext: o
|
|
106
|
-
}) {
|
|
107
|
-
const { requestChallenge: L, signIn: d, isLoading: l } = re(), a = x(), { visible: r, setVisible: A } = ee(), [g, h] = P(!1), [T, k] = P(!1), C = K(!1), z = K(!1), E = K(!1), u = o?.connected ?? a.connected, m = o?.connecting ?? a.connecting, y = o?.publicKey ?? a.publicKey, p = o?.signMessage ?? a.signMessage, w = o?.wallet ?? a.wallet, Y = o?.wallets ?? a.wallets, $ = o ? o.select : (n) => a.select(n), _ = o?.connect ?? a.connect, N = Y.filter(
|
|
108
|
-
(n) => n.adapter.readyState === "Installed" || n.adapter.readyState === "Loadable"
|
|
109
|
-
), U = W(async () => {
|
|
110
|
-
if (!C.current) {
|
|
111
|
-
if (!y || !p) {
|
|
112
|
-
t?.(new Error("Wallet not ready"));
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
C.current = !0;
|
|
116
|
-
try {
|
|
117
|
-
const n = y.toBase58(), I = await L(n), Q = new TextEncoder().encode(I.message), B = await p(Q);
|
|
118
|
-
if (!(B instanceof Uint8Array) || B.length === 0)
|
|
119
|
-
throw new Error("Wallet returned invalid signature");
|
|
120
|
-
let q;
|
|
121
|
-
try {
|
|
122
|
-
q = btoa(String.fromCharCode(...B));
|
|
123
|
-
} catch {
|
|
124
|
-
throw new Error("Failed to encode signature");
|
|
125
|
-
}
|
|
126
|
-
await d(n, q, I.message), E.current = !1, e?.();
|
|
127
|
-
} catch (n) {
|
|
128
|
-
const I = n instanceof Error ? n : new Error(String(n));
|
|
129
|
-
E.current = !0, t?.(I);
|
|
130
|
-
} finally {
|
|
131
|
-
C.current = !1, h(!1);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}, [y, p, L, d, e, t]);
|
|
135
|
-
if (M(() => {
|
|
136
|
-
T && w && !u && !m && (k(!1), _().catch((n) => {
|
|
137
|
-
t?.(n instanceof Error ? n : new Error(String(n))), h(!1);
|
|
138
|
-
}));
|
|
139
|
-
}, [T, w, u, m, _, t]), M(() => {
|
|
140
|
-
g && u && y && p && !C.current && U().catch(() => {
|
|
141
|
-
});
|
|
142
|
-
}, [g, u, y, p, U]), M(() => {
|
|
143
|
-
r ? z.current = !0 : z.current && (z.current = !1, g && !u && w && !m ? k(!0) : g && !u && h(!1));
|
|
144
|
-
}, [r, g, u, w, m]), f && N.length === 0)
|
|
145
|
-
return null;
|
|
146
|
-
const O = async () => {
|
|
147
|
-
i || l || m || (u && y && p && !E.current ? (h(!0), await U()) : N.length === 1 && !w ? ($(N[0].adapter.name), h(!0), k(!0)) : (E.current = !1, w && a.select(null), A(!0), h(!0)));
|
|
148
|
-
}, G = {
|
|
149
|
-
sm: "cedros-button-sm",
|
|
150
|
-
md: "cedros-button-md",
|
|
151
|
-
lg: "cedros-button-lg"
|
|
152
|
-
}, J = {
|
|
153
|
-
default: "cedros-button-social",
|
|
154
|
-
outline: "cedros-button-social-outline"
|
|
155
|
-
}, v = l || m || g && !u;
|
|
156
|
-
return M(() => {
|
|
157
|
-
S?.(v);
|
|
158
|
-
}, [v, S]), /* @__PURE__ */ H(
|
|
159
|
-
"button",
|
|
160
|
-
{
|
|
161
|
-
type: "button",
|
|
162
|
-
className: `cedros-button ${J[s]} ${G[D]} ${b}`,
|
|
163
|
-
onClick: O,
|
|
164
|
-
disabled: i || v,
|
|
165
|
-
"aria-label": "Continue with Solana",
|
|
166
|
-
children: [
|
|
167
|
-
v ? /* @__PURE__ */ c(ae, { size: "sm" }) : /* @__PURE__ */ H(
|
|
168
|
-
"svg",
|
|
169
|
-
{
|
|
170
|
-
className: "cedros-button-icon",
|
|
171
|
-
width: "18",
|
|
172
|
-
height: "18",
|
|
173
|
-
viewBox: "0 0 128 128",
|
|
174
|
-
fill: "currentColor",
|
|
175
|
-
"aria-hidden": "true",
|
|
176
|
-
children: [
|
|
177
|
-
/* @__PURE__ */ c("path", { d: "M25.38 96.04a4.35 4.35 0 0 1 3.07-1.27h91.68c1.93 0 2.9 2.34 1.54 3.7l-17.71 17.72a4.35 4.35 0 0 1-3.07 1.27H9.21c-1.93 0-2.9-2.34-1.54-3.7l17.71-17.72z" }),
|
|
178
|
-
/* @__PURE__ */ c("path", { d: "M25.38 11.81a4.47 4.47 0 0 1 3.07-1.27h91.68c1.93 0 2.9 2.34 1.54 3.7L103.96 31.96a4.35 4.35 0 0 1-3.07 1.27H9.21c-1.93 0-2.9-2.34-1.54-3.7L25.38 11.81z" }),
|
|
179
|
-
/* @__PURE__ */ c("path", { d: "M102.62 53.76a4.35 4.35 0 0 0-3.07-1.27H7.87c-1.93 0-2.9 2.34-1.54 3.7l17.71 17.72a4.35 4.35 0 0 0 3.07 1.27h91.68c1.93 0 2.9-2.34 1.54-3.7L102.62 53.76z" })
|
|
180
|
-
]
|
|
181
|
-
}
|
|
182
|
-
),
|
|
183
|
-
/* @__PURE__ */ c("span", { children: "Continue with Solana" })
|
|
184
|
-
]
|
|
185
|
-
}
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
export {
|
|
189
|
-
he as S,
|
|
190
|
-
se as r,
|
|
191
|
-
re as u
|
|
192
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SolanaLoginButton-DFOoLqoj.js","sources":["../src/hooks/useSolanaAuth.ts","../src/utils/mobileWalletAdapter.ts","../src/components/solana/SolanaLoginButton.tsx"],"sourcesContent":["import { useState, useCallback, useMemo } from 'react';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { ApiClient, handleApiError } from '../utils/apiClient';\nimport { validateSolanaPublicKey } from '../utils/validation';\nimport type { AuthResponse, AuthError, ChallengeResponse } from '../types';\n\nexport interface UseSolanaAuthReturn {\n requestChallenge: (publicKey: string) => Promise<ChallengeResponse>;\n signIn: (publicKey: string, signature: string, message: string) => Promise<AuthResponse>;\n isLoading: boolean;\n error: AuthError | null;\n clearError: () => void;\n}\n\n/**\n * Hook for Solana wallet authentication.\n *\n * @example\n * ```tsx\n * function SolanaLogin() {\n * const { requestChallenge, signIn, isLoading } = useSolanaAuth();\n * const { publicKey, signMessage } = useWallet();\n *\n * const handleLogin = async () => {\n * const challenge = await requestChallenge(publicKey.toBase58());\n * const signature = await signMessage(new TextEncoder().encode(challenge.message));\n * const result = await signIn(\n * publicKey.toBase58(),\n * Buffer.from(signature).toString('base64'),\n * challenge.message\n * );\n * };\n * }\n * ```\n */\nexport function useSolanaAuth(): UseSolanaAuthReturn {\n const { config, _internal } = useCedrosLogin();\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<AuthError | null>(null);\n\n const apiClient = useMemo(\n () =>\n new ApiClient({\n baseUrl: config.serverUrl,\n timeoutMs: config.requestTimeout,\n retryAttempts: config.retryAttempts,\n }),\n [config.serverUrl, config.requestTimeout, config.retryAttempts]\n );\n\n const requestChallenge = useCallback(\n async (publicKey: string): Promise<ChallengeResponse> => {\n // Validate public key format before making API call\n if (!validateSolanaPublicKey(publicKey)) {\n const authError: AuthError = {\n code: 'INVALID_PUBLIC_KEY',\n message: 'Invalid Solana public key format',\n };\n setError(authError);\n throw authError;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const data = await apiClient.post<ChallengeResponse>(\n '/solana/challenge',\n { publicKey },\n { credentials: 'omit' }\n );\n return data;\n } catch (err) {\n const authError = handleApiError(err, 'Unable to start wallet verification. Please try again.');\n setError(authError);\n throw authError;\n } finally {\n setIsLoading(false);\n }\n },\n [apiClient]\n );\n\n const signIn = useCallback(\n async (publicKey: string, signature: string, message: string): Promise<AuthResponse> => {\n // Validate public key format before making API call\n if (!validateSolanaPublicKey(publicKey)) {\n const authError: AuthError = {\n code: 'INVALID_PUBLIC_KEY',\n message: 'Invalid Solana public key format',\n };\n setError(authError);\n throw authError;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const data = await apiClient.post<AuthResponse>('/solana', {\n publicKey,\n signature,\n message,\n });\n config.callbacks?.onLoginSuccess?.(data.user, 'solana');\n _internal?.handleLoginSuccess(data.user, data.tokens);\n return data;\n } catch (err) {\n const authError = handleApiError(err, 'Unable to sign in with your wallet. Please try again.');\n setError(authError);\n throw authError;\n } finally {\n setIsLoading(false);\n }\n },\n [apiClient, config.callbacks, _internal]\n );\n\n const clearError = useCallback(() => setError(null), []);\n\n return {\n requestChallenge,\n signIn,\n isLoading,\n error,\n clearError,\n };\n}\n","/**\n * Mobile Wallet Adapter (MWA) registration for web.\n *\n * On Android Chrome, MWA lets users authenticate with their installed Solana\n * wallet app (e.g., Phantom, Solflare) via Android Intents — no browser\n * extension needed.\n *\n * Once registered, MWA appears as a wallet option in the wallet adapter's\n * wallet list (alongside browser extension wallets). Users see it as\n * \"Use Installed Wallet\" in the wallet selector.\n *\n * Requires the optional peer dependency: @solana-mobile/wallet-standard-mobile\n *\n * @see https://docs.solanamobile.com/get-started/web/installation\n */\n\nexport interface MobileWalletConfig {\n /** App name shown in the wallet's authorization dialog */\n name?: string;\n /** App URI for identity verification */\n uri?: string;\n /** App icon path/URL shown in the wallet dialog */\n icon?: string;\n /** Solana cluster(s) to support. Default: ['solana:mainnet'] */\n chains?: string[];\n}\n\n/**\n * Register Mobile Wallet Adapter as a wallet-standard wallet.\n *\n * Call this once at your application root (before rendering). After registration,\n * MWA automatically appears as \"Use Installed Wallet\" for users browsing on\n * Android Chrome with a Solana wallet app installed.\n *\n * Must be called in a non-SSR context (browser only). For Next.js, call in a\n * Client Component with `'use client'`.\n *\n * @example\n * ```tsx\n * import { registerMobileWallet, CedrosLoginProvider } from '@cedros/login-react';\n *\n * // Register before provider mounts\n * registerMobileWallet({ name: 'My App', uri: 'https://myapp.com' });\n *\n * function App() {\n * return (\n * <CedrosLoginProvider config={{ serverUrl: '...' }}>\n * <LoginForm />\n * </CedrosLoginProvider>\n * );\n * }\n * ```\n *\n * @returns true if registration succeeded, false if package not installed or SSR\n */\nexport function registerMobileWallet(config?: MobileWalletConfig): boolean {\n if (typeof window === 'undefined') {\n return false;\n }\n\n try {\n // Dynamic import to avoid bundling the optional peer dep\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const mwa = require('@solana-mobile/wallet-standard-mobile');\n\n const chains = config?.chains ?? ['solana:mainnet'];\n\n const registrationConfig: Record<string, unknown> = {\n appIdentity: {\n name: config?.name,\n uri: config?.uri,\n icon: config?.icon,\n },\n chains,\n };\n\n // Use built-in defaults for optional config if available\n if (typeof mwa.createDefaultAuthorizationCache === 'function') {\n registrationConfig.authorizationCache = mwa.createDefaultAuthorizationCache();\n }\n if (typeof mwa.createDefaultChainSelector === 'function') {\n registrationConfig.chainSelector = mwa.createDefaultChainSelector();\n }\n if (typeof mwa.createDefaultWalletNotFoundHandler === 'function') {\n registrationConfig.onWalletNotFound = mwa.createDefaultWalletNotFoundHandler();\n }\n\n mwa.registerMwa(registrationConfig);\n return true;\n } catch {\n // @solana-mobile/wallet-standard-mobile not installed\n return false;\n }\n}\n","import { useState, useEffect, useCallback, useRef } from 'react';\nimport { WalletProvider, useWallet } from '@solana/wallet-adapter-react';\nimport { WalletModalProvider, useWalletModal } from '@solana/wallet-adapter-react-ui';\nimport type { WalletName } from '@solana/wallet-adapter-base';\nimport { useSolanaAuth } from '../../hooks/useSolanaAuth';\nimport { registerMobileWallet } from '../../utils/mobileWalletAdapter';\nimport { LoadingSpinner } from '../shared/LoadingSpinner';\n\n// Auto-register Mobile Wallet Adapter on Android for Solana phone (Seeker/Seed Vault) support.\n// Runs at import time so detectSolanaWallets() in LoginForm picks up MWA before first render.\nif (typeof navigator !== 'undefined' && /Android/i.test(navigator.userAgent)) {\n registerMobileWallet();\n}\n\nexport interface SolanaLoginButtonProps {\n onSuccess?: () => void;\n onError?: (error: Error) => void;\n className?: string;\n variant?: 'default' | 'outline';\n size?: 'sm' | 'md' | 'lg';\n disabled?: boolean;\n /**\n * Hide the button if no Solana wallets are detected.\n * When true (default), button renders nothing if no wallets are installed.\n * When false, button always renders (useful for showing \"install wallet\" prompts).\n * @default true\n */\n hideIfNoWallet?: boolean;\n /** Called when the button's loading state changes (connecting, signing, etc.). */\n onLoadingChange?: (loading: boolean) => void;\n /**\n * Solana wallet adapter context. Pass this from @solana/wallet-adapter-react's useWallet().\n * When provided, the component assumes a WalletProvider exists in the React tree and\n * uses the consumer's wallet context for wallet discovery and connection.\n * When omitted, the component provides its own WalletProvider with wallet-standard discovery.\n */\n walletContext?: {\n publicKey: { toBase58: () => string } | null;\n signMessage: ((message: Uint8Array) => Promise<Uint8Array>) | null;\n connected: boolean;\n connecting: boolean;\n connect: () => Promise<void>;\n wallet: { adapter: { name: string } } | null;\n select: (walletName: string) => void;\n wallets: Array<{\n adapter: {\n name: string;\n icon: string;\n readyState: string;\n };\n }>;\n };\n}\n\n/** Stable empty array to avoid re-renders in self-contained WalletProvider. */\nconst EMPTY_ADAPTERS: [] = [];\n\n/**\n * Solana wallet login button with one-click authentication.\n *\n * Uses the standard wallet adapter modal for wallet selection, which provides\n * real brand icons and discovers all wallet-standard-compliant wallets.\n *\n * When `walletContext` is provided, assumes a WalletProvider exists in the tree.\n * Otherwise, wraps itself with WalletProvider for self-contained operation.\n */\nexport function SolanaLoginButton(props: SolanaLoginButtonProps) {\n if (props.walletContext) {\n // Consumer has their own WalletProvider; just add modal capability\n return (\n <WalletModalProvider>\n <SolanaLoginInner {...props} />\n </WalletModalProvider>\n );\n }\n\n // Self-contained: provide wallet-standard discovery + modal\n return (\n <WalletProvider wallets={EMPTY_ADAPTERS} localStorageKey=\"cedros-walletName\">\n <WalletModalProvider>\n <SolanaLoginInner {...props} />\n </WalletModalProvider>\n </WalletProvider>\n );\n}\n\nfunction SolanaLoginInner({\n onSuccess,\n onError,\n className = '',\n variant = 'default',\n size = 'md',\n disabled = false,\n hideIfNoWallet = true,\n onLoadingChange,\n walletContext,\n}: SolanaLoginButtonProps) {\n const { requestChallenge, signIn, isLoading: isAuthLoading } = useSolanaAuth();\n const adapterWallet = useWallet();\n const { visible: modalVisible, setVisible: setModalVisible } = useWalletModal();\n const [pendingLogin, setPendingLogin] = useState(false);\n const [triggerConnect, setTriggerConnect] = useState(false);\n const isProcessingRef = useRef(false);\n const modalWasOpen = useRef(false);\n const signRejectedRef = useRef(false);\n\n // Use walletContext if provided, otherwise use adapter's useWallet()\n const connected = walletContext?.connected ?? adapterWallet.connected;\n const connecting = walletContext?.connecting ?? adapterWallet.connecting;\n const publicKey = walletContext?.publicKey ?? adapterWallet.publicKey;\n const signMessage = walletContext?.signMessage ?? adapterWallet.signMessage;\n const wallet = walletContext?.wallet ?? adapterWallet.wallet;\n const wallets = walletContext?.wallets ?? adapterWallet.wallets;\n const select = walletContext\n ? walletContext.select\n : (name: string) => adapterWallet.select(name as WalletName);\n const connect = walletContext?.connect ?? adapterWallet.connect;\n\n // Get installed/ready wallets\n const installedWallets = wallets.filter(\n (w) => w.adapter.readyState === 'Installed' || w.adapter.readyState === 'Loadable'\n );\n\n // Execute the sign-in flow (challenge → sign → verify)\n const executeSignIn = useCallback(async () => {\n if (isProcessingRef.current) return;\n if (!publicKey || !signMessage) {\n onError?.(new Error('Wallet not ready'));\n return;\n }\n\n isProcessingRef.current = true;\n try {\n const pubKeyString = publicKey.toBase58();\n\n const challenge = await requestChallenge(pubKeyString);\n\n const messageBytes = new TextEncoder().encode(challenge.message);\n const signatureBytes = await signMessage(messageBytes);\n\n if (!(signatureBytes instanceof Uint8Array) || signatureBytes.length === 0) {\n throw new Error('Wallet returned invalid signature');\n }\n\n let signature: string;\n try {\n signature = btoa(String.fromCharCode(...signatureBytes));\n } catch {\n throw new Error('Failed to encode signature');\n }\n\n await signIn(pubKeyString, signature, challenge.message);\n signRejectedRef.current = false;\n onSuccess?.();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n signRejectedRef.current = true;\n onError?.(error);\n } finally {\n isProcessingRef.current = false;\n setPendingLogin(false);\n }\n }, [publicKey, signMessage, requestChallenge, signIn, onSuccess, onError]);\n\n // Auto-connect when wallet is selected and triggerConnect is set\n useEffect(() => {\n if (triggerConnect && wallet && !connected && !connecting) {\n setTriggerConnect(false);\n connect().catch((err) => {\n onError?.(err instanceof Error ? err : new Error(String(err)));\n setPendingLogin(false);\n });\n }\n }, [triggerConnect, wallet, connected, connecting, connect, onError]);\n\n // Auto-execute sign-in when connected with pending login\n useEffect(() => {\n if (pendingLogin && connected && publicKey && signMessage && !isProcessingRef.current) {\n executeSignIn().catch(() => {\n /* Errors already passed to onError callback inside executeSignIn */\n });\n }\n }, [pendingLogin, connected, publicKey, signMessage, executeSignIn]);\n\n // When modal closes: connect if a wallet was selected, else reset.\n // Wallet is always deselected before opening, so any non-null wallet = user chose one.\n useEffect(() => {\n if (modalVisible) {\n modalWasOpen.current = true;\n } else if (modalWasOpen.current) {\n modalWasOpen.current = false;\n if (pendingLogin && !connected && wallet && !connecting) {\n setTriggerConnect(true);\n } else if (pendingLogin && !connected) {\n setPendingLogin(false);\n }\n }\n }, [modalVisible, pendingLogin, connected, wallet, connecting]);\n\n // Hide button if no wallets detected\n if (hideIfNoWallet && installedWallets.length === 0) {\n return null;\n }\n\n const handleClick = async () => {\n if (disabled || isAuthLoading || connecting) return;\n\n if (connected && publicKey && signMessage && !signRejectedRef.current) {\n // Already connected, previous attempt wasn't rejected — sign immediately\n setPendingLogin(true);\n await executeSignIn();\n } else if (installedWallets.length === 1 && !wallet) {\n // Single installed wallet, nothing remembered — auto-select + connect\n select(installedWallets[0].adapter.name);\n setPendingLogin(true);\n setTriggerConnect(true);\n } else {\n // Show wallet picker — deselect any remembered wallet first so that\n // every selection in the modal registers as \"new\" and dismissing\n // leaves wallet as null (no accidental auto-connect).\n signRejectedRef.current = false;\n if (wallet) {\n adapterWallet.select(null as unknown as WalletName);\n }\n setModalVisible(true);\n setPendingLogin(true);\n }\n };\n\n const sizeClasses = {\n sm: 'cedros-button-sm',\n md: 'cedros-button-md',\n lg: 'cedros-button-lg',\n };\n\n const variantClasses = {\n default: 'cedros-button-social',\n outline: 'cedros-button-social-outline',\n };\n\n const isLoading = isAuthLoading || connecting || (pendingLogin && !connected);\n\n // Notify parent of loading state changes\n useEffect(() => {\n onLoadingChange?.(isLoading);\n }, [isLoading, onLoadingChange]);\n\n return (\n <button\n type=\"button\"\n className={`cedros-button ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}\n onClick={handleClick}\n disabled={disabled || isLoading}\n aria-label=\"Continue with Solana\"\n >\n {isLoading ? (\n <LoadingSpinner size=\"sm\" />\n ) : (\n <svg\n className=\"cedros-button-icon\"\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 128 128\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path d=\"M25.38 96.04a4.35 4.35 0 0 1 3.07-1.27h91.68c1.93 0 2.9 2.34 1.54 3.7l-17.71 17.72a4.35 4.35 0 0 1-3.07 1.27H9.21c-1.93 0-2.9-2.34-1.54-3.7l17.71-17.72z\" />\n <path d=\"M25.38 11.81a4.47 4.47 0 0 1 3.07-1.27h91.68c1.93 0 2.9 2.34 1.54 3.7L103.96 31.96a4.35 4.35 0 0 1-3.07 1.27H9.21c-1.93 0-2.9-2.34-1.54-3.7L25.38 11.81z\" />\n <path d=\"M102.62 53.76a4.35 4.35 0 0 0-3.07-1.27H7.87c-1.93 0-2.9 2.34-1.54 3.7l17.71 17.72a4.35 4.35 0 0 0 3.07 1.27h91.68c1.93 0 2.9-2.34 1.54-3.7L102.62 53.76z\" />\n </svg>\n )}\n <span>Continue with Solana</span>\n </button>\n );\n}\n"],"names":["useSolanaAuth","config","_internal","useCedrosLogin","isLoading","setIsLoading","useState","error","setError","apiClient","useMemo","ApiClient","requestChallenge","useCallback","publicKey","validateSolanaPublicKey","authError","err","handleApiError","signIn","signature","message","data","clearError","registerMobileWallet","mwa","chains","registrationConfig","EMPTY_ADAPTERS","SolanaLoginButton","props","WalletModalProvider","jsx","SolanaLoginInner","WalletProvider","onSuccess","onError","className","variant","size","disabled","hideIfNoWallet","onLoadingChange","walletContext","isAuthLoading","adapterWallet","useWallet","modalVisible","setModalVisible","useWalletModal","pendingLogin","setPendingLogin","triggerConnect","setTriggerConnect","isProcessingRef","useRef","modalWasOpen","signRejectedRef","connected","connecting","signMessage","wallet","wallets","select","name","connect","installedWallets","w","executeSignIn","pubKeyString","challenge","messageBytes","signatureBytes","useEffect","handleClick","sizeClasses","variantClasses","jsxs","LoadingSpinner"],"mappings":"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"}
|