FIX - Add detailed custom-app debug logging

Enable verbose tracing for custom apps and wire diagnostics through the stack. Sets Config.CustomApps.Debug = true, adds a debug_custom_app logger exposed as SkyPhoneApps.Debug, and instruments client and shared compatibility code (registration, normalization, provider exports, catalog sync, lifecycle hooks, resource stop, and app add/update/remove) with structured debug messages. Frontend sends a debug flag with the catalog and will call back to NUI (custom-app:catalog-debug) when debug is enabled. Tests updated to enable debug and assert fixBlur preservation and NUI catalog ACK handling. This provides detailed traces for exports, registration, catalog sync and lifecycle events.
This commit is contained in:
Leon.Schmidt
2026-08-19 01:27:38 +02:00
parent 8843b2763f
commit dcf7b47113
7 changed files with 410 additions and 17 deletions
+12
View File
@@ -650,6 +650,18 @@ function onMessage(event: MessageEvent<AppMessage>): void {
if (event.data?.type === 'custom-apps:catalog') {
appCatalog.replaceCatalog(event.data.data)
const catalogPayload = event.data.data as
| { apps?: unknown; debug?: unknown }
| undefined
if (catalogPayload?.debug === true) {
void nuiCall('custom-app:catalog-debug', {
acceptedCount: appCatalog.externalApps.length,
acceptedIds: appCatalog.externalApps.map((app) => app.id),
receivedCount: Array.isArray(catalogPayload.apps)
? catalogPayload.apps.length
: -1,
})
}
const activeAppId = route.params.appId
if (typeof activeAppId === 'string' && !isPhoneAppId(activeAppId)) {
void router.push('/')
+1
View File
@@ -44,6 +44,7 @@ Config.CustomApps = {
Enabled = true,
BundledApps = true,
ExternalApps = true,
Debug = true, -- detailed client traces for exports, registration, catalog sync and lifecycle events
ReadyTimeoutMs = 8000,
MaximumMessageBytes = 65536,
MaximumStorageBytesPerApp = 262144,
@@ -1,6 +1,7 @@
local RESOURCE_NAME = GetCurrentResourceName()
local providers = SkyPhoneCompatibility.Providers
local core = SkyPhoneApps.CompatibilityCore
local debug_custom_app = SkyPhoneApps.Debug or function() end
local provider_apps = {}
local high_client_apps = {}
local high_server_apps = {}
@@ -33,7 +34,24 @@ end
local function register_provider_app(provider, owner_resource, definition, vendor_data)
local app_id = definition.id
local existing = provider_apps[app_id]
debug_custom_app(
"provider",
"registration requested provider=%s id=%s owner=%s operation=%s",
tostring(provider),
tostring(app_id),
tostring(owner_resource),
existing and "update" or "add"
)
if existing and (existing.owner_resource ~= owner_resource or existing.provider ~= provider) then
debug_custom_app(
"provider",
"registration rejected provider=%s id=%s owner=%s existing_provider=%s existing_owner=%s error=duplicate_app_id",
tostring(provider),
tostring(app_id),
tostring(owner_resource),
tostring(existing.provider),
tostring(existing.owner_resource)
)
return false, "duplicate_app_id"
end
@@ -51,6 +69,15 @@ local function register_provider_app(provider, owner_resource, definition, vendo
vendor_data = vendor_data,
}
end
debug_custom_app(
"provider",
"registration result provider=%s id=%s owner=%s success=%s error=%s",
tostring(provider),
tostring(app_id),
tostring(owner_resource),
tostring(success),
tostring(error_message)
)
return success, error_message
end
@@ -82,6 +109,14 @@ local function remove_provider_app(owner_resource, app_id, allowed_providers)
end
function SkyPhoneApps.RegisterCompatibilityExport(owner_resource, app_data)
debug_custom_app(
"provider",
"compatibility export received owner=%s identifier=%s key=%s data_type=%s",
tostring(owner_resource),
type(app_data) == "table" and tostring(app_data.identifier) or "nil",
type(app_data) == "table" and tostring(app_data.key) or "nil",
type(app_data)
)
local provider
local definition
local definition_error
@@ -92,10 +127,22 @@ function SkyPhoneApps.RegisterCompatibilityExport(owner_resource, app_data)
provider = providers.yseries
definition, definition_error = SkyPhoneCompatibility.BuildYSeriesDefinition(app_data)
else
debug_custom_app(
"provider",
"compatibility export rejected owner=%s error=unknown_app_provider",
tostring(owner_resource)
)
return false, "unknown_app_provider"
end
if not definition then
debug_custom_app(
"provider",
"definition rejected provider=%s owner=%s error=%s",
tostring(provider),
tostring(owner_resource),
tostring(definition_error)
)
Bridge.Debug("warn", "[%s] %s registration rejected for %s: %s.",
RESOURCE_NAME,
provider,
@@ -104,6 +151,17 @@ function SkyPhoneApps.RegisterCompatibilityExport(owner_resource, app_data)
)
return false, definition_error
end
debug_custom_app(
"provider",
"definition built provider=%s id=%s owner=%s ui=%s icon=%s default_installed=%s fix_blur=%s",
tostring(provider),
tostring(definition.id),
tostring(owner_resource),
tostring(definition.ui),
tostring(definition.icon),
tostring(definition.defaultInstalled),
definition.compatibility and tostring(definition.compatibility.fixBlur) or "nil"
)
return register_provider_app(provider, owner_resource, definition, copy_record_data(app_data))
end
@@ -666,6 +724,7 @@ SkyPhoneCompatibility.RegisterExportAlias("yseries", "GetDataLoaded", function()
end)
CreateThread(function()
debug_custom_app("provider", "requesting provider snapshots and emitting compatibility ready signals")
TriggerServerEvent("sky_phone:compat:high:server:requestSnapshot")
TriggerEvent("17mov_Phone:Client:Ready")
@@ -677,6 +736,12 @@ CreateThread(function()
"yseries",
}
for index = 1, #provider_resources do
debug_custom_app(
"provider",
"emitting onResourceStart compatibility signal for %s (state=%s)",
provider_resources[index],
tostring(GetResourceState(provider_resources[index]))
)
TriggerEvent("onResourceStart", provider_resources[index])
end
end)
+283 -15
View File
@@ -28,6 +28,21 @@ local phone_open = false
local queued_messages = {}
local default_icon_url = ("https://cfx-nui-%s/img/custom-app.svg"):format(current_resource)
local function debug_custom_app(area, message, ...)
if Config.CustomApps.Debug ~= true then
return
end
local formatted = select("#", ...) > 0 and message:format(...) or message
Bridge.Debug(
"debug",
("[sky_phone][custom-apps][%s] %s"):format(area, formatted),
{ notice = true }
)
end
SkyPhoneApps.Debug = debug_custom_app
local function trim(value)
return value:match("^%s*(.-)%s*$")
end
@@ -300,7 +315,7 @@ local function validate_optional_text(value, error_code, maximum_length)
return normalized
end
local function normalize_external_definition(owner_resource, adapter_resource, definition)
local function normalize_external_definition_value(owner_resource, adapter_resource, definition)
if type(definition) ~= "table" then
return nil, "invalid_definition"
end
@@ -476,6 +491,43 @@ local function normalize_external_definition(owner_resource, adapter_resource, d
}
end
local function normalize_external_definition(owner_resource, adapter_resource, definition)
local requested_id = type(definition) == "table" and definition.id or "<invalid>"
debug_custom_app(
"normalize",
"begin id=%s owner=%s adapter=%s definition_type=%s",
tostring(requested_id),
tostring(owner_resource),
tostring(adapter_resource),
type(definition)
)
local app, definition_error = normalize_external_definition_value(owner_resource, adapter_resource, definition)
if not app then
debug_custom_app(
"normalize",
"rejected id=%s owner=%s error=%s",
tostring(requested_id),
tostring(owner_resource),
tostring(definition_error)
)
return nil, definition_error
end
debug_custom_app(
"normalize",
"accepted id=%s owner=%s provider=%s ui=%s icon=%s default_installed=%s removable=%s",
app.catalog.id,
app.ownerResource,
app.catalog.compatibility and tostring(app.catalog.compatibility.provider) or "native",
app.catalog.ui,
app.catalog.icon,
tostring(app.catalog.defaultInstalled),
tostring(app.catalog.removable)
)
return app
end
local function normalize_bundled_manifest(manifest)
local resource_base_path = ("custom_apps/%s/"):format(manifest.folder)
local base_url = ("https://cfx-nui-%s/%s"):format(current_resource, resource_base_path)
@@ -539,17 +591,37 @@ local function get_catalog()
end
local function send_catalog()
local catalog = get_catalog()
local entries = {}
for index = 1, #catalog do
local app = catalog[index]
entries[index] = ("%s(owner=%s,default=%s)"):format(
app.id,
app.ownerResource,
tostring(app.defaultInstalled)
)
end
debug_custom_app(
"catalog",
"sending %s app(s) to NUI: %s",
#catalog,
#entries > 0 and table.concat(entries, ", ") or "<empty>"
)
SendNUIMessage({
type = "custom-apps:catalog",
data = {
apps = get_catalog(),
apps = catalog,
debug = Config.CustomApps.Debug == true,
},
})
end
local function sync_catalog_if_open()
if phone_open then
debug_custom_app("catalog", "phone is open; synchronizing catalog")
send_catalog()
else
debug_custom_app("catalog", "phone is closed; catalog sync deferred")
end
end
@@ -578,6 +650,13 @@ local function register_app(app)
local app_id = app.catalog.id
local existing = apps_by_id[app_id]
if existing then
debug_custom_app(
"register",
"duplicate id=%s requested_owner=%s existing_owner=%s",
app_id,
app.ownerResource,
existing.ownerResource
)
Bridge.Debug(
"warn",
"[sky_phone] Rejected duplicate custom app '%s' from '%s'; it is already owned by '%s'.",
@@ -593,6 +672,16 @@ local function register_app(app)
if app.adapterResource then
add_owner_index(app_ids_by_adapter, app.adapterResource, app_id)
end
debug_custom_app(
"register",
"success id=%s owner=%s adapter=%s provider=%s default_installed=%s phone_open=%s",
app_id,
app.ownerResource,
tostring(app.adapterResource),
app.catalog.compatibility and tostring(app.catalog.compatibility.provider) or "native",
tostring(app.catalog.defaultInstalled),
tostring(phone_open)
)
sync_catalog_if_open()
return true
end
@@ -661,6 +750,7 @@ end
local function remove_registered_app(app_id, invoke_close_hook, synchronize)
local app = apps_by_id[app_id]
if not app then
debug_custom_app("remove", "rejected id=%s error=app_not_found", tostring(app_id))
return false, "app_not_found"
end
@@ -680,20 +770,41 @@ local function remove_registered_app(app_id, invoke_close_hook, synchronize)
if synchronize then
sync_catalog_if_open()
end
debug_custom_app(
"remove",
"success id=%s owner=%s synchronize=%s",
app_id,
app.ownerResource,
tostring(synchronize)
)
return true
end
local function get_direct_owner()
local owner_resource = GetInvokingResource()
if not owner_resource then
debug_custom_app("owner", "rejected export call: GetInvokingResource returned nil")
return nil, "missing_invoking_resource"
end
local valid_owner, owner_error = validate_owner_resource(owner_resource, false)
if not valid_owner then
debug_custom_app(
"owner",
"rejected invoking_resource=%s state=%s error=%s",
owner_resource,
tostring(GetResourceState(owner_resource)),
tostring(owner_error)
)
return nil, owner_error
end
debug_custom_app(
"owner",
"accepted invoking_resource=%s state=%s",
owner_resource,
tostring(GetResourceState(owner_resource))
)
return owner_resource
end
@@ -772,21 +883,53 @@ local function add_custom_app_from_adapter(owner_resource, definition)
end
local function add_compatibility_app(owner_resource, definition)
local app_id = type(definition) == "table" and definition.id or "<invalid>"
debug_custom_app(
"compat-core",
"add requested id=%s owner=%s",
tostring(app_id),
tostring(owner_resource)
)
if not Config.CustomApps.Enabled or not Config.CustomApps.ExternalApps then
debug_custom_app("compat-core", "add rejected id=%s error=external_apps_disabled", tostring(app_id))
return false, "external_apps_disabled"
end
local valid_owner, owner_error = validate_owner_resource(owner_resource, false)
if not valid_owner then
debug_custom_app(
"compat-core",
"add rejected id=%s owner=%s state=%s error=%s",
tostring(app_id),
tostring(owner_resource),
tostring(GetResourceState(owner_resource)),
tostring(owner_error)
)
return false, owner_error
end
local app, definition_error = normalize_external_definition(owner_resource, nil, definition)
if not app then
debug_custom_app(
"compat-core",
"add rejected id=%s owner=%s error=%s",
tostring(app_id),
tostring(owner_resource),
tostring(definition_error)
)
return false, definition_error
end
return register_app(app)
local success, register_error = register_app(app)
debug_custom_app(
"compat-core",
"add result id=%s owner=%s success=%s error=%s",
tostring(app_id),
tostring(owner_resource),
tostring(success),
tostring(register_error)
)
return success, register_error
end
local function replace_registered_app(app, adapter_resource)
@@ -806,6 +949,14 @@ local function replace_registered_app(app, adapter_resource)
end
apps_by_id[app_id] = app
debug_custom_app(
"update",
"success id=%s owner=%s adapter=%s default_installed=%s",
app_id,
app.ownerResource,
tostring(adapter_resource),
tostring(app.catalog.defaultInstalled)
)
sync_catalog_if_open()
return true
end
@@ -849,6 +1000,13 @@ local function update_custom_app_from_adapter(owner_resource, definition)
end
local function update_compatibility_app(owner_resource, definition)
local app_id = type(definition) == "table" and definition.id or "<invalid>"
debug_custom_app(
"compat-core",
"update requested id=%s owner=%s",
tostring(app_id),
tostring(owner_resource)
)
if not Config.CustomApps.Enabled or not Config.CustomApps.ExternalApps then
return false, "external_apps_disabled"
end
@@ -860,9 +1018,25 @@ local function update_compatibility_app(owner_resource, definition)
local app, definition_error = normalize_external_definition(owner_resource, nil, definition)
if not app then
debug_custom_app(
"compat-core",
"update rejected id=%s owner=%s error=%s",
tostring(app_id),
tostring(owner_resource),
tostring(definition_error)
)
return false, definition_error
end
return replace_registered_app(app, nil)
local success, update_error = replace_registered_app(app, nil)
debug_custom_app(
"compat-core",
"update result id=%s owner=%s success=%s error=%s",
tostring(app_id),
tostring(owner_resource),
tostring(success),
tostring(update_error)
)
return success, update_error
end
local function remove_custom_app(app_id)
@@ -1252,6 +1426,13 @@ end
local function set_phone_open(is_open)
phone_open = is_open
debug_custom_app(
"phone",
"state changed open=%s active_app=%s registered_apps=%s",
tostring(phone_open),
tostring(active_app_id),
#get_catalog()
)
if phone_open then
send_catalog()
return
@@ -1307,6 +1488,15 @@ if Config.CustomApps.Enabled and Config.CustomApps.BundledApps then
end
local function add_custom_app_export(definition)
debug_custom_app(
"dispatch",
"AddCustomApp received type=%s id=%s identifier=%s key=%s invoking_resource=%s",
type(definition),
type(definition) == "table" and tostring(definition.id) or "nil",
type(definition) == "table" and tostring(definition.identifier) or "nil",
type(definition) == "table" and tostring(definition.key) or "nil",
tostring(GetInvokingResource())
)
if type(definition) ~= "table" then
return add_custom_app(definition)
end
@@ -1322,19 +1512,32 @@ local function add_custom_app_export(definition)
provider_markers = provider_markers + 1
end
if provider_markers > 1 then
debug_custom_app("dispatch", "AddCustomApp rejected error=ambiguous_app_provider")
return false, "ambiguous_app_provider"
end
if definition.identifier == nil and definition.key == nil then
debug_custom_app("dispatch", "routing AddCustomApp through native sky_phone registration")
return add_custom_app(definition)
end
local owner_resource, owner_error = get_direct_owner()
if not owner_resource then
debug_custom_app(
"dispatch",
"compatibility AddCustomApp rejected error=%s",
tostring(owner_error)
)
return false, owner_error
end
if not SkyPhoneApps.RegisterCompatibilityExport then
debug_custom_app("dispatch", "compatibility AddCustomApp rejected error=compatibility_not_ready")
return false, "compatibility_not_ready"
end
debug_custom_app(
"dispatch",
"routing AddCustomApp through compatibility provider owner=%s",
owner_resource
)
return SkyPhoneApps.RegisterCompatibilityExport(owner_resource, definition)
end
@@ -1371,15 +1574,66 @@ SkyPhoneCompatibility.RegisterExportAlias("lb-phone", "SendCustomAppMessage", se
SkyPhoneCompatibility.RegisterExportAlias("yseries", "AddCustomApp", add_custom_app_export)
SkyPhoneCompatibility.RegisterExportAlias("yseries", "RemoveCustomApp", remove_custom_app)
RegisterNUICallback("custom-app:catalog-debug", function(data, cb)
local accepted_ids = type(data) == "table" and data.acceptedIds or nil
local ids = {}
if type(accepted_ids) == "table" then
for index = 1, #accepted_ids do
if type(accepted_ids[index]) == "string" then
ids[#ids + 1] = accepted_ids[index]
end
end
end
debug_custom_app(
"nui",
"catalog applied received=%s accepted=%s ids=%s",
type(data) == "table" and tostring(data.receivedCount) or "invalid",
type(data) == "table" and tostring(data.acceptedCount) or "invalid",
#ids > 0 and table.concat(ids, ", ") or "<empty>"
)
cb({ success = true })
end)
RegisterNUICallback("custom-app:lifecycle", function(data, cb)
local requested_app_id = type(data) == "table" and data.appId or nil
local requested_event = type(data) == "table" and data.event or nil
local function respond(success, error_message)
debug_custom_app(
"lifecycle",
"result app=%s event=%s success=%s error=%s active_app=%s ready=%s phone_open=%s",
tostring(requested_app_id),
tostring(requested_event),
tostring(success),
tostring(error_message),
tostring(active_app_id),
tostring(active_app_ready),
tostring(phone_open)
)
local response = { success = success }
if error_message then
response.error = error_message
end
cb(response)
end
debug_custom_app(
"lifecycle",
"request app=%s event=%s phone_open=%s active_app=%s ready=%s",
tostring(requested_app_id),
tostring(requested_event),
tostring(phone_open),
tostring(active_app_id),
tostring(active_app_ready)
)
if type(data) ~= "table" then
cb({ success = false, error = "invalid_lifecycle_request" })
respond(false, "invalid_lifecycle_request")
return
end
local valid_id, id_error = SkyPhoneApps.ValidateAppId(data.appId)
if not valid_id then
cb({ success = false, error = id_error })
respond(false, id_error)
return
end
@@ -1389,7 +1643,7 @@ RegisterNUICallback("custom-app:lifecycle", function(data, cb)
and lifecycle_event ~= "ready"
and lifecycle_event ~= "close"
then
cb({ success = false, error = "invalid_lifecycle_event" })
respond(false, "invalid_lifecycle_event")
return
end
@@ -1397,15 +1651,15 @@ RegisterNUICallback("custom-app:lifecycle", function(data, cb)
if lifecycle_event == "close"
and (not phone_open or not app or active_app_id ~= data.appId)
then
cb({ success = true })
respond(true)
return
end
if not phone_open then
cb({ success = false, error = "phone_closed" })
respond(false, "phone_closed")
return
end
if not app then
cb({ success = false, error = "app_not_found" })
respond(false, "app_not_found")
return
end
@@ -1414,7 +1668,7 @@ RegisterNUICallback("custom-app:lifecycle", function(data, cb)
Config.CustomApps.MaximumMessageBytes
)
if payload_error then
cb({ success = false, error = payload_error })
respond(false, payload_error)
return
end
@@ -1431,11 +1685,11 @@ RegisterNUICallback("custom-app:lifecycle", function(data, cb)
queued_messages[data.appId] = {}
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" })
respond(false, "app_not_active")
return
elseif lifecycle_event == "ready" then
if active_app_ready then
cb({ success = true })
respond(true)
return
end
active_app_ready = true
@@ -1451,10 +1705,10 @@ RegisterNUICallback("custom-app:lifecycle", function(data, cb)
end
if not hook_success then
cb({ success = false, error = "hook_failed" })
respond(false, "hook_failed")
return
end
cb({ success = true })
respond(true)
invoke_deferred_hooks(deferred_hooks)
end)
@@ -1463,6 +1717,14 @@ AddEventHandler("onClientResourceStop", function(resource_name)
return
end
debug_custom_app(
"resource",
"stop detected resource=%s owner_apps=%s adapter_apps=%s",
tostring(resource_name),
tostring(app_ids_by_owner[resource_name] ~= nil),
tostring(app_ids_by_adapter[resource_name] ~= nil)
)
local app_ids = {}
local owner_apps = app_ids_by_owner[resource_name]
if owner_apps then
@@ -1486,4 +1748,10 @@ AddEventHandler("onClientResourceStop", function(resource_name)
if removed then
sync_catalog_if_open()
end
debug_custom_app(
"resource",
"stop cleanup resource=%s removed_any=%s",
tostring(resource_name),
tostring(removed)
)
end)
+29 -1
View File
@@ -6,7 +6,34 @@ function SkyPhoneCompatibility.RegisterExportAlias(resource_name, export_name, h
assert(type(handler) == "function", "Export alias handler must be a function")
AddEventHandler(("__cfx_export_%s_%s"):format(resource_name, export_name), function(set_callback)
set_callback(handler)
set_callback(function(...)
local debug_custom_app = SkyPhoneApps and SkyPhoneApps.Debug
local invoking_resource = GetInvokingResource and GetInvokingResource() or nil
if debug_custom_app then
debug_custom_app(
"export",
"call provider=%s export=%s invoking_resource=%s argument_count=%s",
resource_name,
export_name,
tostring(invoking_resource),
select("#", ...)
)
end
local results = table.pack(handler(...))
if debug_custom_app then
debug_custom_app(
"export",
"result provider=%s export=%s invoking_resource=%s success=%s error=%s",
resource_name,
export_name,
tostring(invoking_resource),
tostring(results[1]),
tostring(results[2])
)
end
return table.unpack(results, 1, results.n)
end)
end)
end
@@ -137,6 +164,7 @@ function SkyPhoneCompatibility.BuildLbDefinition(owner_resource, app_data)
compatibility = {
provider = SkyPhoneCompatibility.Providers.lb,
apiVersion = 1,
fixBlur = app_data.fixBlur == true,
resourceName = type(app_data.resource) == "string" and app_data.resource or owner_resource,
},
}
+2
View File
@@ -6,6 +6,7 @@ local lb_definition = assert(SkyPhoneCompatibility.BuildLbDefinition("lb_app", {
description = "Dispatch terminal",
ui = "ui/index.html",
defaultApp = true,
fixBlur = true,
landscape = true,
onUse = function() end,
}))
@@ -14,6 +15,7 @@ assert(lb_definition.ui == "ui/index.html", "LB relative UI must remain owner-re
assert(lb_definition.orientation == "landscape", "LB landscape flag must be preserved")
assert(type(lb_definition.onOpen) == "function", "LB onUse must map to the open lifecycle")
assert(lb_definition.compatibility.resourceName == "lb_app", "LB callbacks must target the registering resource")
assert(lb_definition.compatibility.fixBlur, "LB fixBlur must be preserved")
local bridged_lb_definition = assert(SkyPhoneCompatibility.BuildLbDefinition("phone_adapter", {
identifier = "bridged",
+18 -1
View File
@@ -12,6 +12,7 @@ Config = {
CustomApps = {
AllowRemoteOrigins = {},
BundledApps = false,
Debug = true,
Enabled = true,
ExternalApps = true,
MaximumMessageBytes = 65536,
@@ -23,17 +24,19 @@ Config = {
},
}
local encoded_json_value = nil
json = {
decode = function(encoded)
if encoded == "null" then
return nil, 5
end
return {}
return encoded_json_value
end,
encode = function(value)
if value == nil then
return "null"
end
encoded_json_value = value
return "{}"
end,
}
@@ -83,6 +86,7 @@ function CreateThread(handler)
handler()
end
dofile("sky_phone/source/bridge/shared.lua")
dofile("sky_phone/source/shared/custom_apps.lua")
dofile("sky_phone/source/shared/custom_app_compat.lua")
dofile("sky_phone/source/client/custom_apps.lua")
@@ -186,6 +190,8 @@ local lb_definition = {
identifier = "dispatch",
name = "Dispatch",
description = "Dispatch terminal",
defaultApp = false,
fixBlur = true,
ui = "ui/index.html",
onInstall = make_cfx_function_reference("install-hook", function()
install_hook_after_response = lifecycle_response_delivered
@@ -214,6 +220,17 @@ for index = 1, #retry_catalog.data.apps do
end
end
assert(retried_app and retried_app.name == "Dispatch Updated", "same-owner retry must publish the updated app")
assert(not retried_app.defaultInstalled, "LB defaultApp=false must keep the app available for App Store installation")
assert(retried_app.compatibility.fixBlur, "LB fixBlur must be preserved for every provider app")
local catalog_debug_response
assert(registered_nui_callbacks["custom-app:catalog-debug"])({
acceptedCount = 1,
acceptedIds = { "dispatch" },
receivedCount = 1,
}, function(response)
catalog_debug_response = response
end)
assert(catalog_debug_response.success, "the NUI catalog acknowledgement must be accepted")
lifecycle_response_delivered = false
lifecycle_callback({ appId = "dispatch", event = "install" }, function(response)
lifecycle_response = response