Crie primeiro um pequeno aplicativo NUI com aparência de telefone para aprender o ciclo completo de abertura, mensagem, callback e fechamento. Este exemplo mostra uma nota de sessão que permanece enquanto o recurso está em execução. Ele não tem chamadas, serviço de mensagens, banco de dados nem dependência de framework; adicionar essas funções exige autorização e persistência separadas no servidor.
Crie três arquivos
Crie resources/[local]/tutorial_phone/ com fxmanifest.lua, client.lua e index.html. O exemplo foi desenvolvido para o ambiente de execução de recursos gta5 do GTA V Legacy. Consulte as orientações de migração para Enhanced do Cfx.re antes de adaptá-lo a uma instalação 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>
Iniciar e verificar
Adicione ensure tutorial_phone à configuração ativa do servidor. No servidor de teste, execute refresh e ensure tutorial_phone, conecte-se e execute tutorial_phone no F8. O aplicativo deve abrir e colocar o foco no campo de notas.
Digite uma nota, feche pelo botão e abra novamente. A nota deve permanecer durante esta sessão do recurso. Teste Escape, abertura e fechamento repetidos e a parada do recurso enquanto estiver aberto; a entrada do jogo deve ser liberada. Reconectar ou reiniciar o recurso cria uma nova sessão da interface e apaga a nota.
Inspecione os limites da NUI
O navegador chama o callback do recurso pelo nome real do recurso que o contém. O Lua sempre responde por meio de cb, e o navegador trata as solicitações que falham. Siga as instruções de depuração de NUI do Cfx.re para inspecionar o console e as solicitações de callback no seu cliente.
Adicione funções reais de telefone de forma deliberada
Um recurso de telefone existente pode disponibilizar seu próprio SDK de aplicativos; use essa integração compatível em vez de executar um segundo telefone completo. Contatos persistentes precisam de verificações de propriedade do personagem no servidor, limites de tamanho validados, consultas parametrizadas e uma migração para o esquema instalado.
Chamadas reais precisam da integração compatível do recurso de voz escolhido. Nunca aceite um identificador arbitrário de personagem enviado pela interface como prova de acesso, nem afirme que um formulário de contatos é um sistema de telefone completo e seguro.