Created
June 24, 2025 15:05
-
-
Save MatiFalcone/79cdc94a5f89006bceafbd886aaefb3c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 'use client'; | |
| import React, { | |
| createContext, | |
| useContext, | |
| useState, | |
| useCallback, | |
| useRef, | |
| ReactNode, | |
| } from 'react'; | |
| import type { AirUserDetails as AirKitUserDetails } from '@mocanetwork/airkit'; | |
| // Define the context shape | |
| interface AirKitContextType { | |
| // Core connection state | |
| isAirKitConnected: boolean; | |
| // Authentication | |
| connectWithAirKit: () => Promise<void>; | |
| logout: () => Promise<void>; | |
| clearInit: () => void; | |
| // User information | |
| getUserInformation: () => Promise<AirKitUserDetails | undefined>; | |
| // Wallet management | |
| getProvider: () => Promise<any>; // Returns Eip1193Provider compatible with web3.js, ethers.js, viem, etc. | |
| preloadWallet: () => Promise<void>; | |
| isSmartAccountDeployed: () => Promise<boolean>; | |
| deploySmartAccount: () => Promise<void>; | |
| // MFA and security | |
| setupOrUpdateMfa: () => Promise<void>; | |
| // Air ID | |
| claimAirId: () => Promise<void>; | |
| // Partner integration | |
| goToPartner: (partnerUrl: string) => Promise<{ urlWithToken: string }>; | |
| getAccessToken: () => Promise<object | null>; | |
| // Event handling | |
| on: (event: string, callback: (...args: any[]) => void) => void; | |
| off: (event: string, callback: (...args: any[]) => void) => void; | |
| // New API methods | |
| /** | |
| * Add a new user to the Motorverse Hub | |
| * This should be called first after successful Airkit authentication | |
| * @returns The created user data | |
| */ | |
| addUser: () => Promise<any>; | |
| /** | |
| * Add an email to the current user's profile | |
| * @param email The email address to add | |
| * @returns The updated user data | |
| */ | |
| addEmail: (email: string) => Promise<any>; | |
| /** | |
| * Add a wallet to the current user's profile | |
| * @param address The wallet address to add | |
| * @param signature The signature of the message | |
| * @param message The message that was signed | |
| * @param walletType The type of wallet (e.g., 'metamask') | |
| * @returns The updated user data | |
| */ | |
| addWallet: (address: string, signature: string, message: string, walletType?: string) => Promise<any>; | |
| /** | |
| * Remove a wallet from the current user's profile | |
| * @param walletId The ID of the wallet to remove | |
| * @returns True if the wallet was successfully removed | |
| */ | |
| deleteWallet: (walletId: string) => Promise<boolean>; | |
| getUserWallets: () => Promise<Array<{ address: string; chainId: number }>>; | |
| /** | |
| * Set a wallet as the default wallet for the user | |
| * @param walletId The ID of the wallet to set as default | |
| * @returns True if the operation was successful | |
| */ | |
| setDefaultWallet: (walletId: string) => Promise<boolean>; | |
| /** | |
| * Get the current user's information | |
| * @returns The user's data | |
| */ | |
| getUser: () => Promise<any>; | |
| } | |
| const AirKitContext = createContext<AirKitContextType | undefined>(undefined); | |
| // Import the singleton instance creator | |
| import { getAirService, AirKitApi } from '@/lib/airkitApi'; | |
| export function AirKitProvider({ children }: { children: ReactNode }) { | |
| const [isAirKitConnected, setIsAirKitConnected] = useState(false); | |
| const serviceRef = useRef<any>(null); | |
| const airkitApiRef = useRef<any>(null); | |
| const [isInitialized, setIsInitialized] = useState(false); | |
| // Initialize the service once | |
| if (!serviceRef.current) { | |
| serviceRef.current = getAirService(); | |
| // Create a new instance of AirKitApi | |
| airkitApiRef.current = new AirKitApi(); | |
| } | |
| // Check for existing session on mount | |
| React.useEffect(() => { | |
| const checkSession = async () => { | |
| if (!serviceRef.current || isInitialized) return; | |
| try { | |
| // First initialize with rehydration enabled | |
| await serviceRef.current.init({ | |
| buildEnv: "staging", | |
| enableLogging: true, | |
| skipRehydration: false // Enable rehydration to restore session | |
| }); | |
| // After initialization, check if we have a valid session | |
| // by trying to get the user info | |
| try { | |
| const userInfo = await serviceRef.current.getUserInfo(); | |
| const hasValidSession = !!userInfo && typeof userInfo === 'object' && Object.keys(userInfo).length > 0; | |
| if (hasValidSession) { | |
| console.log('User session restored:', userInfo); | |
| setIsAirKitConnected(true); | |
| } else { | |
| console.log('No active session found'); | |
| setIsAirKitConnected(false); | |
| } | |
| } catch (err) { | |
| console.log('No active session or error getting user info:', err); | |
| setIsAirKitConnected(false); | |
| } | |
| } catch (err) { | |
| console.error("Error initializing AirKit:", err); | |
| setIsAirKitConnected(false); | |
| } finally { | |
| setIsInitialized(true); | |
| } | |
| }; | |
| checkSession(); | |
| }, [isInitialized]); | |
| // Connect and login function | |
| const connectWithAirKit = useCallback(async () => { | |
| if (!serviceRef.current) return; | |
| try { | |
| await serviceRef.current.init({ | |
| buildEnv: "staging", | |
| enableLogging: true, | |
| skipRehydration: false // Enable rehydration to maintain session | |
| }); | |
| await serviceRef.current.login(); | |
| setIsAirKitConnected(true); | |
| } catch (err) { | |
| console.error("AIR Kit login failed:", err); | |
| setIsAirKitConnected(false); | |
| throw err; // Re-throw to allow error handling in the component | |
| } | |
| }, []); | |
| // Get user information function | |
| const getUserInformation = useCallback(async (): Promise<AirKitUserDetails | undefined> => { | |
| if (!serviceRef.current) return; | |
| try { | |
| // @ts-ignore - We know getUserInfo exists on the service | |
| const userInfo = await serviceRef.current.getUserInfo(); | |
| console.log('User info received:', userInfo); | |
| return userInfo as AirKitUserDetails; | |
| } catch (err) { | |
| console.error("AIR Kit getUserInfo failed:", err); | |
| throw err; | |
| } | |
| }, []); | |
| const goToPartner = useCallback(async (partnerUrl: string) => { | |
| if (!serviceRef.current) throw new Error("AirKit service not initialized"); | |
| return serviceRef.current.goToPartner(partnerUrl); | |
| }, []); | |
| const getProvider = useCallback(async () => { | |
| if (!serviceRef.current) throw new Error("AirKit service not initialized"); | |
| return await serviceRef.current.getProvider(); | |
| }, []); | |
| const getAccessToken = useCallback(async (): Promise<object | null> => { | |
| if (!serviceRef.current) throw new Error("AirKit service not initialized"); | |
| return await serviceRef.current.getAccessToken(); | |
| }, []); | |
| const preloadWallet = useCallback(async () => { | |
| if (!serviceRef.current) throw new Error("AirKit service not initialized"); | |
| await serviceRef.current.preloadWallet(); | |
| }, []); | |
| const setupOrUpdateMfa = useCallback(async () => { | |
| if (!serviceRef.current) throw new Error("AirKit service not initialized"); | |
| await serviceRef.current.setupOrUpdateMfa(); | |
| }, []); | |
| const claimAirId = useCallback(async () => { | |
| if (!serviceRef.current) throw new Error("AirKit service not initialized"); | |
| // Assuming there's a claimAirId method in the SDK | |
| // @ts-ignore - If the method doesn't exist, this will be a no-op | |
| if (typeof serviceRef.current.claimAirId === 'function') { | |
| // @ts-ignore | |
| await serviceRef.current.claimAirId(); | |
| } else { | |
| console.warn('claimAirId method not available in the current SDK version'); | |
| } | |
| }, []); | |
| const isSmartAccountDeployed = useCallback(async (): Promise<boolean> => { | |
| if (!serviceRef.current) throw new Error("AirKit service not initialized"); | |
| return serviceRef.current.isSmartAccountDeployed(); | |
| }, []); | |
| const deploySmartAccount = useCallback(async () => { | |
| if (!serviceRef.current) throw new Error("AirKit service not initialized"); | |
| // Assuming there's a deploySmartAccount method in the SDK | |
| // @ts-ignore - If the method doesn't exist, this will be a no-op | |
| if (typeof serviceRef.current.deploySmartAccount === 'function') { | |
| // @ts-ignore | |
| await serviceRef.current.deploySmartAccount(); | |
| } else { | |
| console.warn('deploySmartAccount method not available in the current SDK version'); | |
| } | |
| }, []); | |
| const logout = useCallback(async () => { | |
| if (!serviceRef.current) return; // Early return if not initialized | |
| console.log('Explicit logout called'); | |
| try { | |
| // @ts-ignore - Check if logout method exists | |
| if (typeof serviceRef.current.logout === 'function') { | |
| // @ts-ignore | |
| await serviceRef.current.logout(); | |
| } | |
| setIsAirKitConnected(false); | |
| } catch (error) { | |
| console.error('Error during logout:', error); | |
| throw error; // Re-throw to allow error handling in the calling component | |
| } | |
| }, []); | |
| const clearInit = useCallback(() => { | |
| // This would typically clear any initialization state | |
| // Implementation depends on SDK requirements | |
| console.log('Clear init called'); | |
| }, []); | |
| const on = useCallback((event: string, callback: (...args: any[]) => void) => { | |
| if (!serviceRef.current) return; | |
| // @ts-ignore - If the method doesn't exist, this will be a no-op | |
| if (typeof serviceRef.current.on === 'function') { | |
| // @ts-ignore | |
| serviceRef.current.on(event, callback); | |
| } | |
| }, []); | |
| const off = useCallback((event: string, callback: (...args: any[]) => void) => { | |
| if (!serviceRef.current) return; | |
| // @ts-ignore - If the method doesn't exist, this will be a no-op | |
| if (typeof serviceRef.current.off === 'function') { | |
| // @ts-ignore | |
| serviceRef.current.off(event, callback); | |
| } | |
| }, []); | |
| const addUser = useCallback(async () => { | |
| if (!airkitApiRef.current) throw new Error("AirKit API not initialized"); | |
| return airkitApiRef.current.addUser(); | |
| }, []); | |
| const addEmail = useCallback(async (email: string) => { | |
| if (!airkitApiRef.current) throw new Error("AirKit API not initialized"); | |
| return airkitApiRef.current.addEmail(email); | |
| }, []); | |
| const addWallet = useCallback(async (address: string, signature: string, message: string, walletType: string = 'metamask') => { | |
| if (!airkitApiRef.current) throw new Error("AirKit API not initialized"); | |
| return airkitApiRef.current.addWallet(address, signature, message, walletType); | |
| }, []); | |
| const deleteWallet = useCallback(async (walletId: string) => { | |
| if (!airkitApiRef.current) throw new Error("AirKit API not initialized"); | |
| return airkitApiRef.current.deleteWallet(walletId); | |
| }, []); | |
| const getUserWallets = useCallback(async () => { | |
| if (!airkitApiRef.current) throw new Error("AirKit API not initialized"); | |
| return airkitApiRef.current.getUserWallets(); | |
| }, []); | |
| const getUser = useCallback(async () => { | |
| try { | |
| if (!airkitApiRef.current) { | |
| console.error('AirKit API not initialized - attempting to initialize...'); | |
| // Try to initialize if not already done | |
| if (!serviceRef.current) { | |
| serviceRef.current = getAirService(); | |
| airkitApiRef.current = new AirKitApi(); | |
| } | |
| if (!airkitApiRef.current) { | |
| throw new Error('Failed to initialize AirKit API'); | |
| } | |
| } | |
| console.log('Calling getUser on AirKit API...'); | |
| const result = await airkitApiRef.current.getUser(); | |
| console.log('getUser result:', result); | |
| return result; | |
| } catch (error) { | |
| console.error('Error in getUser:', { | |
| error: error instanceof Error ? error.message : 'Unknown error', | |
| hasAirKitApi: !!airkitApiRef.current, | |
| hasAirService: !!serviceRef.current, | |
| isAirKitConnected, | |
| timestamp: new Date().toISOString() | |
| }); | |
| throw error; | |
| } | |
| }, [isAirKitConnected]); | |
| const setDefaultWallet = useCallback(async (walletId: string) => { | |
| if (!airkitApiRef.current) throw new Error("AirKit API not initialized"); | |
| return airkitApiRef.current.setDefaultWallet(walletId); | |
| }, []); | |
| const contextValue = { | |
| isAirKitConnected, | |
| connectWithAirKit, | |
| getUserInformation, | |
| goToPartner, | |
| getProvider, | |
| getAccessToken, | |
| preloadWallet, | |
| setupOrUpdateMfa, | |
| claimAirId, | |
| isSmartAccountDeployed, | |
| deploySmartAccount, | |
| logout, | |
| clearInit, | |
| on, | |
| off, | |
| addUser, | |
| addEmail, | |
| addWallet, | |
| deleteWallet, | |
| getUserWallets, | |
| setDefaultWallet, | |
| getUser, | |
| }; | |
| return ( | |
| <AirKitContext.Provider value={contextValue}> | |
| {children} | |
| </AirKitContext.Provider> | |
| ); | |
| } | |
| export function useAirKit() { | |
| const context = useContext(AirKitContext); | |
| if (context === undefined) { | |
| throw new Error('useAirKit must be used within an AirKitProvider'); | |
| } | |
| return context; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment