overleaf/services/web/frontend/js/features/settings/context/sso-context.tsx

97 lines
2.5 KiB
TypeScript
Raw Normal View History

import {
createContext,
useCallback,
useContext,
useState,
useMemo,
ReactNode,
} from 'react'
import { postJSON } from '../../../infrastructure/fetch-json'
import useIsMounted from '../../../shared/hooks/use-is-mounted'
import { set, cloneDeep } from 'lodash'
import getMeta from '../../../utils/meta'
import type {
OAuthProviders,
OAuthProvider,
} from '../../../../../types/oauth-providers'
import type { ThirdPartyIds } from '../../../../../types/third-party-ids'
export type SSOSubscription = {
providerId: string
provider: OAuthProvider
linked: boolean
}
type SSOContextValue = {
subscriptions: Record<string, SSOSubscription>
unlink: (id: string, signal?: AbortSignal) => Promise<void>
}
export const SSOContext = createContext<SSOContextValue | undefined>(undefined)
type SSOProviderProps = {
children: ReactNode
}
export function SSOProvider({ children }: SSOProviderProps) {
const isMountedRef = useIsMounted()
const oauthProviders = getMeta('ol-oauthProviders') as OAuthProviders
const thirdPartyIds = getMeta('ol-thirdPartyIds') as ThirdPartyIds
const [subscriptions, setSubscriptions] = useState<
Record<string, SSOSubscription>
>(() => {
const initialSubscriptions: Record<string, SSOSubscription> = {}
for (const [id, provider] of Object.entries(oauthProviders)) {
const linked = !!thirdPartyIds[id]
if (!provider.hideWhenNotLinked || linked) {
initialSubscriptions[id] = {
providerId: id,
provider,
linked,
}
}
}
return initialSubscriptions
})
const unlink = useCallback(
(providerId: string, signal?: AbortSignal) => {
if (!subscriptions[providerId].linked) {
return Promise.resolve()
}
const body = {
link: false,
providerId,
}
return postJSON('/user/oauth-unlink', { body, signal }).then(() => {
if (isMountedRef.current) {
setSubscriptions(subs =>
set(cloneDeep(subs), `${providerId}.linked`, false)
)
}
})
},
[isMountedRef, subscriptions]
)
const value = useMemo<SSOContextValue>(
() => ({
subscriptions,
unlink,
}),
[subscriptions, unlink]
)
return <SSOContext.Provider value={value}>{children}</SSOContext.Provider>
}
export function useSSOContext() {
const context = useContext(SSOContext)
if (!context) {
throw new Error('SSOContext is only available inside SSOProvider')
}
return context
}