RelayAdminProvider.tsx raw

   1  /**
   2   * RelayAdminProvider
   3   *
   4   * Detects whether smesh is embedded in an ORLY relay (same-origin) and provides
   5   * admin context: user role, ACL mode, relay info. Admin features are only shown
   6   * when embedded AND the user has admin/owner privileges.
   7   */
   8  import relayAdmin from '@/services/relay-admin.service'
   9  import { useNostr } from '@/providers/NostrProvider'
  10  import { createContext, useContext, useEffect, useState, useCallback } from 'react'
  11  
  12  type TRelayAdminContext = {
  13    isEmbedded: boolean
  14    isLoading: boolean
  15    userRole: string
  16    aclMode: string
  17    relayInfo: Record<string, unknown> | null
  18    isAdmin: boolean
  19    isOwner: boolean
  20    refreshRole: () => Promise<void>
  21    refreshACLMode: () => Promise<void>
  22  }
  23  
  24  const RelayAdminContext = createContext<TRelayAdminContext | undefined>(undefined)
  25  
  26  export const useRelayAdmin = () => {
  27    const context = useContext(RelayAdminContext)
  28    if (!context) {
  29      throw new Error('useRelayAdmin must be used within a RelayAdminProvider')
  30    }
  31    return context
  32  }
  33  
  34  export function RelayAdminProvider({ children }: { children: React.ReactNode }) {
  35    const { pubkey } = useNostr()
  36    const [isEmbedded, setIsEmbedded] = useState(false)
  37    const [isLoading, setIsLoading] = useState(true)
  38    const [userRole, setUserRole] = useState('')
  39    const [aclMode, setAclMode] = useState('')
  40    const [relayInfo, setRelayInfo] = useState<Record<string, unknown> | null>(null)
  41  
  42    // Detect embedded mode on mount
  43    useEffect(() => {
  44      let cancelled = false
  45      const detect = async () => {
  46        const embedded = await relayAdmin.isEmbeddedInRelay()
  47        if (cancelled) return
  48        setIsEmbedded(embedded)
  49        if (embedded) {
  50          const [info, mode] = await Promise.all([
  51            relayAdmin.fetchRelayInfo(),
  52            relayAdmin.fetchACLMode()
  53          ])
  54          if (!cancelled) {
  55            setRelayInfo(info)
  56            setAclMode(mode)
  57          }
  58        }
  59        if (!cancelled) setIsLoading(false)
  60      }
  61      detect()
  62      return () => {
  63        cancelled = true
  64      }
  65    }, [])
  66  
  67    // Fetch user role when pubkey changes and we're embedded
  68    useEffect(() => {
  69      if (!isEmbedded || !pubkey) {
  70        setUserRole('')
  71        return
  72      }
  73      let cancelled = false
  74      const fetchRole = async () => {
  75        try {
  76          const role = await relayAdmin.fetchUserRole()
  77          if (!cancelled) setUserRole(role)
  78        } catch {
  79          if (!cancelled) setUserRole('')
  80        }
  81      }
  82      fetchRole()
  83      return () => {
  84        cancelled = true
  85      }
  86    }, [isEmbedded, pubkey])
  87  
  88    const refreshRole = useCallback(async () => {
  89      if (!isEmbedded || !pubkey) return
  90      const role = await relayAdmin.fetchUserRole()
  91      setUserRole(role)
  92    }, [isEmbedded, pubkey])
  93  
  94    const refreshACLMode = useCallback(async () => {
  95      if (!isEmbedded) return
  96      const mode = await relayAdmin.fetchACLMode()
  97      setAclMode(mode)
  98    }, [isEmbedded])
  99  
 100    const isAdmin = userRole === 'admin' || userRole === 'owner'
 101    const isOwner = userRole === 'owner'
 102  
 103    return (
 104      <RelayAdminContext.Provider
 105        value={{
 106          isEmbedded,
 107          isLoading,
 108          userRole,
 109          aclMode,
 110          relayInfo,
 111          isAdmin,
 112          isOwner,
 113          refreshRole,
 114          refreshACLMode
 115        }}
 116      >
 117        {children}
 118      </RelayAdminContext.Provider>
 119    )
 120  }
 121