sso-context.tsx 2.5 KB

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