React-библиотека
@webgui/react предоставляет хуки React 18 и TypeScript-типы для SPA, работающих внутри мода WebGUI. Все хуки используют useSyncExternalStore — безопасны для concurrent mode, Provider не нужен.
Установка
npm install @webgui/react
# или
pnpm add @webgui/reactТребует React 18+ как peer dependency.
Быстрый пример
import { useWebGUIClient, isInMod, isReady } from '@webgui/react'
export function PlayerInfo() {
const client = useWebGUIClient()
if (!isInMod()) return <p>Откройте эту страницу внутри Minecraft.</p>
if (!isReady(client)) return <p>Подключение…</p>
return <p>Привет, {client!.username}</p>
}Как это работает
Мод внедряет window.webgui на каждую страницу и отправляет CustomEvent webgui:client при каждом обновлении данных клиента. Библиотека подписывается на это событие при загрузке модуля и обновляет все хуки через singleton store — никакой настройки не нужно.
Доступные данные
Мод отправляет снимок данных со скоростью 20 TPS (раз в тик клиента):
interface WebGUIClient {
playerUuid: string // UUID игрока в мире
username: string // отображаемое имя
webviewMode: 'GUI_SCREEN' | 'HUD_OVERLAY' | 'NONE'
dimension: string // напр. "minecraft:overworld"
pos: { x: number; y: number; z: number }
server?: {
address?: string // адрес сервера
ping?: number // пинг в мс
}
}Если GUI был открыт нажатием ПКМ по сущности (через /webgui bind entity), мод также устанавливает window.webgui.entity:
interface WebGUIEntity {
uuid: string // UUID сущности
type: string // тип с неймспейсом, напр. "minecraft:villager"
name: string // кастомное имя, если установлено, иначе отображаемое имя типа
pos: { x: number; y: number; z: number }
}window.webgui.entity равен null, если GUI был открыт командой, а не взаимодействием с сущностью.
Хуки
useWebGUIClient
function useWebGUIClient(): WebGUIClient | nullВозвращает последний снимок данных. null до первой отправки или вне мода. Перерисовывается при каждом обновлении.
useWebGUIEntity
function useWebGUIEntity(): WebGUIEntity | nullВозвращает сущность, по которой игрок нажал ПКМ для открытия этого GUI, или null, если GUI был открыт командой.
import { useWebGUIEntity } from '@webgui/react'
export function NpcHeader() {
const entity = useWebGUIEntity()
if (!entity) return null
return <h2>Разговариваете с: {entity.name}</h2>
}useWebGUISelector
function useWebGUISelector<T>(
selector: (client: WebGUIClient) => T,
equalFn?: (a: T, b: T) => boolean
): T | nullВычисляет значение и перерисовывается только при его изменении.
const username = useWebGUISelector(c => c.username)usePostToGame
function usePostToGame(): (payload: PostToGamePayload) => voidСтабильный колбэк, отправляющий сообщение в игру через CEF message router мода. Вне мода — no-op.
const post = usePostToGame()
post({ channel: 'log', level: 'info', message: 'магазин открыт' })
post({ channel: 'shop:buy', itemId: 'minecraft:diamond', qty: 1 })useCloseGui
function useCloseGui(): () => voidСтабильный колбэк, закрывающий активный HUD-оверлей или GUI-экран из SPA.
const close = useCloseGui()
<button onClick={close}>✕ Закрыть</button>useWebGUIToken
function useWebGUIToken(paramName?: string): string | nullВозвращает подписанный токен, который мод добавил к URL текущей страницы, или null, если токен отсутствует (токены отключены на сервере или страница открыта в обычном браузере). Токен не меняется в течение всего времени жизни страницы.
| Параметр | По умолчанию | Описание |
|---|---|---|
paramName | "webgui_token" | Имя query-параметра, настроенное в server.json |
import { useWebGUIToken } from '@webgui/react'
export function App() {
const token = useWebGUIToken()
useEffect(() => {
if (!token) return
fetch('/api/data?webgui_token=' + token)
.then(r => r.json())
.then(setData)
}, [token])
}Смотрите раздел Проверка токена на бэкенде для примеров верификации на Python и Node.js.
useWebGUIEvent
function useWebGUIEvent<T = unknown>(
eventName: string,
handler: (data: T) => void
): voidПодписывается на именованное событие, отправленное с сервера через WebviewApi.emitToPage. Имя eventName указывается без префикса webgui:; в handler приходит полезная нагрузка из detail при каждом срабатывании. Слушатель регистрируется при монтировании и автоматически удаляется при размонтировании.
import { useState } from 'react'
import { useWebGUIEvent } from '@webgui/react'
export function Wallet() {
const [balance, setBalance] = useState(0)
useWebGUIEvent<{ balance: number }>('walletUpdate', ({ balance }) => {
setBalance(balance)
})
return <span>Баланс: {balance}</span>
}TIP
Передавайте стабильный handler (например, обёрнутый в useCallback), если его идентичность иначе меняется на каждом рендере, — подписка пересоздаётся при изменении eventName или handler.
Как сервер отправляет такие события, смотрите в разделе События.
Утилиты
Обычные функции — можно использовать вне React, в условиях, в тестах.
isInMod()
function isInMod(): booleantrue, когда window.webgui присутствует.
isReady(client)
function isReady(client: WebGUIClient | null): booleantrue, когда client !== null — получен хотя бы один снимок данных.
TypeScript — глобальные типы
Пакет поставляется с глобальными аугментациями для window.webgui, события webgui:client и события webgui:entity. Добавьте в tsconfig.json:
{
"compilerOptions": {
"types": ["@webgui/react"]
}
}Затем:
window.webgui?.postToGame({ channel: 'log', message: 'hello' })
window.addEventListener('webgui:client', (e) => {
console.log(e.detail.username) // полная типизация
})
window.addEventListener('webgui:entity', (e) => {
console.log(e.detail?.uuid) // WebGUIEntity | null, полная типизация
})Server-side rendering
Все хуки возвращают null на сервере. isInMod() возвращает false. Проверки typeof window в компонентах не нужны.