import matrix , { ICreateClientOpts , MatrixClient , } from "@robertlong/matrix-js-sdk" ; import React , { createContext , PropsWithChildren , useMemo , useState , useCallback , useEffect , } from "react" ; function waitForSync ( client ) { return new Promise < void > ( ( resolve ) => { const onSync = ( state ) => { if ( state === "PREPARED" ) { resolve ( ) ; client . removeListener ( "sync" , onSync ) ; } } ; client . on ( "sync" , onSync ) ; } ) ; } async function initClient ( clientOptions , guest ? ) { const client = matrix . createClient ( clientOptions ) ; if ( guest ) { client . setGuest ( true ) ; } await client . startClient ( { initialSyncLimit : 50 , } ) ; await waitForSync ( client ) ; return client ; } interface ClientContextProps { client ? : MatrixClient ; loading : boolean ; authenticated : boolean ; error ? : Error ; login : ( userName : string , password : string ) => Promise < void > ; registerGuest : ( displayName : string ) => Promise < void > ; register : ( userName : string , password : string ) => Promise < void > ; logout : ( ) => void ; } export const ClientContext = createContext < ClientContextProps > ( { client : undefined , loading : false , authenticated : false , error : undefined , login : ( username , password ) => Promise . reject ( "uninitialized" ) , registerGuest : ( displayName ) => Promise . reject ( "uninitialized" ) , register : ( username , password ) => Promise . reject ( "uninitialized" ) , logout : ( ) => { throw new Error ( "uninitialized" ) ; } , } ) ; interface ClientContextProviderState { client ? : MatrixClient ; loading : boolean ; authenticated : boolean ; } const homeserverUrl = process . env . MATRIX_HOMESERVER_URL as string ; const CURRENT_SESSION_VERSION = 1 ; export function ClientContextProvider ( { children } ) { const [ state , setState ] = useState < ClientContextProviderState > ( { loading : true , authenticated : false , } ) ; useEffect ( ( ) => { async function restore ( ) { try { const authStore = localStorage . getItem ( "matrix-auth-store" ) ; if ( authStore ) { const { user_id , device_id , access_token , session_version } = JSON . parse ( authStore ) ; if ( ! session_version || session_version < CURRENT_SESSION_VERSION ) { localStorage . removeItem ( "matrix-auth-store" ) ; return ; } const client = await initClient ( { baseUrl : homeserverUrl , accessToken : access_token , userId : user_id , deviceId : device_id , } ) ; localStorage . setItem ( "matrix-auth-store" , JSON . stringify ( { user_id , device_id , access_token , session_version : CURRENT_SESSION_VERSION , } ) ) ; return client ; } } catch ( err ) { localStorage . removeItem ( "matrix-auth-store" ) ; throw err ; } } restore ( ) . then ( ( client ) => { if ( client ) { setState ( { client , loading : false , authenticated : true } ) ; } else { setState ( { client : undefined , loading : false , authenticated : false } ) ; } } ) . catch ( ( ) => { setState ( { client : undefined , loading : false , authenticated : false } ) ; } ) ; } , [ ] ) ; const login = useCallback ( async ( username , password ) => { try { const registrationClient = matrix . createClient ( homeserverUrl ) ; const { user_id , device_id , access_token } = await registrationClient . loginWithPassword ( username , password ) ; const client = await initClient ( { baseUrl : homeserverUrl , accessToken : access_token , userId : user_id , deviceId : device_id , } ) ; localStorage . setItem ( "matrix-auth-store" , JSON . stringify ( { user_id , device_id , access_token , session_version : CURRENT_SESSION_VERSION , } ) ) ; setState ( { client , loading : false , authenticated : true } ) ; } catch ( err ) { localStorage . removeItem ( "matrix-auth-store" ) ; setState ( { client : undefined , loading : false , authenticated : false } ) ; throw err ; } } , [ ] ) ; const registerGuest = useCallback ( async ( displayName ) => { try { const registrationClient = matrix . createClient ( homeserverUrl ) ; const { user_id , device_id , access_token } = await registrationClient . registerGuest ( { } ) ; const client = await initClient ( { baseUrl : homeserverUrl , accessToken : access_token , userId : user_id , deviceId : device_id , } , true ) ; localStorage . setItem ( "matrix-auth-store" , JSON . stringify ( { user_id , device_id , access_token , session_version : CURRENT_SESSION_VERSION , } ) ) ; setState ( { client , loading : false , authenticated : true } ) ; } catch ( err ) { localStorage . removeItem ( "matrix-auth-store" ) ; setState ( { client : undefined , loading : false , authenticated : false } ) ; throw err ; } } , [ ] ) ; const register = useCallback ( async ( username , password ) => { try { const registrationClient = matrix . createClient ( homeserverUrl ) ; const { user_id , device_id , access_token } = await registrationClient . register ( username , password , null , { type : "m.login.dummy" , } ) ; const client = await initClient ( { baseUrl : homeserverUrl , accessToken : access_token , userId : user_id , deviceId : device_id , } ) ; localStorage . setItem ( "matrix-auth-store" , JSON . stringify ( { user_id , device_id , access_token , session_version : CURRENT_SESSION_VERSION , } ) ) ; setState ( { client , loading : false , authenticated : true } ) ; } catch ( err ) { localStorage . removeItem ( "matrix-auth-store" ) ; setState ( { client : undefined , loading : false , authenticated : false } ) ; throw err ; } } , [ ] ) ; const logout = useCallback ( ( ) => { localStorage . removeItem ( "matrix-auth-store" ) ; setState ( { client : undefined , loading : false , authenticated : false } ) ; } , [ ] ) ; const context = useMemo ( ( ) => ( { ... state , login , registerGuest , register , logout } ) , [ state ] ) ; if ( state . loading ) { return < div > Loading ... < /div>; } return ( < ClientContext . Provider value = { context } > { children } < /ClientContext.Provider>); }