revenge-bundle-next/src/plugins/init/developer-settings/react-devtools.ts

94 lines
2.3 KiB
TypeScript
Raw Normal View History

import { ToastActionCreators } from '@revenge-mod/discord/actions'
2025-05-19 01:43:41 +07:00
import { lookupGeneratedIconComponent } from '@revenge-mod/utils/discord'
import { getErrorStack } from '@revenge-mod/utils/errors'
import { useReRender } from '@revenge-mod/utils/react'
import { useEffect } from 'react'
import { api } from '.'
export const DevToolsContext: {
ws: WebSocket | undefined
address: string
open: boolean
available: boolean
} = {
available: Boolean(globalThis.__REACT_DEVTOOLS__),
ws: undefined,
address: 'localhost:8097',
open: false,
}
type Subscription = (event: 1 | 2 | 3, err?: unknown) => void
const subscriptions: Set<Subscription> = new Set()
2025-06-03 16:24:58 +07:00
const CircleXIcon = lookupGeneratedIconComponent(
'CircleXIcon',
'CircleXIcon-secondary',
'CircleXIcon-primary',
)
subscriptions.add((e, err) => {
if (e === 3) {
2025-06-03 16:24:58 +07:00
const actualError =
(err as { message: string }).message ?? getErrorStack(err)
api.logger.error('React DevTools error:', actualError)
ToastActionCreators.open({
key: 'REACT_DEVTOOLS_ERROR',
IconComponent: CircleXIcon,
content: actualError,
})
}
})
export function connectToDevTools() {
if (!DevToolsContext.available || DevToolsContext.ws) return
2025-06-03 16:24:58 +07:00
const websocket = (DevToolsContext.ws = new WebSocket(
`ws://${DevToolsContext.address}`,
))
websocket.addEventListener('open', () => {
DevToolsContext.open = true
for (const sub of subscriptions) sub(1)
})
websocket.addEventListener('close', () => {
cleanup()
for (const sub of subscriptions) sub(2)
})
websocket.addEventListener('error', e => {
cleanup()
for (const sub of subscriptions) sub(3, e)
})
__REACT_DEVTOOLS__!.exports.connectToDevTools({
websocket,
})
}
function cleanup() {
DevToolsContext.open = false
DevToolsContext.ws = undefined
}
export function disconnectFromDevTools() {
if (DevToolsContext.ws) DevToolsContext.ws.close()
}
export function useIsDevToolsOpen() {
const rerender = useReRender()
useEffect(() => {
const sub: Subscription = e => (e === 1 || e === 2) && rerender()
subscriptions.add(sub)
return () => {
subscriptions.delete(sub)
}
}, [rerender])
return DevToolsContext.open
}