diff --git a/imports/callback/client.lua b/imports/callback/client.lua index 3f0d52b..db739b0 100644 --- a/imports/callback/client.lua +++ b/imports/callback/client.lua @@ -5,9 +5,12 @@ local callbackTimeout = GetConvarInt('ox:callbackTimeout', 300000) RegisterNetEvent(cbEvent:format(cache.resource), function(key, ...) local cb = pendingCallbacks[key] + + if not cb then return end + pendingCallbacks[key] = nil - return cb and cb(...) + cb(...) end) ---@param event string @@ -41,12 +44,19 @@ local function triggerServerCallback(_, event, delay, cb, ...) key = ('%s:%s'):format(event, math.random(0, 100000)) until not pendingCallbacks[key] + TriggerServerEvent('ox_lib:validateCallback', event, cache.resource, key) TriggerServerEvent(cbEvent:format(event), cache.resource, key, ...) ---@type promise | false local promise = not cb and promise.new() pendingCallbacks[key] = function(response, ...) + if response == 'cb_invalid' then + response = ("callback '%s' does not exist"):format(event) + + return promise and promise:reject(response) or error(response) + end + response = { response, ... } if promise then @@ -113,7 +123,11 @@ local pcall = pcall ---Registers an event handler and callback function to respond to server requests. ---@diagnostic disable-next-line: duplicate-set-field function lib.callback.register(name, cb) - RegisterNetEvent(cbEvent:format(name), function(resource, key, ...) + event = cbEvent:format(name) + + lib.setValidCallback(name, true) + + RegisterNetEvent(event, function(resource, key, ...) TriggerServerEvent(cbEvent:format(resource), key, callbackResponse(pcall(cb, ...))) end) end diff --git a/imports/callback/server.lua b/imports/callback/server.lua index 127c3bf..41eb346 100644 --- a/imports/callback/server.lua +++ b/imports/callback/server.lua @@ -4,9 +4,12 @@ local callbackTimeout = GetConvarInt('ox:callbackTimeout', 300000) RegisterNetEvent(cbEvent:format(cache.resource), function(key, ...) local cb = pendingCallbacks[key] + + if not cb then return end + pendingCallbacks[key] = nil - return cb and cb(...) + cb(...) end) ---@param _ any @@ -24,12 +27,19 @@ local function triggerClientCallback(_, event, playerId, cb, ...) key = ('%s:%s:%s'):format(event, math.random(0, 100000), playerId) until not pendingCallbacks[key] + TriggerClientEvent('ox_lib:validateCallback', playerId, event, cache.resource, key) TriggerClientEvent(cbEvent:format(event), playerId, cache.resource, key, ...) ---@type promise | false local promise = not cb and promise.new() pendingCallbacks[key] = function(response, ...) + if response == 'cb_invalid' then + response = ("callback '%s' does not exist"):format(event) + + return promise and promise:reject(response) or error(response) + end + response = { response, ... } if promise then @@ -96,7 +106,11 @@ local pcall = pcall ---Registers an event handler and callback function to respond to client requests. ---@diagnostic disable-next-line: duplicate-set-field function lib.callback.register(name, cb) - RegisterNetEvent(cbEvent:format(name), function(resource, key, ...) + event = cbEvent:format(name) + + lib.setValidCallback(name, true) + + RegisterNetEvent(event, function(resource, key, ...) TriggerClientEvent(cbEvent:format(resource), source, key, callbackResponse(pcall(cb, source, ...))) end) end diff --git a/package/client/resource/callback/index.ts b/package/client/resource/callback/index.ts index 23dcb09..e64fd62 100644 --- a/package/client/resource/callback/index.ts +++ b/package/client/resource/callback/index.ts @@ -5,9 +5,12 @@ const callbackTimeout = GetConvarInt('ox:callbackTimeout', 300000); onNet(`__ox_cb_${cache.resource}`, (key: string, ...args: any) => { const resolve = pendingCallbacks[key]; + + if (!resolve) return; + delete pendingCallbacks[key]; - return resolve && resolve(...args); + resolve(args); }); const eventTimers: Record = {}; @@ -37,16 +40,23 @@ export function triggerServerCallback( key = `${eventName}:${Math.floor(Math.random() * (100000 + 1))}`; } while (pendingCallbacks[key]); + emitNet(`ox_lib:validateCallback`, eventName, cache.resource, key); emitNet(`__ox_cb_${eventName}`, cache.resource, key, ...args); return new Promise((resolve, reject) => { - pendingCallbacks[key] = resolve; + pendingCallbacks[key] = (args) => { + if (args[0] === 'cb_invalid') reject(`callback '${eventName} does not exist`); + + resolve(args); + }; setTimeout(reject, callbackTimeout, `callback event '${key}' timed out`); }); } export function onServerCallback(eventName: string, cb: (...args: any[]) => any) { + exports.ox_lib.setValidCallback(eventName, true) + onNet(`__ox_cb_${eventName}`, async (resource: string, key: string, ...args: any[]) => { let response: any; diff --git a/package/server/resource/callback/index.ts b/package/server/resource/callback/index.ts index 6b24fa6..9bf3a94 100644 --- a/package/server/resource/callback/index.ts +++ b/package/server/resource/callback/index.ts @@ -5,9 +5,12 @@ const callbackTimeout = GetConvarInt('ox:callbackTimeout', 300000); onNet(`__ox_cb_${cache.resource}`, (key: string, ...args: any) => { const resolve = pendingCallbacks[key]; + + if (!resolve) return; + delete pendingCallbacks[key]; - return resolve && resolve(...args); + resolve(args); }); export function triggerClientCallback( @@ -21,16 +24,23 @@ export function triggerClientCallback( key = `${eventName}:${Math.floor(Math.random() * (100000 + 1))}:${playerId}`; } while (pendingCallbacks[key]); + emitNet(`ox_lib:validateCallback`, playerId, eventName, cache.resource, key); emitNet(`__ox_cb_${eventName}`, playerId, cache.resource, key, ...args); return new Promise((resolve, reject) => { - pendingCallbacks[key] = resolve; + pendingCallbacks[key] = (args) => { + if (args[0] === 'cb_invalid') reject(`callback '${eventName} does not exist`); + + resolve(args); + }; setTimeout(reject, callbackTimeout, `callback event '${key}' timed out`); }); } export function onClientCallback(eventName: string, cb: (playerId: number, ...args: any[]) => any) { + exports.ox_lib.setValidCallback(eventName, true) + onNet(`__ox_cb_${eventName}`, async (resource: string, key: string, ...args: any[]) => { const src = source; let response: any; diff --git a/resource/callbacks/shared.lua b/resource/callbacks/shared.lua new file mode 100644 index 0000000..aea1272 --- /dev/null +++ b/resource/callbacks/shared.lua @@ -0,0 +1,54 @@ +local registeredCallbacks = {} + +AddEventHandler('onResourceStop', function(resourceName) + if cache.resource == resourceName then return end + + for callbackName, resource in pairs(registeredCallbacks) do + if resource == resourceName then + registeredCallbacks[callbackName] = nil + end + end +end) + +---For internal use only. +---Sets a callback event as registered to a specific resource, preventing it from +---being overwritten. Any unknown callbacks will return an error to the caller. +---@param callbackName string +---@param isValid boolean +function lib.setValidCallback(callbackName, isValid) + local resourceName = GetInvokingResource() or cache.resource + local callbackResource = registeredCallbacks[callbackName] + + if callbackResource then + if not isValid then + callbackResource[callbackName] = nil + return + end + + if callbackResource == resourceName then return end + + error(("cannot overwrite callback '%s' owned by resource '%s'"):format(callbackName, callbackResource)) + end + + lib.print.verbose(("set valid callback '%s' for resource '%s'"):format(callbackName, resourceName)) + + registeredCallbacks[callbackName] = resourceName +end + +function lib.isCallbackValid(callbackName) + return registeredCallbacks[callbackName] == GetInvokingResource() or cache.resource +end + +local cbEvent = '__ox_cb_%s' + +RegisterNetEvent('ox_lib:validateCallback', function(callbackName, invokingResource, key) + if registeredCallbacks[callbackName] then return end + + local event = cbEvent:format(invokingResource) + + if cache.game == 'fxserver' then + return TriggerClientEvent(event, source, key, 'cb_invalid') + end + + TriggerServerEvent(event, key, 'cb_invalid') +end)