From a7163bba722db5129de185e06bae285aa3d1b39e Mon Sep 17 00:00:00 2001 From: Murai Dev <89431075+mur4i@users.noreply.github.com> Date: Mon, 5 Oct 2026 07:07:25 -0300 Subject: [PATCH] fix(bluetooth): keep onConnect/onDisconnect passed through the export A function handed to registerBluetoothDevice from another resource arrives as a function reference (a table with __call), so the type(...) == 'function' check dropped it and the owning script never heard about connections. Accept any callable value. --- server/bluetooth/registry.lua | 4 ++-- shared/bluetooth.lua | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/server/bluetooth/registry.lua b/server/bluetooth/registry.lua index e28f1f2ef..cf006b44d 100644 --- a/server/bluetooth/registry.lua +++ b/server/bluetooth/registry.lua @@ -141,10 +141,10 @@ end ---Runs an owning script's callback without letting it take the caller down with it. A third-party ---handler that errors must never stall the reconnect tick or abort a pairing. ----@param fn function|nil +---@param fn function|table|nil ---@param ... any local function safely(fn, ...) - if type(fn) ~= 'function' then return end + if not bt.isCallable(fn) then return end local ok, err = pcall(fn, ...) if not ok then print(('^1[sd-phone:bluetooth]^0 device callback errored: %s'):format(tostring(err))) diff --git a/shared/bluetooth.lua b/shared/bluetooth.lua index 85c8c6fa1..c17cc774c 100644 --- a/shared/bluetooth.lua +++ b/shared/bluetooth.lua @@ -75,6 +75,17 @@ function bluetooth.merge(device, patch) return def end +---Whether a value can be called. A function handed over through an export arrives as a function +---reference: a table with a __call metamethod, not a Lua function. +---@param value any +---@return boolean +function bluetooth.isCallable(value) + if type(value) == 'function' then return true end + if type(value) ~= 'table' then return false end + local meta = getmetatable(value) + return type(meta) == 'table' and meta.__call ~= nil +end + ---Validates a registration, returning the fields worth keeping. Rejects rather than repairs, so a ---script learns its device is wrong instead of finding it silently unreachable. ---@param def table registration passed to registerBluetoothDevice @@ -106,8 +117,8 @@ function bluetooth.validate(def) entity = entity, range = range, maxConnections = max, - onConnect = type(def.onConnect) == 'function' and def.onConnect or nil, - onDisconnect = type(def.onDisconnect) == 'function' and def.onDisconnect or nil, + onConnect = bluetooth.isCallable(def.onConnect) and def.onConnect or nil, + onDisconnect = bluetooth.isCallable(def.onDisconnect) and def.onDisconnect or nil, } end