Explorer
KNOW-PAT-093

FiveM - Node.js CFX Runtime Bridge : execution JS server-side avec Lua interop

Domaine
fivem
Type
pattern
Priorité
P1

Parent : [[INDEX-FIVEM]]

FiveM - Node.js CFX Runtime Bridge : exécution JS server-side avec Lua interop

Problème

FiveM supporte nativement Lua server-side, mais certains algorithmes (cryptographie, parsing complexe, intégrations API externes) sont plus faciles à écrire en JavaScript/Node.js.

Solution

Utiliser le runtime Node.js natif de CFX FiveM (node_version) pour exécuter du JS server-side avec accès aux natives CFX via global et bridge Lua/JS via exports.

-- fxmanifest.lua
fx_version 'cerulean'
game 'gta5'
node_version '22'
server_scripts {
    '@oxmysql/lib/MySQL.js',
    'server/server.js'
}
files {
    'web/**/*',
    'data/okok_payphone_ring.dat54.rel',
    'audiodirectory/okokphone_sounds.awc'
}
data_file 'AUDIO_WAVEPACK' 'audiodirectory'
data_file 'AUDIO_SOUNDDATA' 'data/okok_payphone_ring.dat'
// server.js
const { MySQL } = require('@oxmysql/lib/MySQL.js');

// Accès aux natives CFX via global
global.onNet('phone:sendMessage', (target, message) => {
    const src = global.source;
    
    // Appel Lua depuis JS via exports
    const result = exports['my_lua_resource'].ValidateMessage(src, message);
    
    if (result.valid) {
        MySQL.insert('INSERT INTO phone_messages (sender, receiver, message) VALUES (?, ?, ?)', [
            src, target, message
        ]);
        
        global.emitNet('phone:receiveMessage', target, {
            sender: src,
            message: message,
            timestamp: Date.now()
        });
    }
});

// Presigned URLs pour uploads (S3/Cloudflare R2)
const generatePresignedUrl = (filename, expiry = 3600) => {
    // Logique de signature AWS v4
    return `https://cdn.example.com/${filename}?X-Amz-Algorithm=...`;
};

Pourquoi

  • Node.js v22 supporte async/await natif, fetch, crypto, streams
  • Permet d'utiliser des packages npm (axios, crypto-js, etc.)
  • Bridge transparent avec les resources Lua existantes via exports

Quand l'utiliser

  • Intégrations API externes complexes (OAuth, webhooks, uploads)
  • Traitement audio/image (FFmpeg, sharp) via child_process
  • Algorithmes cryptographiques ou de parsing (JWT, XML, JSON schema)

Quand NE PAS l'utiliser

  • Logique simple (event forwarding, CRUD basique) → Lua suffit
  • Si la ressource n'a pas besoin de npm packages

Exemples

Correct

// Server JS avec gestion d'erreurs et rate limiting
const rateLimits = new Map();

global.onNet('phone:uploadMedia', async (data) => {
    const src = global.source;
    const now = Date.now();
    
    if (rateLimits.has(src) && now - rateLimits.get(src) < 5000) {
        return global.emitNet('phone:error', src, 'Rate limit');
    }
    rateLimits.set(src, now);
    
    try {
        const url = generatePresignedUrl(data.filename);
        global.emitNet('phone:uploadUrl', src, { url, fields: data.fields });
    } catch (err) {
        console.error('Upload error:', err);
        global.emitNet('phone:error', src, 'Upload failed');
    }
});

Incorrect

// JS sans gestion d'erreurs → crash du thread Node.js
global.onNet('phone:send', (data) => {
    MySQL.insert('INSERT ...', [data.sender, data.message]); // Crash si data undefined
    global.emitNet('phone:ok', global.source);
});

Références

  • okokPhone/fxmanifest.lua L15-28
  • okokPhone/server/server.js
  • CFX Docs : Node.js runtime