FiveM RegisterNUICallback คืออะไร ใช้อย่างไร? รับข้อมูลจาก NUI JavaScript เข้า Lua แบบถูกต้อง
FiveM RegisterNUICallback คือ API ฝั่ง Client ที่ใช้รับ Request จากหน้า NUI ซึ่งเขียนด้วย HTML/JavaScript แล้วนำข้อมูลจาก Browser เข้ามาประมวลผลใน FiveM Client Script เช่น Player กดปุ่มปิด Inventory, เลือกรถ, ใช้ Item, กดซื้อสินค้า หรือส่ง Form จาก UI
Flow พื้นฐานคือ
HTML / JavaScript
↓
fetch()
↓
NUI Callback
↓
FiveM Client Lua
↓
ประมวลผล
↓
cb(...)
↓
JavaScript ได้ Response
ตัวอย่าง JavaScript
const response = await fetch(
`https://${GetParentResourceName()}/close`,
{
method: 'POST',
headers: {
'Content-Type':
'application/json; charset=UTF-8'
},
body: JSON.stringify({})
}
);
const result =
await response.json();
ฝั่ง Client Lua
RegisterNuiCallback(
'close',
function(data, cb)
SetNuiFocus(
false,
false
)
cb({
ok = true
})
end
)
จุดสำคัญมากสำหรับ Code ปัจจุบันคือ Cfx.re ระบุว่า RegisterNUICallback แบบชื่อเดิมเป็น Legacy API ที่คงไว้เพื่อ Backward Compatibility และ Documentation ปัจจุบันใช้ RegisterNuiCallback ซึ่งเป็น Wrapper ของ REGISTER_NUI_CALLBACK สำหรับ Code ใหม่
ดังนั้นคุณยังอาจเจอ Script เก่าเขียน
RegisterNUICallback(...)
แต่ Resource ใหม่ควรอิง API ปัจจุบัน
RegisterNuiCallback(...)
① RegisterNUICallback คืออะไร
หน้าที่หลักคือรับ Event จาก NUI Browser
สมมติหน้า Inventory มี Button
<button id="close">
ปิด
</button>
JavaScript เมื่อกด Button
fetch(
`https://${GetParentResourceName()}/close`,
{
method: 'POST',
headers: {
'Content-Type':
'application/json; charset=UTF-8'
},
body:
JSON.stringify({})
}
);
FiveM Client จะสามารถรับ Callback ชื่อ
close
ได้
② RegisterNUICallback ทำงานฝั่งไหน
ทำงานใน Client Runtime
Flow คือ
NUI Browser
↓
FiveM Client
ไม่ใช่
NUI Browser
↓
FXServer โดยตรง
ถ้าต้องใช้ Server Authority ต้องส่งต่อ
NUI
↓
Client
↓
Server
อีกชั้นหนึ่ง
③ API ปัจจุบันควรเขียนอย่างไร
Lua ปัจจุบันตาม Cfx.re Documentation ใช้
RegisterNuiCallback(
'eventName',
function(data, cb)
-- handle request
cb({
ok = true
})
end
)
ตัวอย่าง
RegisterNuiCallback(
'close',
function(data, cb)
print(
'Close requested'
)
cb({
ok = true
})
end
)
④ แล้ว RegisterNUICallback แบบตัวใหญ่ล่ะ
คุณจะยังพบ
RegisterNUICallback(
'close',
function(data, cb)
cb({})
end
)
ใน Script FiveM รุ่นเก่าจำนวนมาก
Cfx.re ปัจจุบันระบุว่า API ชื่อนี้เก็บไว้เพื่อ Backward Compatibility
จึงไม่ควรตีความว่า Script เก่าทุกตัวพังทันที
แต่ Code ใหม่ควรอิง API ปัจจุบัน
⑤ REGISTER_NUI_CALLBACK คืออะไร
REGISTER_NUI_CALLBACK คือ Native/API ระดับปัจจุบันสำหรับ Register NUI Callback
ใน Lua มี Convenience Wrapper
RegisterNuiCallback(...)
จึงไม่จำเป็นต้องเขียนชื่อ Native แบบตัวใหญ่ทุกครั้ง
จำง่าย ๆ
Native
REGISTER_NUI_CALLBACK
Lua wrapper
RegisterNuiCallback
⑥ Browser เรียก Callback อย่างไร
ใช้
fetch(
`https://${GetParentResourceName()}/callbackName`,
...
);
ถ้า Lua Register
RegisterNuiCallback(
'close',
...
)
JavaScript ต้องเรียก
/close
ชื่อทั้งสองต้องตรงกัน
⑦ GetParentResourceName คืออะไร
ใช้หาชื่อ Resource ที่กำลังเป็น Parent ของ NUI
เช่น
const resourceName =
GetParentResourceName();
แล้ว
fetch(
`https://${resourceName}/close`,
...
);
ดีกว่า Hardcode
fetch(
'https://my_inventory/close',
...
);
เพราะหาก Rename Resource Browser Callback ยังสามารถทำงานตามชื่อจริงของ Resource
⑧ ทำไมต้องใช้ https
Resource ที่ใช้
fx_version 'cerulean'
ใช้ Secure-context NUI
ดังนั้น Callback URL ปัจจุบันเป็น
https://
ไม่ใช่ Pattern รุ่นเก่า
http://
Script NUI เก่าที่ Migration มา cerulean จึงควรตรวจจุดนี้
⑨ Callback Name ต้องตรงกัน
Lua
RegisterNuiCallback(
'buyItem',
...
)
JavaScript ต้องเป็น
fetch(
`https://${GetParentResourceName()}/buyItem`,
...
);
ถ้า JavaScript เรียก
/buyitem
หรือ
/buy
ก็ไม่ใช่ชื่อเดียวกัน
ควรใช้ Naming Convention ให้สม่ำเสมอ
⑩ POST Data ส่งอย่างไร
JavaScript
body:
JSON.stringify({
item: 'water',
amount: 2
})
FiveM จะ Parse POST JSON ให้ Callback ตาม Runtime API
Lua
RegisterNuiCallback(
'buyItem',
function(data, cb)
print(
data.item,
data.amount
)
cb({
ok = true
})
end
)
⑪ data คืออะไร
data คือ Payload ที่ NUI ส่งเข้ามา
เช่น Browser
{
"item": "water",
"amount": 2
}
Lua จะเข้าถึง
data.item
และ
data.amount
ได้
⑫ ต้องตรวจ data ไหม
ต้อง
อย่าทำ
local amount =
data.amount
UseItem(
amount
)
ทันที
ควรตรวจ
local amount =
tonumber(
data.amount
)
if not amount then
cb({
ok = false,
error = 'invalid_amount'
})
return
end
NUI เป็น Client-side Input
จึงไม่ควรเชื่อถือโดยอัตโนมัติ
⑬ cb คืออะไร
cb ใช้ตอบ Response กลับไปยัง Browser Request
เช่น
cb({
ok = true
})
Browser
const response =
await fetch(...);
const data =
await response.json();
console.log(
data.ok
);
ถ้า Lua ส่ง
cb({
ok = true
})
JavaScript จะได้รับ Object ที่มี
data.ok
เป็น true
⑭ ต้องเรียก cb ทุกครั้งไหม
ควรเรียกทุกครั้ง
Cfx.re ระบุชัดว่าถ้า Callback ไม่คืนข้อมูลผ่าน cb Request จะ Timeout และ Error จะส่งกลับขึ้นไปยัง fetch() ฝั่ง UI
ดังนั้นแม้ไม่มีข้อมูลให้คืนก็ควรใช้
cb({})
หรือ
cb({
ok = true
})
⑮ ตัวอย่างที่ผิด
RegisterNuiCallback(
'close',
function(data, cb)
SetNuiFocus(
false,
false
)
-- ไม่มี cb()
end
)
UI อาจปิดได้
แต่ Browser Request ยังไม่ได้รับ Response
สุดท้าย fetch() อาจ Error/Timeout
⑯ ทุก Branch ต้องตอบ cb
ตัวอย่าง
RegisterNuiCallback(
'getItem',
function(data, cb)
if not data.item then
cb({
ok = false,
error = 'missing_item'
})
return
end
cb({
ok = true,
item = data.item
})
end
)
ทั้ง Error และ Success มี Response
นี่เป็น Pattern ที่ดี
⑰ JSON Encodable หมายถึงอะไร
Cfx.re ระบุว่า Data ที่ส่งเข้า/ออก NUI Callback ต้องสามารถ Encode เป็น JSON ได้
เหมาะกับ
string
number
boolean
array
object/table
null-like values
ไม่เหมาะกับ Runtime Objects ที่ Serialize ไม่ได้ เช่น Function Handle หรือ Native Object บางประเภท
⑱ อย่าส่ง Entity Handle Object ซับซ้อนเข้า Browser
ถ้าต้องการแสดง Vehicle
ส่งข้อมูล Presentation เช่น
cb({
id = vehicleId,
plate = plate,
model = model
})
ไม่ควรพยายามส่ง Runtime Structure ที่ Browser ใช้ไม่ได้
Browser ควรได้รับ Data Model ที่เรียบง่าย
⑲ RegisterNuiCallback ต่างจาก SendNUIMessage อย่างไร
จำง่าย ๆ
SendNUIMessage
FiveM Client
→ NUI
ส่วน
RegisterNuiCallback
NUI
→ FiveM Client
สองตัวจึงทำงานคนละทิศทาง
⑳ Flow สองทางแบบครบ
Client เปิด UI
SendNUIMessage({
action = 'open'
})
Browser รับ
window.addEventListener(
'message',
(event) => {
if (
event.data.action
=== 'open'
) {
// open UI
}
}
);
Player คลิกปิด
fetch(
`https://${GetParentResourceName()}/close`,
...
);
Client รับ
RegisterNuiCallback(
'close',
...
)
นี่คือ Communication Loop พื้นฐาน
㉑ RegisterNuiCallback ต่างจาก RegisterNetEvent อย่างไร
RegisterNuiCallback
NUI Browser
→ Client Resource
ส่วน RegisterNetEvent
Client ↔ Server Network Event
หรือ Network-safe Event ตาม Context
อย่าใช้แทนกัน
㉒ NUI Callback ไม่ใช่ Network Event
หาก NUI เรียก
buyItem
Browser Request เข้าสู่ Client Callback ก่อน
ไม่ได้หมายความว่า Server ได้รับ Event buyItem
ถ้าต้องให้ Server ซื้อ Item ต้องส่งต่อ
TriggerServerEvent(
'shop:buyItem',
item,
amount
)
㉓ NUI Callback ส่งต่อ Server อย่างไร
ตัวอย่าง
RegisterNuiCallback(
'buyItem',
function(data, cb)
local item =
tostring(
data.item or ''
)
local amount =
tonumber(
data.amount
)
if item == ''
or not amount then
cb({
ok = false,
error = 'invalid_input'
})
return
end
TriggerServerEvent(
'shop:buyItem',
item,
amount
)
cb({
ok = true,
accepted = true
})
end
)
แต่ accepted = true ตรงนี้หมายถึง Client รับ Request แล้ว
ไม่ได้หมายความว่า Server ซื้อสำเร็จแล้ว
㉔ Accepted กับ Completed ต่างกัน
นี่เป็น Architecture สำคัญ
NUI
→ ขอซื้อ
Client สามารถตอบ
Request accepted
แต่ผลจริงอาจเป็น
Server:
เงินไม่พอ
Item ไม่มี
ร้านปิด
Cooldown
Permission ไม่ผ่าน
ดังนั้นอย่าใช้ cb({success=true}) ก่อน Server ตัดสิน แล้วให้ UI คิดว่าซื้อสำเร็จ
㉕ ถ้าต้องการผลจาก Server ทำอย่างไร
มี 2 Pattern หลัก
Pattern A — Request แล้ว Update UI ภายหลัง
NUI
↓
Client callback
↓
Server event
↓
Server result event
↓
Client
↓
SendNUIMessage
↓
NUI
เหมาะกับระบบ Event-driven
Pattern B — ใช้ Server Callback/Promise Library
เช่น Framework หรือ Library Callback ที่สามารถ Await Response ได้
แต่ API นี้ขึ้นกับ Library เช่น ESX, QBCore, Qbox หรือ ox_lib
FiveM Core ไม่มี Universal ServerCallback() ชื่อเดียวสำหรับทุก Framework
㉖ ตัวอย่าง Event-driven Buy Item
Client
RegisterNuiCallback(
'buyItem',
function(data, cb)
TriggerServerEvent(
'shop:buyItem',
data.item,
data.amount
)
cb({
ok = true,
pending = true
})
end
)
Server ทำงาน
Validate
↓
Check money
↓
Check stock
↓
Complete purchase
แล้ว Server ส่ง
shop:purchaseResult
กลับ Client
Client จึง
SendNUIMessage({
action = 'purchaseResult',
success = true
})
ให้ Browser Update
㉗ Server ต้อง Validate ซ้ำไหม
ต้อง
แม้ Client Callback ตรวจ
amount > 0
แล้ว
Server ก็ต้องตรวจอีกครั้ง
เพราะ Client Runtime สามารถถูก Manipulate ได้
หลักคือ
Client validation
→ UX / early rejection
Server validation
→ Security / authority
㉘ อย่าให้ NUI เป็น Authority
ตัวอย่างที่ผิด
NUI ส่ง
{
"price": 1
}
แล้ว Server ใช้
price =
clientPrice
ซื้อรถราคา 1
นี่ไม่ปลอดภัย
Server ต้องมี Price Server-side
Vehicle Model
↓
Server Config/Database
↓
Official Price
㉙ อย่าให้ NUI ส่ง Permission
ผิด
{
"isAdmin": true
}
แล้ว Client/Server เชื่อ
Permission ต้องตรวจจาก Server Authority
เช่น ACE, Framework Permission หรือระบบ Server-side ที่กำหนด
㉚ อย่าให้ NUI ส่ง SQL
ห้ามสร้าง Generic Callback
RegisterNuiCallback(
'database',
function(data, cb)
TriggerServerEvent(
'database:run',
data.sql
)
cb({})
end
)
นี่เป็น Architecture ที่อันตรายอย่างมาก
NUI ควรส่ง Business Intent เช่น
garage:storeVehicle
shop:buyItem
phone:sendMessage
ไม่ใช่ SQL
㉛ ตัวอย่างปิด UI ที่ถูกต้อง
Client
local uiOpen =
false
local function closeUi()
uiOpen =
false
SetNuiFocus(
false,
false
)
SendNUIMessage({
action = 'close'
})
end
RegisterNuiCallback(
'close',
function(data, cb)
closeUi()
cb({
ok = true
})
end
)
Browser
await fetch(
`https://${GetParentResourceName()}/close`,
{
method: 'POST',
headers: {
'Content-Type':
'application/json; charset=UTF-8'
},
body:
JSON.stringify({})
}
);
㉜ กด ESC ปิด NUI ได้ไหม
ทำได้หลายแบบ
JavaScript สามารถฟัง Key Event
document.addEventListener(
'keydown',
async (event) => {
if (
event.key === 'Escape'
) {
await closeMenu();
}
}
);
แล้วให้ closeMenu() เรียก NUI Callback
เพื่อให้ Client เป็นผู้คืน Focus
㉝ อย่าซ่อน HTML อย่างเดียวตอนปิด
ไม่ควรมีแค่
menu.style.display =
'none';
เพราะ Client อาจยังมี
SetNuiFocus(
true,
true
)
อยู่
จึงเกิดอาการ
UI หาย
แต่ Mouse/Keyboard ยังติด NUI
ควรเรียก Client Callback เพื่อคืน Focus ด้วย
㉞ SetNuiFocus ควรอยู่ Client
ตัวอย่าง
RegisterNuiCallback(
'close',
function(data, cb)
SetNuiFocus(
false,
false
)
cb({
ok = true
})
end
)
NUI Browser ไม่ควรเป็นคนจัด Game Focus เอง
Client Runtime เป็นตัวควบคุม
㉟ ตัวอย่าง Form Submission
HTML
<input
id="firstname"
type="text"
>
<input
id="lastname"
type="text"
>
<button id="create">
สร้างตัวละคร
</button>
JavaScript
const firstName =
document.getElementById(
'firstname'
).value;
const lastName =
document.getElementById(
'lastname'
).value;
const response =
await fetch(
`https://${GetParentResourceName()}/createCharacter`,
{
method: 'POST',
headers: {
'Content-Type':
'application/json; charset=UTF-8'
},
body:
JSON.stringify({
firstName,
lastName
})
}
);
㊱ Validate Form ฝั่ง Client
RegisterNuiCallback(
'createCharacter',
function(data, cb)
local firstName =
tostring(
data.firstName or ''
)
local lastName =
tostring(
data.lastName or ''
)
if firstName == ''
or lastName == '' then
cb({
ok = false,
error = 'missing_name'
})
return
end
cb({
ok = true
})
end
)
แต่ Server ยังต้อง Validate ซ้ำหากข้อมูลนี้ถูก Persist
㊲ จำกัด Length ก่อนส่ง Server
เช่น
if #firstName > 50 then
cb({
ok = false,
error = 'firstname_too_long'
})
return
end
ช่วย UX และลด Payload ผิดปกติ
แต่ Security Rule หลักต้องอยู่ Server อีกชั้นหนึ่ง
㊳ Type Validation สำคัญไหม
สำคัญ
NUI อาจส่ง
{
"amount": "5"
}
เป็น String
Lua ควร
local amount =
tonumber(
data.amount
)
แล้วตรวจ
if not amount then
...
end
ไม่ควรสมมติ Type จาก UI เสมอ
㊴ Range Validation
เช่นจำนวน Item
if amount < 1
or amount > 100 then
cb({
ok = false,
error = 'invalid_amount'
})
return
end
แต่ Server ต้องมี Limit เดียวกันหรือเข้มกว่า
㊵ Return Error ให้ Browser อย่างไร
Lua
cb({
ok = false,
error = 'invalid_vehicle'
})
Browser
const result =
await response.json();
if (!result.ok) {
console.error(
result.error
);
}
ช่วยให้ Error Handling เป็นระบบ
㊶ ควรใช้ Error Code หรือ Error Message
แนะนำ Error Code
{
"ok": false,
"error": "not_enough_money"
}
แล้ว Browser แปลงเป็นข้อความ
const messages = {
not_enough_money:
'เงินไม่เพียงพอ'
};
ดีกว่า Server/Client ส่งข้อความ UI ภาษาไทยทุกจุด
ช่วยรองรับหลายภาษา
㊷ Response Schema ควรคงที่
ตัวอย่าง Pattern
{
"ok": true,
"data": {}
}
หรือ Error
{
"ok": false,
"error": "invalid_input"
}
ทำให้ Frontend จัดการง่ายกว่า Callback แต่ละตัวคืน Format คนละแบบ
㊸ Browser ควรใช้ try/catch
ตัวอย่าง
try {
const response =
await fetch(
`https://${GetParentResourceName()}/close`,
options
);
if (!response.ok) {
throw new Error(
`HTTP ${response.status}`
);
}
const result =
await response.json();
} catch (error) {
console.error(
'NUI request failed:',
error
);
}
ช่วย Debug Callback Timeout หรือ Request Error
㊹ fetch ค้างควรตรวจอะไร
ตรวจ
① Callback Name ตรงไหม?
② ใช้ https:// หรือไม่?
③ GetParentResourceName ถูกไหม?
④ Client Script Register Callback หรือยัง?
⑤ Resource Started ไหม?
⑥ Handler Error ก่อน cb หรือไม่?
⑦ ทุก Branch เรียก cb หรือไม่?
⑧ F8 มี Client Error ไหม?
⑨ NUI DevTools Network มี Error ไหม?
โดยเฉพาะข้อ cb
เพราะ Cfx.re ระบุว่าไม่คืน Callback จะทำ Request Timeout
㊺ Handler Error ก่อน cb จะเกิดอะไร
ตัวอย่าง
RegisterNuiCallback(
'test',
function(data, cb)
print(
data.user.name
)
cb({
ok = true
})
end
)
ถ้า
data.user == nil
Lua Error ก่อนถึง cb
Browser Request จึงอาจไม่มี Response
ควร Validate ก่อน Access Nested Data
㊻ Safe Nested Data
local user =
data.user
if type(user)
~= 'table' then
cb({
ok = false,
error = 'invalid_user'
})
return
end
local name =
tostring(
user.name or ''
)
ช่วยลด Runtime Error
㊼ Callback ชื่อซ้ำได้ไหม
ไม่ควรออกแบบ Resource ให้ Register Callback Name เดียวกันหลาย Handler โดยไม่เข้าใจ Runtime Behavior
ใช้ชื่อแต่ละ Action ให้ชัด
close
selectVehicle
buyItem
useItem
saveSettings
ภายใน Resource เดียว
㊽ ต้องใส่ Resource Prefix ใน Callback Name ไหม
NUI Callback URL ถูก Scope ด้วย Resource อยู่แล้ว
เช่น
https://my_garage/storeVehicle
ดังนั้น Callback ภายใน Resource สามารถใช้
storeVehicle
ได้โดยไม่จำเป็นต้องเขียน
my_garage:storeVehicle
เสมอไป
แต่ Naming Convention เลือกได้ตาม Project
㊾ RegisterNuiCallback กับ RegisterRawNuiCallback ต่างกันอย่างไร
FiveM มี Raw NUI Callback APIs สำหรับระดับต่ำกว่า
และมี UNREGISTER_RAW_NUI_CALLBACK สำหรับ Cleanup Handler แบบ Raw
แต่ Resource Lua ทั่วไปที่รับ JSON ควรใช้ RegisterNuiCallback ซึ่งจัด JSON Parsing/Response ให้สะดวกกว่า
ไม่ควรเลือก Raw API ถ้าไม่มี Requirement จริง
㊿ JavaScript Client Runtime ใช้ NUI Callback ได้ไหม
ได้
Cfx.re Documentation ปัจจุบันมีตัวอย่าง
RegisterNuiCallback(
'getItemInfo',
(data, cb) => {
if (
!itemCache[data.itemId]
) {
cb({
error:
'No such item!'
});
return;
}
cb(
itemCache[data.itemId]
);
}
);
หลักเหมือน Lua
data
+
cb
51 C# ใช้ NUI Callback ได้ไหม
ได้
Documentation ปัจจุบันมีตัวอย่าง C# ผ่าน
RegisterNuiCallback
กับ Dictionary/Object และ Callback Delegate
ดังนั้น NUI Callback ไม่ได้จำกัดเฉพาะ Lua
แต่บทความนี้ใช้ Lua เพราะเป็นภาษาที่พบมากใน FiveM Resources
52 NUI Callback เร็วไหม
การส่ง Browser → Client เป็น Local NUI Communication
แต่ Performance ยังขึ้นกับ
Payload Size
Callback Frequency
JSON Serialization
Browser JavaScript
Client Handler
อย่าใช้ Callback หลายร้อยครั้งต่อ Frame โดยไม่มีเหตุผล
53 Drag Slider ต้องยิง Callback ทุก Mouse Move ไหม
ไม่จำเป็นเสมอไป
เช่น Volume Slider สามารถ
update UI locally
ระหว่างลาก
แล้วส่ง Callback ตอน
change
mouseup
debounce
แทนการส่งทุก Pixel Movement
ช่วยลด Message Traffic
54 Search Box ควร Debounce
ไม่ควรทำ
พิมพ์ทุกตัวอักษร
→ Callback
→ Server
→ Database Query
ทันทีทุก Keystroke
ควรใช้ Debounce เช่น 200–300 ms ตาม UX
หรือ Filter Client-side ถ้าข้อมูลอยู่ใน NUI แล้ว
55 อย่าใช้ NUI Callback Query Database โดยตรงจาก Client
Flow ที่ควรเป็น
NUI
↓
Client
↓
Server
↓
Validate
↓
Database
Client Lua ไม่ควรถือ Database Credentials หรือ SQL Authority
ถ้า NUI กด Search Player ระบบสำคัญควรส่ง Request ไป Server
56 Resource Stop ต้องทำอะไรกับ NUI Callback
เมื่อ Resource Stop Runtime จะถูกหยุด
แต่สิ่งสำคัญที่ควร Cleanup คือ UI State เช่น
AddEventHandler(
'onClientResourceStop',
function(resourceName)
if resourceName ~=
GetCurrentResourceName() then
return
end
SetNuiFocus(
false,
false
)
end
)
เพื่อป้องกัน Focus ค้างหลัง Restart Resource
57 Debug NUI Callback ที่ไหน
ใช้ 3 จุดหลัก
NUI DevTools
→ Browser JS / Network
F8
→ Client Lua/JS Errors
FXServer Console
→ Server Event/Database Errors
ถ้า fetch() ไม่ตอบ เริ่มจาก DevTools Network + F8
ถ้า Client รับแล้วแต่ Server ไม่ตอบ ไปดู FXServer
58 ตัวอย่าง Resource RegisterNuiCallback แบบครบ
โครงสร้าง
com_garage/
├── fxmanifest.lua
├── client.lua
└── html/
├── index.html
└── app.js
fxmanifest.lua
fx_version 'cerulean'
game 'gta5'
author 'comsiam'
description 'FiveM NUI callback example'
version '1.0.0'
ui_page 'html/index.html'
files {
'html/index.html',
'html/app.js'
}
client_script 'client.lua'
client.lua
local uiOpen =
false
local function setUi(
state
)
uiOpen =
state
SetNuiFocus(
state,
state
)
SendNUIMessage({
action =
state
and 'open'
or 'close'
})
end
RegisterCommand(
'garageui',
function()
setUi(
true
)
end,
false
)
RegisterNuiCallback(
'close',
function(data, cb)
setUi(
false
)
cb({
ok = true
})
end
)
RegisterNuiCallback(
'selectVehicle',
function(data, cb)
local vehicleId =
tonumber(
data.vehicleId
)
if not vehicleId then
cb({
ok = false,
error =
'invalid_vehicle'
})
return
end
TriggerServerEvent(
'com_garage:selectVehicle',
vehicleId
)
cb({
ok = true,
pending = true
})
end
)
AddEventHandler(
'onClientResourceStop',
function(resourceName)
if resourceName ~=
GetCurrentResourceName() then
return
end
SetNuiFocus(
false,
false
)
end
)
app.js
async function nuiRequest(
eventName,
data = {}
) {
const response =
await fetch(
`https://${GetParentResourceName()}/${eventName}`,
{
method: 'POST',
headers: {
'Content-Type':
'application/json; charset=UTF-8'
},
body:
JSON.stringify(
data
)
}
);
if (!response.ok) {
throw new Error(
`NUI request failed: ${response.status}`
);
}
return response.json();
}
document
.getElementById('close')
.addEventListener(
'click',
async () => {
try {
await nuiRequest(
'close'
);
} catch (error) {
console.error(
error
);
}
}
);
async function selectVehicle(
vehicleId
) {
try {
const result =
await nuiRequest(
'selectVehicle',
{
vehicleId
}
);
if (!result.ok) {
console.error(
result.error
);
return;
}
console.log(
'Vehicle request sent'
);
} catch (error) {
console.error(
error
);
}
}
ตรงนี้สร้าง Helper
nuiRequest()
เพื่อไม่ต้องเขียน fetch() ซ้ำทุก Callback
59 Checklist RegisterNuiCallback
ตรวจ
① Resource ใช้ fx_version cerulean หรือไม่?
② ui_page ถูกหรือไม่?
③ JavaScript อยู่ใน files หรือไม่?
④ Browser ใช้ https:// หรือไม่?
⑤ ใช้ GetParentResourceName หรือไม่?
⑥ Callback Name ตรงกันหรือไม่?
⑦ ใช้ RegisterNuiCallback สำหรับ Code ใหม่หรือไม่?
⑧ data เป็น JSON-encodable หรือไม่?
⑨ Browser POST JSON ถูกหรือไม่?
⑩ Content-Type ถูกหรือไม่?
⑪ data ถูก Validate หรือไม่?
⑫ Type ถูกตรวจหรือไม่?
⑬ Range ถูกตรวจหรือไม่?
⑭ cb ถูกเรียกหรือไม่?
⑮ Error Branch มี cb หรือไม่?
⑯ Success Branch มี cb หรือไม่?
⑰ Handler Error ก่อน cb หรือไม่?
⑱ Browser ใช้ try/catch หรือไม่?
⑲ Response Schema สม่ำเสมอหรือไม่?
⑳ NUI Input ถูกมองเป็น Untrusted หรือไม่?
㉑ Server Validate ซ้ำหรือไม่?
㉒ Client ส่ง Price/Permission ไป Server แบบเชื่อถือหรือไม่?
㉓ Generic SQL Event มีหรือไม่?
㉔ SetNuiFocus คืนตอนปิดหรือไม่?
㉕ Resource Stop คืน Focus หรือไม่?
㉖ Callback ถูกยิงถี่เกินไปหรือไม่?
㉗ Search/Slider มี Debounce หรือไม่?
㉘ F8 มี Error หรือไม่?
㉙ NUI DevTools Network มี Timeout หรือไม่?
㉚ Test หลัง Restart Resource แล้วหรือยัง?
⑥⓪ Architecture RegisterNuiCallback สำหรับ Production
สมมติระบบ Shop
NUI
│
├── buyItem
├── close
└── search
Client
RegisterNuiCallback
│
├── Validate UI payload
├── UI focus/state
└── Forward business request
Server
RegisterNetEvent / Callback
│
├── Validate source
├── Validate item
├── Check price
├── Check money
├── Check stock
├── Update inventory
└── Persist database
Database
Persistent state
Flow ซื้อสินค้า
Player คลิก Buy
↓
Browser fetch()
↓
RegisterNuiCallback
↓
Client ตรวจ Payload ขั้นต้น
↓
Server Request
↓
Server ตรวจ Item
↓
Server หา Price เอง
↓
Server ตรวจเงิน
↓
Server Update Inventory
↓
Server Result
↓
Client
↓
SendNUIMessage
↓
Browser แสดงผล
นี่เป็น Boundary ที่ปลอดภัยกว่าการให้ Browser เป็นผู้ตัดสินผลธุรกรรม
ตัวอย่าง Callback ที่ไม่ปลอดภัย
RegisterNuiCallback(
'buyCar',
function(data, cb)
TriggerServerEvent(
'garage:buyCar',
data.model,
data.price
)
cb({
ok = true
})
end
)
ถ้า Server เชื่อ data.price
Player สามารถพยายามแก้ Price ใน Client ได้
Server ต้องรับเพียง Identifier ที่จำเป็น
เช่น
TriggerServerEvent(
'garage:buyCar',
data.model
)
แล้ว Server หา Price จาก Server-side Config เอง
Callback ที่ดีควรทำงานเล็กและชัดเจน
ไม่ควรมี Callback หนึ่งตัวทำทุกอย่าง
uiAction
→ buy
→ sell
→ delete
→ transfer
→ admin
ตามค่า data.action
แนะนำแยก
buyVehicle
sellVehicle
storeVehicle
transferVehicle
เพื่อให้อ่าน Security Boundary ง่าย
RegisterNuiCallback และการตั้งชื่อ
ตัวอย่างที่ดี
close
getVehicles
selectVehicle
storeVehicle
saveSettings
sendMessage
ชื่อควรสะท้อน Intent
หลีกเลี่ยง
doThing
action
callback1
test
execute
เพราะเมื่อ Resource ใหญ่จะ Debug ยาก
คำถามที่พบบ่อยเกี่ยวกับ FiveM RegisterNUICallback
FiveM RegisterNUICallback คืออะไร
ใช้รับ Request จาก NUI Browser เข้าสู่ Client Resource
RegisterNUICallback ยังใช้ได้ไหม
Cfx.re ระบุว่า API ชื่อเดิมยังคงไว้เพื่อ Backward Compatibility แต่ New Code ควรใช้ REGISTER_NUI_CALLBACK และ Lua Wrapper ปัจจุบัน RegisterNuiCallback
Code Lua ใหม่ควรเขียนอย่างไร
RegisterNuiCallback(
'close',
function(data, cb)
cb({
ok = true
})
end
)
NUI เรียก Callback อย่างไร
ผ่าน fetch()
fetch(
`https://${GetParentResourceName()}/close`,
...
);
ใช้ http ได้ไหม
Resource cerulean ใช้ Secure-context NUI และ Callback URL ควรใช้ https://
data คืออะไร
Payload JSON ที่ Browser POST เข้ามา
cb คืออะไร
Function สำหรับส่ง Response กลับ Browser
ต้อง cb ทุกครั้งไหม
ใช่ ควรตอบทุก Request ไม่เช่นนั้น Request จะ Timeout
ไม่มีข้อมูลคืนใช้ cb อะไร
cb({})
หรือ
cb({
ok = true
})
JavaScript ต้อง JSON.stringify ไหม
สำหรับ POST Object ตาม Pattern ปัจจุบันใช้ JSON.stringify(...)
GetParentResourceName คืออะไร
คืนชื่อ Resource ปัจจุบันให้ NUI Browser
Hardcode Resource Name ได้ไหม
ทำได้แต่ไม่แนะนำ เพราะ Rename Resource แล้ว Callback URL จะพัง
RegisterNuiCallback เป็น Server API ไหม
NUI Callback ใช้เชื่อม NUI กับ Client Runtime
ต้องไป Server ทำอย่างไร
Client ใช้ Network Event หรือ Server Callback Pattern ที่เหมาะสม
Client Validation พอไหม
ไม่ Server ต้อง Validate Input ที่มีผลต่อ Gameplay/Economy/Permission ซ้ำ
NUI ส่ง Price ให้ Server ได้ไหม
ส่งได้ในเชิงข้อมูล แต่ Serverไม่ควรเชื่อเป็น Authority ควรหา Price จริงเอง
NUI ส่ง Admin Permission ได้ไหม
ไม่ควรใช้เป็นหลักฐาน Permission
NUI ต่อ Database โดยตรงได้ไหม
ไม่ควร
NUI Callback ใช้กับ JavaScript Client ได้ไหม
ได้
ใช้กับ C# ได้ไหม
ได้
Callback Data ต้องเป็นแบบไหน
ต้อง JSON-encodable ตาม Cfx.re Documentation
Callback Return ต้อง JSON-encodable ไหม
ใช่ Data ที่ส่งกลับผ่าน cb ต้องอยู่ในรูปที่ NUI สามารถ Serialize/Parse ได้
fetch Timeout แก้อย่างไร
ตรวจ Callback Name, Client Error และโดยเฉพาะว่าทุก Code Path เรียก cb
UI ปิดแล้ว Cursor ค้างแก้อย่างไร
เรียก
SetNuiFocus(
false,
false
)
ใน Client Callback และ Cleanup ตอน Resource Stop
RegisterNuiCallback ต่างจาก SendNUIMessage อย่างไร
RegisterNuiCallback รับ Browser→Client ส่วน SendNUIMessage ส่ง Client→Browser
RegisterNuiCallback ต่างจาก RegisterNetEvent อย่างไร
NUI Callback เชื่อม Browser กับ Client ส่วน Network Event ใช้สื่อสารผ่าน FiveM Network Context
ใช้ Callback ทุก Mouse Move ได้ไหม
ทำได้แต่ไม่ควรหากไม่มีเหตุผล ควร Debounce/Throttle เพื่อ Performance
สรุป FiveM RegisterNUICallback คืออะไร ใช้อย่างไร
RegisterNUICallback/RegisterNuiCallback คือกลไกที่ทำให้ JavaScript ใน FiveM NUI ส่งข้อมูลกลับเข้าสู่ Client Script ได้
Flow หลักคือ
Browser
↓
fetch()
↓
https://resource/callbackName
↓
RegisterNuiCallback
↓
data
↓
Client Logic
↓
cb(...)
↓
Browser Response
ตัวอย่าง Browser
const response =
await fetch(
`https://${GetParentResourceName()}/close`,
{
method: 'POST',
headers: {
'Content-Type':
'application/json; charset=UTF-8'
},
body:
JSON.stringify({})
}
);
const result =
await response.json();
Client
RegisterNuiCallback(
'close',
function(data, cb)
SetNuiFocus(
false,
false
)
cb({
ok = true
})
end
)
สิ่งที่ต้องจำมากที่สุดมี 5 ข้อ
① Code ใหม่ใช้ RegisterNuiCallback/REGISTER_NUI_CALLBACK
② cerulean ใช้ https://
③ ใช้ GetParentResourceName()
④ data จาก NUI ต้องถือว่าไม่น่าเชื่อถือ
⑤ ต้องตอบ cb ทุก Request
หาก Callback ต้องทำ Business Logic สำคัญ เช่นซื้อรถหรือใช้ Item
Architecture ควรเป็น
NUI
↓
Client Callback
↓
Server
↓
Validate
↓
Execute
↓
Result
↓
Client
↓
NUI
ไม่ใช่
NUI
↓
บอกผลสำคัญ
↓
Server เชื่อทันที
สำหรับผู้ที่พัฒนา FiveM กับ comsiam จุดสำคัญของ NUI Callback ไม่ได้อยู่แค่ทำให้ Button กดแล้วทำงาน แต่ต้องทำให้ทุก Request มี Response, Input ถูก Validate และ Business Authority สำคัญไม่หลุดมาอยู่ฝั่ง Browser
หลักสำคัญจาก comsiam คือ NUI Callback เป็นสะพานระหว่าง Frontend กับ FiveM Client ไม่ใช่ช่องทางลัดข้าม Security Layer ของ Server เมื่อ Action มีผลต่อเงิน Inventory Vehicle Permission หรือ Database ให้ Server เป็นผู้ตัดสินเสมอ
หัวข้อถัดไปคือ FiveM SendNUIMessage และ SetNuiFocus ใช้อย่างไร ซึ่งจะเจาะทิศทาง Client → Browser โดยตรง ตั้งแต่การเปิด/ปิด UI, ส่ง Object/Array, อัปเดต HUD, Focus Keyboard/Mouse, Focus ค้าง, Resource Restart และวิธีลด NUI Message ที่ยิงถี่เกินไป
Comments
Post a Comment