Repository navigation
CEF
This page explains how communication works between the server, client, and frontend (CEF UI).
In a default RAGE:MP setup, communication with CEF follows this pattern:
Server → Client → Frontend
The client is required as a middle layer to forward data to the browser.
In this framework, communication is abstracted and structured.
Instead of manually managing raw events, the framework introduces a page-based event system:
RAGERP.cef.emit(player, page, pointer, data);
RAGERP.cef.register(page, pointer, handler);This creates a predictable and organized communication layer between server and frontend.
flowchart LR
S[Server]
C[Client Bridge]
F[Frontend CEF]
S --> C --> F
F --> C --> S
Events follow this internal format:
cef::page:pointer
cef::inventory:update
cef::hud:setData
This is automatically handled by the framework.
RAGERP.cef.emit(player, page, pointer, data);RAGERP.cef.emit(player, "hud", "setData", { level: 1 });-
Event name is generated:
cef::hud:setData -
Server sends event to client:
player.call("client::eventManager", [eventName, data]);
-
Client forwards it to CEF using
Browser.processEvent -
Frontend receives and handles it
RAGERP.cef.register(page, pointer, handler);RAGERP.cef.register("inventory", "moveItem", (player, data) => {
// handle logic
});-
Event is registered as:
server::inventory:moveItem -
Client forwards frontend events using:
client::eventManager::emitServer -
Server receives structured event with:
- player
- predefined data
This system is not meant to:
- improve performance
- reduce memory usage
- add security
Its purpose is to improve:
- developer experience
- structure
- predictability of data
Using default RAGE:MP:
mp.events.add("inventory:update", (player, data) => {
// what is inside data?
});Problems:
- unknown data structure
- requires debugging or logging
- requires checking frontend manually
- no clear contract between layers
With this system:
- Events are split into page + pointer
- Data is defined in:
CefData.ts - Each event has a known structure
This allows:
- predictable data flow
- no guessing
- no back-and-forth between frontend and backend
The emit method is strongly typed:
emit<T, K>(player, page: T, pointer: K, data: CefData.Interfaces.CefEventMap[T][K])This means:
- wrong data will be caught early
- event structure is enforced
- frontend and backend stay synchronized
The client acts as a transport layer.
It does not contain business logic.
Browser.processEvent(eventName, ...args);This:
- receives
cef::page:pointer - converts it into:
window.callHandler(pointer, data);- receive server events
- forward them to CEF
- receive frontend events
- forward them to server
The client is purely a bridge.
sequenceDiagram
participant P as Player
participant F as Frontend
participant C as Client
participant S as Server
P->>F: Click "Use Item"
F->>C: emit("inventory:moveItem")
C->>S: server::inventory:moveItem
S->>S: Validate & process
S-->>C: cef::inventory:update
C-->>F: processEvent("inventory:update")
F-->>P: Update UI
RAGERP.cef.emitRAGERP.cef.register- validation
- business logic
- database interaction
- Browser class
- event forwarding
- CEF execution
- UI rendering
- EventManager
- user interaction
Server is always authoritative.
Each event should:
- have defined structure
- match
CefData
Avoid:
mp.events.add(...)Use:
RAGERP.cef.register(...)Frontend should:
- render
- emit actions
Not:
- validate
- store important state
Avoid large, overloaded events.
Technically yes, but:
- overhead is negligible
- benefit in structure is massive
No.
All validation must still happen on the server.
- Server = logic + authority
- Client = transport layer
- Frontend = UI
- CEF system = structured communication layer
The frontend uses a custom event system called EventManager.
This system acts as a local event bus for UI logic.
Events follow a consistent structure:
target:name
chat:setActive
chat:sendMessage
inventory:update
EventManager.addHandler("chat", "setActive", (data) => {
// handle event
});This registers a handler for:
chat:setActive
Internally, events are triggered using:
EventManager.callHandler("chat:setActive", data);This is what gets called from CEF bridge (window.callHandler).
EventManager.emitServer("chat", "sendMessage", text);This becomes:
server::chat:sendMessage
Which is forwarded through: Frontend → Client → Server
EventManager.emitClient("chat", "close");This becomes:
client::chat:close
From client side:
window.callHandler("chat:setActive", data);Which results in:
EventManager.callHandler("chat:setActive", data);This system provides:
- structured event naming
- modular UI logic
- separation between features
- easier debugging
- no direct dependency on RAGE:MP inside UI
EventManager.addHandler("chat", "setActive", (data) => {
this.setActive(data);
});This connects UI events directly to state updates.
Frontend should not directly depend on:
- RAGE:MP events
- server logic
Instead, it uses EventManager as an abstraction layer.
EventManager is:
- a frontend event bus
- used for UI communication
- connected to the client via CEF bridge
- responsible for routing
target:nameevents
It keeps frontend clean, modular, and independent from backend logic.
The framework introduces a structured CEF communication system:
- Events are split into
page:pointer - Data is defined in shared contracts
- Server communicates using
emit - Server receives using
register
This creates:
- predictable data flow
- cleaner architecture
- easier debugging
- better developer experience
Even though the client is still part of the flow, it is abstracted away to keep development simple and consistent.