sso-context.tsx 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import {
  2. createContext,
  3. useCallback,
  4. useContext,
  5. useState,
  6. useMemo,
  7. ReactNode,
  8. } from 'react'
  9. import { postJSON } from '../../../infrastructure/fetch-json'
  10. import useIsMounted from '../../../shared/hooks/use-is-mounted'
  11. import { set, cloneDeep } from 'lodash'
  12. import getMeta from '../../../utils/meta'
  13. import type { OAuthProvider } from '../../../../../types/oauth-providers'
  14. export type SSOSubscription = {
  15. providerId: string
  16. provider: OAuthProvider
  17. linked: boolean
  18. }
  19. type SSOContextValue = {
  20. subscriptions: Record<string, SSOSubscription>
  21. unlink: (id: string, signal?: AbortSignal) => Promise<void>
  22. }
  23. export const SSOContext = createContext<SSOContextValue | undefined>(undefined)
  24. type SSOProviderProps = {
  25. children: ReactNode
  26. }
  27. export function SSOProvider({ children }: SSOProviderProps) {
  28. const isMounted = useIsMounted()
  29. const oauthProviders = getMeta('ol-oauthProviders') || {}
  30. const thirdPartyIds = getMeta('ol-thirdPartyIds')
  31. const [subscriptions, setSubscriptions] = useState<
  32. Record<string, SSOSubscription>
  33. >(() => {
  34. const initialSubscriptions: Record<string, SSOSubscription> = {}
  35. for (const [id, provider] of Object.entries(oauthProviders)) {
  36. const linked = !!thirdPartyIds[id]
  37. if (!provider.hideWhenNotLinked || linked) {
  38. initialSubscriptions[id] = {
  39. providerId: id,
  40. provider,
  41. linked,
  42. }
  43. }
  44. }
  45. return initialSubscriptions
  46. })
  47. const unlink = useCallback(
  48. (providerId: string, signal?: AbortSignal) => {
  49. if (!subscriptions[providerId].linked) {
  50. return Promise.resolve()
  51. }
  52. const body = {
  53. link: false,
  54. providerId,
  55. }
  56. return postJSON('/user/oauth-unlink', { body, signal }).then(() => {
  57. if (isMounted.current) {
  58. setSubscriptions(subs =>
  59. set(cloneDeep(subs), `${providerId}.linked`, false)
  60. )
  61. }
  62. })
  63. },
  64. [isMounted, subscriptions]
  65. )
  66. const value = useMemo<SSOContextValue>(
  67. () => ({
  68. subscriptions,
  69. unlink,
  70. }),
  71. [subscriptions, unlink]
  72. )
  73. return <SSOContext.Provider value={value}>{children}</SSOContext.Provider>
  74. }
  75. export function useSSOContext() {
  76. const context = useContext(SSOContext)
  77. if (!context) {
  78. throw new Error('SSOContext is only available inside SSOProvider')
  79. }
  80. return context
  81. }