From c95775fb2ce5dd635188a192a20d336818391dc6 Mon Sep 17 00:00:00 2001 From: "Leon.Schmidt" Date: Tue, 11 Aug 2026 18:39:11 +0200 Subject: [PATCH] FIX - defer custom app lifecycle hooks --- sky_phone/source/client/custom_apps.lua | 44 ++++++++++++++++++++----- tests/custom_app_compat_client.lua | 13 +++++++- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/sky_phone/source/client/custom_apps.lua b/sky_phone/source/client/custom_apps.lua index c098f70..87b4222 100644 --- a/sky_phone/source/client/custom_apps.lua +++ b/sky_phone/source/client/custom_apps.lua @@ -609,14 +609,40 @@ local function invoke_hook(app, hook_name, payload) return false end -local function clear_active_app(invoke_close_hook) +local function invoke_or_defer_hook(app, hook_name, payload, deferred_hooks) + if not app.catalog.compatibility then + return invoke_hook(app, hook_name, payload) + end + + deferred_hooks[#deferred_hooks + 1] = { + app = app, + hookName = hook_name, + payload = payload, + } + return true +end + +local function invoke_deferred_hooks(deferred_hooks) + for index = 1, #deferred_hooks do + local deferred_hook = deferred_hooks[index] + CreateThread(function() + invoke_hook(deferred_hook.app, deferred_hook.hookName, deferred_hook.payload) + end) + end +end + +local function clear_active_app(invoke_close_hook, deferred_hooks) if not active_app_id then return end local app = apps_by_id[active_app_id] if app and invoke_close_hook then - invoke_hook(app, "onClose") + if deferred_hooks then + invoke_or_defer_hook(app, "onClose", nil, deferred_hooks) + else + invoke_hook(app, "onClose") + end end queued_messages[active_app_id] = nil active_app_id = nil @@ -1383,17 +1409,18 @@ RegisterNUICallback("custom-app:lifecycle", function(data, cb) return end + local deferred_hooks = {} local hook_success = true if lifecycle_event == "install" then - hook_success = invoke_hook(app, "onInstall", lifecycle_payload) + hook_success = invoke_or_defer_hook(app, "onInstall", lifecycle_payload, deferred_hooks) elseif lifecycle_event == "open" then if active_app_id and active_app_id ~= data.appId then - clear_active_app(true) + clear_active_app(true, deferred_hooks) end active_app_id = data.appId active_app_ready = false queued_messages[data.appId] = {} - hook_success = invoke_hook(app, "onOpen", lifecycle_payload) + hook_success = invoke_or_defer_hook(app, "onOpen", lifecycle_payload, deferred_hooks) elseif active_app_id ~= data.appId then cb({ success = false, error = "app_not_active" }) return @@ -1408,17 +1435,18 @@ RegisterNUICallback("custom-app:lifecycle", function(data, cb) for index = 1, #pending do deliver_custom_app_message(data.appId, pending[index].payload) end - hook_success = invoke_hook(app, "onReady", lifecycle_payload) + hook_success = invoke_or_defer_hook(app, "onReady", lifecycle_payload, deferred_hooks) else - hook_success = invoke_hook(app, "onClose", lifecycle_payload) + hook_success = invoke_or_defer_hook(app, "onClose", lifecycle_payload, deferred_hooks) clear_active_app(false) end - if not hook_success and not app.catalog.compatibility then + if not hook_success then cb({ success = false, error = "hook_failed" }) return end cb({ success = true }) + invoke_deferred_hooks(deferred_hooks) end) AddEventHandler("onClientResourceStop", function(resource_name) diff --git a/tests/custom_app_compat_client.lua b/tests/custom_app_compat_client.lua index f4a5027..558184d 100644 --- a/tests/custom_app_compat_client.lua +++ b/tests/custom_app_compat_client.lua @@ -153,16 +153,21 @@ end assert(yseries_get_data_loaded(), "YSeries must see the compatibility provider as loaded") invoking_resource = "lb_app" +local lifecycle_response_delivered = false +local install_hook_after_response = false +local open_hook_after_response = false local lb_success, lb_error = lb_add_custom_app({ identifier = "dispatch", name = "Dispatch", description = "Dispatch terminal", ui = "ui/index.html", onInstall = make_cfx_function_reference("install-hook", function() + install_hook_after_response = lifecycle_response_delivered error("vendor install failure") end), onOpen = make_cfx_function_reference("open-hook", function() - error("vendor open failure") + open_hook_after_response = lifecycle_response_delivered + error(5) end), }) assert(lb_success and lb_error == nil, "LB AddCustomApp must register through the shared export") @@ -170,14 +175,20 @@ assert(lb_success and lb_error == nil, "LB AddCustomApp must register through th local lifecycle_callback = assert(registered_nui_callbacks["custom-app:lifecycle"]) local lifecycle_response SkyPhoneApps.SetPhoneOpen(true) +lifecycle_response_delivered = false lifecycle_callback({ appId = "dispatch", event = "install" }, function(response) lifecycle_response = response + lifecycle_response_delivered = true end) assert(lifecycle_response.success, "a vendor install hook failure must not fail installation") +assert(install_hook_after_response, "a vendor install hook must run after the NUI response") +lifecycle_response_delivered = false lifecycle_callback({ appId = "dispatch", event = "open" }, function(response) lifecycle_response = response + lifecycle_response_delivered = true end) assert(lifecycle_response.success, "a vendor open hook failure must not prevent opening the app") +assert(open_hook_after_response, "a vendor open hook must run after the NUI response") lifecycle_callback({ appId = "dispatch", event = "ready" }, function(response) lifecycle_response = response end)