Skip to content

React-библиотека

@webgui/react предоставляет хуки React 18 и TypeScript-типы для SPA, работающих внутри мода WebGUI. Все хуки используют useSyncExternalStore — безопасны для concurrent mode, Provider не нужен.

Установка

bash
npm install @webgui/react
# или
pnpm add @webgui/react

Требует React 18+ как peer dependency.

Быстрый пример

tsx
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 (раз в тик клиента):

ts
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:

ts
interface WebGUIEntity {
  uuid: string          // UUID сущности
  type: string          // тип с неймспейсом, напр. "minecraft:villager"
  name: string          // кастомное имя, если установлено, иначе отображаемое имя типа
  pos:  { x: number; y: number; z: number }
}

window.webgui.entity равен null, если GUI был открыт командой, а не взаимодействием с сущностью.

Хуки

useWebGUIClient

ts
function useWebGUIClient(): WebGUIClient | null

Возвращает последний снимок данных. null до первой отправки или вне мода. Перерисовывается при каждом обновлении.

useWebGUIEntity

ts
function useWebGUIEntity(): WebGUIEntity | null

Возвращает сущность, по которой игрок нажал ПКМ для открытия этого GUI, или null, если GUI был открыт командой.

tsx
import { useWebGUIEntity } from '@webgui/react'

export function NpcHeader() {
  const entity = useWebGUIEntity()
  if (!entity) return null
  return <h2>Разговариваете с: {entity.name}</h2>
}

useWebGUISelector

ts
function useWebGUISelector<T>(
  selector: (client: WebGUIClient) => T,
  equalFn?: (a: T, b: T) => boolean
): T | null

Вычисляет значение и перерисовывается только при его изменении.

tsx
const username = useWebGUISelector(c => c.username)

usePostToGame

ts
function usePostToGame(): (payload: PostToGamePayload) => void

Стабильный колбэк, отправляющий сообщение в игру через CEF message router мода. Вне мода — no-op.

tsx
const post = usePostToGame()

post({ channel: 'log', level: 'info', message: 'магазин открыт' })
post({ channel: 'shop:buy', itemId: 'minecraft:diamond', qty: 1 })

useCloseGui

ts
function useCloseGui(): () => void

Стабильный колбэк, закрывающий активный HUD-оверлей или GUI-экран из SPA.

tsx
const close = useCloseGui()
<button onClick={close}>✕ Закрыть</button>

useWebGUIToken

ts
function useWebGUIToken(paramName?: string): string | null

Возвращает подписанный токен, который мод добавил к URL текущей страницы, или null, если токен отсутствует (токены отключены на сервере или страница открыта в обычном браузере). Токен не меняется в течение всего времени жизни страницы.

ПараметрПо умолчаниюОписание
paramName"webgui_token"Имя query-параметра, настроенное в server.json
tsx
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

ts
function useWebGUIEvent<T = unknown>(
  eventName: string,
  handler: (data: T) => void
): void

Подписывается на именованное событие, отправленное с сервера через WebviewApi.emitToPage. Имя eventName указывается без префикса webgui:; в handler приходит полезная нагрузка из detail при каждом срабатывании. Слушатель регистрируется при монтировании и автоматически удаляется при размонтировании.

tsx
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()

ts
function isInMod(): boolean

true, когда window.webgui присутствует.

isReady(client)

ts
function isReady(client: WebGUIClient | null): boolean

true, когда client !== null — получен хотя бы один снимок данных.

TypeScript — глобальные типы

Пакет поставляется с глобальными аугментациями для window.webgui, события webgui:client и события webgui:entity. Добавьте в tsconfig.json:

json
{
  "compilerOptions": {
    "types": ["@webgui/react"]
  }
}

Затем:

ts
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 в компонентах не нужны.