mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-29 06:01:35 +00:00
dcf7b47113
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.
298 lines
9.7 KiB
Lua
298 lines
9.7 KiB
Lua
SkyPhoneCompatibility = {}
|
|
|
|
function SkyPhoneCompatibility.RegisterExportAlias(resource_name, export_name, handler)
|
|
assert(type(resource_name) == "string" and resource_name ~= "", "Export alias resource must be a non-empty string")
|
|
assert(type(export_name) == "string" and export_name ~= "", "Export alias name must be a non-empty string")
|
|
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(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
|
|
|
|
SkyPhoneCompatibility.Providers = {
|
|
lb = "lb_phone",
|
|
seventeen = "17mov",
|
|
high = "high_phone",
|
|
quasar = "quasar_v3",
|
|
yseries = "yseries",
|
|
}
|
|
|
|
local function normalize_at_resource_url(owner_resource, url, error_prefix)
|
|
if type(url) ~= "string" or url == "" then
|
|
return nil, "invalid_" .. error_prefix
|
|
end
|
|
if url:sub(1, 7) == "http://" then
|
|
return nil, "insecure_" .. error_prefix
|
|
end
|
|
if url:sub(1, 1) ~= "@" then
|
|
return url
|
|
end
|
|
|
|
local url_owner, url_path = url:match("^@([^/]+)/(.+)$")
|
|
if url_owner ~= owner_resource or not url_path then
|
|
return nil, error_prefix .. "_owner_mismatch"
|
|
end
|
|
return owner_resource .. "/" .. url_path
|
|
end
|
|
|
|
local function resolve_lb_asset_resource(owner_resource, app_data)
|
|
local ui = app_data.ui
|
|
if type(ui) ~= "string" then
|
|
return nil
|
|
end
|
|
|
|
local explicit_ui_resource = ui:match("^https://cfx%-nui%-([^/]+)/")
|
|
or ui:match("^nui://([^/]+)/")
|
|
local asset_resource = explicit_ui_resource or ui:match("^([%w][%w._-]*)/")
|
|
if not asset_resource or asset_resource == owner_resource then
|
|
return nil
|
|
end
|
|
|
|
if explicit_ui_resource or app_data.resource == asset_resource then
|
|
return asset_resource
|
|
end
|
|
|
|
local icon = app_data.icon
|
|
local icon_resource = type(icon) == "string" and (
|
|
icon:match("^https://cfx%-nui%-([^/]+)/") or icon:match("^nui://([^/]+)/")
|
|
) or nil
|
|
if icon_resource == asset_resource then
|
|
return asset_resource
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
local function build_locale_maps(app_name, locales)
|
|
local names = {}
|
|
local descriptions = {}
|
|
|
|
if type(locales) == "table" then
|
|
for locale, locale_data in pairs(locales) do
|
|
if type(locale) == "string" and type(locale_data) == "table" then
|
|
if type(locale_data.label) == "string" and locale_data.label ~= "" then
|
|
names[locale] = locale_data.label
|
|
end
|
|
if type(locale_data.description) == "string" and locale_data.description ~= "" then
|
|
descriptions[locale] = locale_data.description
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if not next(names) then
|
|
return app_name, app_name
|
|
end
|
|
for locale, label in pairs(names) do
|
|
if not descriptions[locale] then
|
|
descriptions[locale] = label
|
|
end
|
|
end
|
|
return names, descriptions
|
|
end
|
|
|
|
function SkyPhoneCompatibility.CopyQuasarData(app_data)
|
|
local copy = {}
|
|
for key, value in pairs(app_data) do
|
|
if key == "iframe" and type(value) == "table" then
|
|
copy.iframe = {}
|
|
for iframe_key, iframe_value in pairs(value) do
|
|
copy.iframe[iframe_key] = iframe_value
|
|
end
|
|
else
|
|
copy[key] = value
|
|
end
|
|
end
|
|
return copy
|
|
end
|
|
|
|
function SkyPhoneCompatibility.BuildLbDefinition(owner_resource, app_data)
|
|
if type(app_data) ~= "table" or type(app_data.identifier) ~= "string" then
|
|
return nil, "invalid_app_data"
|
|
end
|
|
|
|
local ui, ui_error = normalize_at_resource_url(owner_resource, app_data.ui, "ui")
|
|
if not ui then
|
|
return nil, ui_error
|
|
end
|
|
|
|
return {
|
|
schemaVersion = 1,
|
|
id = app_data.identifier,
|
|
name = app_data.name,
|
|
description = app_data.description,
|
|
developer = app_data.developer,
|
|
category = app_data.game and "games" or "utilities",
|
|
ui = ui,
|
|
assetResource = resolve_lb_asset_resource(owner_resource, app_data),
|
|
bridgeMode = "legacy",
|
|
icon = app_data.icon,
|
|
defaultInstalled = app_data.defaultApp or false,
|
|
removable = not app_data.defaultApp,
|
|
orientation = app_data.landscape and "landscape" or "portrait",
|
|
onInstall = app_data.onInstall,
|
|
onOpen = app_data.onOpen or app_data.onUse,
|
|
onClose = app_data.onClose,
|
|
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,
|
|
},
|
|
}
|
|
end
|
|
|
|
function SkyPhoneCompatibility.Build17MovDefinition(app_data)
|
|
if type(app_data) ~= "table" or type(app_data.name) ~= "string" then
|
|
return nil, "invalid_app_data"
|
|
end
|
|
|
|
return {
|
|
schemaVersion = 1,
|
|
id = app_data.name,
|
|
name = app_data.label,
|
|
description = app_data.description or app_data.label,
|
|
category = "utilities",
|
|
ui = app_data.ui,
|
|
bridgeMode = "legacy",
|
|
icon = app_data.icon,
|
|
iconBackground = type(app_data.iconBackground) == "string" and app_data.iconBackground or nil,
|
|
defaultInstalled = app_data.default or app_data.preInstalled or false,
|
|
removable = not app_data.default,
|
|
orientation = "portrait",
|
|
compatibility = {
|
|
provider = SkyPhoneCompatibility.Providers.seventeen,
|
|
apiVersion = 1,
|
|
},
|
|
}
|
|
end
|
|
|
|
function SkyPhoneCompatibility.BuildHighDefinition(owner_resource, app_name, data, locales)
|
|
if type(owner_resource) ~= "string" or type(app_name) ~= "string" or type(data) ~= "table" then
|
|
return nil, "invalid_app_data"
|
|
end
|
|
if #app_name > 64 or not app_name:match("^[a-z0-9][a-z0-9._-]+$") then
|
|
return nil, "invalid_app_id"
|
|
end
|
|
|
|
local external_url, url_error = normalize_at_resource_url(
|
|
owner_resource,
|
|
data.externalUrl,
|
|
"external_url"
|
|
)
|
|
if not external_url then
|
|
return nil, url_error
|
|
end
|
|
|
|
local name, description = build_locale_maps(app_name, locales)
|
|
local icon = type(data.icon) == "table" and data.icon.imageUrl or data.icon
|
|
return {
|
|
schemaVersion = 1,
|
|
id = app_name,
|
|
name = name,
|
|
description = description,
|
|
developer = data.developer,
|
|
category = "utilities",
|
|
ui = external_url,
|
|
bridgeMode = "legacy",
|
|
icon = icon,
|
|
iconBackground = type(data.icon) == "table" and data.icon.background or nil,
|
|
defaultInstalled = data.preAdded or false,
|
|
removable = data.removable ~= false,
|
|
orientation = "portrait",
|
|
compatibility = {
|
|
provider = SkyPhoneCompatibility.Providers.high,
|
|
apiVersion = 1,
|
|
},
|
|
}
|
|
end
|
|
|
|
function SkyPhoneCompatibility.BuildQuasarDefinition(app_data)
|
|
local iframe_url = type(app_data) == "table"
|
|
and type(app_data.iframe) == "table"
|
|
and app_data.iframe.url
|
|
or nil
|
|
if type(app_data) ~= "table"
|
|
or type(app_data.id) ~= "string"
|
|
or type(app_data.label) ~= "string"
|
|
or type(iframe_url) ~= "string"
|
|
then
|
|
return nil, "invalid_app_data"
|
|
end
|
|
|
|
return {
|
|
schemaVersion = 1,
|
|
id = app_data.id,
|
|
name = app_data.label,
|
|
description = app_data.description or app_data.label,
|
|
category = "utilities",
|
|
ui = iframe_url,
|
|
bridgeMode = "legacy",
|
|
icon = app_data.icon,
|
|
defaultInstalled = app_data.defaultInstalled or false,
|
|
removable = true,
|
|
orientation = "portrait",
|
|
compatibility = {
|
|
provider = SkyPhoneCompatibility.Providers.quasar,
|
|
apiVersion = 1,
|
|
},
|
|
}
|
|
end
|
|
|
|
function SkyPhoneCompatibility.BuildYSeriesDefinition(app_data)
|
|
if type(app_data) ~= "table" or type(app_data.key) ~= "string" then
|
|
return nil, "invalid_app_data"
|
|
end
|
|
|
|
local icon = app_data.icon
|
|
if type(icon) == "table" then
|
|
icon = icon.yos or icon.humanoid
|
|
end
|
|
|
|
return {
|
|
schemaVersion = 1,
|
|
id = app_data.key,
|
|
name = app_data.name,
|
|
description = app_data.description or app_data.name,
|
|
category = app_data.game and "games" or "utilities",
|
|
ui = app_data.ui,
|
|
bridgeMode = "legacy",
|
|
icon = icon,
|
|
defaultInstalled = app_data.defaultApp or false,
|
|
removable = true,
|
|
orientation = "portrait",
|
|
compatibility = {
|
|
provider = SkyPhoneCompatibility.Providers.yseries,
|
|
apiVersion = 1,
|
|
},
|
|
}
|
|
end
|