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