Commencez par créer une petite application NUI de type téléphone pour apprendre l'intégralité du cycle de vie : ouverture, message, callback et fermeture. Cet exemple affiche une note de session qui persiste pendant l'exécution de la ressource. Il n'a ni appels, ni service de messagerie, ni base de données, ni dépendance à un framework ; les ajouter nécessite une autorisation côté serveur et une persistance distinctes.
Créez trois fichiers
Créez resources/[local]/tutorial_phone/ avec fxmanifest.lua, client.lua et index.html. Cet exemple cible l’environnement d’exécution des ressources gta5 de GTA V Legacy. Consultez les consignes de migration vers Enhanced de Cfx.re avant de l’adapter à un déploiement Enhanced.
fxmanifest.lua:
fx_version 'cerulean'
game 'gta5'
ui_page 'index.html'
files { 'index.html' }
client_script 'client.lua'
client.lua:
local visible = false
local ready = false
local function closePhone()
visible = false
SetNuiFocus(false, false)
SendNUIMessage({ action = 'close' })
end
RegisterCommand('tutorial_phone', function()
if not ready then
print('Phone UI is loading; try again shortly.')
return
end
if visible then closePhone(); return end
visible = true
SetNuiFocus(true, true)
SendNUIMessage({ action = 'open' })
end, false)
RegisterNUICallback('ready', function(_, cb)
ready = true
cb({ ok = true })
end)
RegisterNUICallback('close', function(_, cb)
closePhone()
cb({ ok = true })
end)
AddEventHandler('onClientResourceStop', function(name)
if name == GetCurrentResourceName() and visible then
SetNuiFocus(false, false)
end
end)
index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Session note</title>
<style>
body { margin: 0; background: transparent; font: 18px system-ui; }
#phone { position: fixed; right: 4vw; bottom: 4vh; width: min(320px, 85vw);
padding: 24px; box-sizing: border-box; color: #fff; background: #18222d;
border-radius: 20px; }
[hidden] { display: none !important; }
textarea { box-sizing: border-box; width: 100%; min-height: 140px; font: inherit;
margin: 12px 0; padding: 8px; }
button { font: inherit; padding: 10px 16px; cursor: pointer; }
:focus-visible { outline: 3px solid #67d7ff; outline-offset: 3px; }
</style>
</head>
<body>
<section id="phone" role="dialog" aria-label="Session note" hidden>
<h1>Session note</h1>
<label for="note">What will your character do next?</label>
<textarea id="note" maxlength="500"></textarea>
<p>This note clears when the resource restarts.</p>
<button id="close" type="button">Close</button>
<p id="error" role="status"></p>
</section>
<script>
const phone = document.getElementById('phone');
const note = document.getElementById('note');
const error = document.getElementById('error');
async function call(name) {
const response = await fetch(`https://${GetParentResourceName()}/${name}`, {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}'
});
if (!response.ok) throw new Error('NUI callback failed');
return response.json();
}
window.addEventListener('message', ({ data }) => {
if (data?.action === 'open') {
phone.hidden = false; error.textContent = ''; note.focus();
} else if (data?.action === 'close') {
phone.hidden = true;
}
});
async function close() {
try { await call('close'); }
catch { error.textContent = 'Could not close. Check the F8 console.'; }
}
document.getElementById('close').addEventListener('click', close);
window.addEventListener('keydown', event => {
if (event.key === 'Escape' && !phone.hidden) { event.preventDefault(); close(); }
});
call('ready').catch(() => console.error('Phone readiness callback failed.'));
</script>
</body>
</html>
Démarrer et vérifier
Ajouter ensure tutorial_phone à la configuration serveur active. Sur votre serveur de test, exécutez refresh et ensure tutorial_phone, connectez-vous, puis exécutez tutorial_phone dans F8. L’application doit s’ouvrir et placer le focus sur son champ de note.
Saisissez une note, fermez avec le bouton puis rouvrez. La note doit être conservée pendant cette session de la ressource. Testez Escape, des ouvertures/fermetures répétées et l’arrêt de la ressource alors qu’elle est ouverte ; les commandes du jeu doivent être libérées. Une reconnexion ou un redémarrage de la ressource crée une nouvelle session d’UI et efface la note.
Inspecter la frontière NUI
Le navigateur appelle le callback de la ressource par son nom de parent réel. Lua répond toujours via cb, et le navigateur gère une requête échouée. Suivez les instructions de débogage NUI de Cfx.re pour inspecter la console et les requêtes de callback sur votre client.
Ajoutez de véritables fonctionnalités de téléphone de manière délibérée
Une ressource de téléphone existante peut exposer son propre SDK d’applications ; utilisez cette intégration prise en charge plutôt que de faire tourner un second téléphone complet. Des contacts persistants nécessitent des vérifications de propriété du personnage côté serveur, des longueurs validées, des requêtes paramétrées et une migration pour le schéma installé.
Les véritables appels nécessitent l’intégration prise en charge de la ressource vocale choisie. N’acceptez jamais un identifiant de personnage arbitraire provenant de l’UI comme preuve d’accès, et n’affirmez pas qu’un formulaire de contacts constitue un système téléphonique complet et sécurisé.