mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-28 17:01:18 +00:00
FIX - support third-party custom apps
This commit is contained in:
@@ -6,8 +6,9 @@
|
||||
Hersteller-Aliase und übernimmt unterstützte Apps automatisch in Springboard und App Store. In
|
||||
App-Ressourcen müssen dafür keine Sky-Vorlagen abgelegt werden. Unterstützt werden die
|
||||
dokumentierten Basisverträge von LB Phone, 17Movement, High Phone, Quasar Smartphone V3 und
|
||||
YSeries; die Aliase `lb-phone`, `17mov_Phone`, `high-phone`, `qs-smartphone` und `yseries` werden
|
||||
direkt von `sky_phone` bereitgestellt. Einrichtung und ehrliche Kompatibilitätsgrenzen stehen in der
|
||||
YSeries; die Resource- und Cfx-Export-Aliase `lb-phone`, `17mov_Phone`, `high-phone`,
|
||||
`qs-smartphone` und `yseries` werden direkt von `sky_phone` bereitgestellt. Einrichtung und
|
||||
ehrliche Kompatibilitätsgrenzen stehen in der
|
||||
[deutschen Custom-App-Anleitung](docs/custom-apps.md).
|
||||
|
||||
## FlipTok verification
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||
<defs>
|
||||
<linearGradient id="background" x1="16" y1="12" x2="112" y2="116" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#64748b"/>
|
||||
<stop offset="1" stop-color="#1e293b"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="28" fill="url(#background)"/>
|
||||
<g fill="#f8fafc">
|
||||
<rect x="29" y="29" width="29" height="29" rx="8"/>
|
||||
<rect x="70" y="29" width="29" height="29" rx="8"/>
|
||||
<rect x="29" y="70" width="29" height="29" rx="8"/>
|
||||
<rect x="70" y="70" width="29" height="29" rx="8"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 613 B |
@@ -561,11 +561,6 @@ AddEventHandler("onClientResourceStop", function(resource_name)
|
||||
end
|
||||
end)
|
||||
|
||||
CreateThread(function()
|
||||
TriggerServerEvent("sky_phone:compat:high:server:requestSnapshot")
|
||||
TriggerEvent("17mov_Phone:Client:Ready")
|
||||
end)
|
||||
|
||||
exports("AddApplication", add_17mov_application)
|
||||
exports("RemoveApplication", remove_17mov_application)
|
||||
exports("SendAppMessage", send_app_message)
|
||||
@@ -579,3 +574,42 @@ exports("getCustomApps", get_quasar_apps)
|
||||
exports("OpenPhoneApp", open_quasar_app)
|
||||
exports("OpenApp", open_phone_app)
|
||||
exports("CloseApp", close_phone_app)
|
||||
|
||||
SkyPhoneCompatibility.RegisterExportAlias("lb-phone", "OpenApp", open_phone_app)
|
||||
SkyPhoneCompatibility.RegisterExportAlias("lb-phone", "CloseApp", close_phone_app)
|
||||
|
||||
SkyPhoneCompatibility.RegisterExportAlias("17mov_Phone", "AddApplication", add_17mov_application)
|
||||
SkyPhoneCompatibility.RegisterExportAlias("17mov_Phone", "RemoveApplication", remove_17mov_application)
|
||||
SkyPhoneCompatibility.RegisterExportAlias("17mov_Phone", "SendAppMessage", send_app_message)
|
||||
|
||||
SkyPhoneCompatibility.RegisterExportAlias("high-phone", "addApplication", add_high_application)
|
||||
SkyPhoneCompatibility.RegisterExportAlias("high-phone", "sendAppNui", send_high_app_nui)
|
||||
|
||||
SkyPhoneCompatibility.RegisterExportAlias("qs-smartphone", "addCustomApp", add_quasar_app)
|
||||
SkyPhoneCompatibility.RegisterExportAlias("qs-smartphone", "addCustomAppsBatch", add_quasar_apps_batch)
|
||||
SkyPhoneCompatibility.RegisterExportAlias("qs-smartphone", "updateCustomApp", update_quasar_app)
|
||||
SkyPhoneCompatibility.RegisterExportAlias("qs-smartphone", "removeCustomApp", remove_quasar_app)
|
||||
SkyPhoneCompatibility.RegisterExportAlias("qs-smartphone", "getCustomApps", get_quasar_apps)
|
||||
SkyPhoneCompatibility.RegisterExportAlias("qs-smartphone", "OpenPhoneApp", open_quasar_app)
|
||||
|
||||
SkyPhoneCompatibility.RegisterExportAlias("yseries", "SendAppMessage", send_app_message)
|
||||
SkyPhoneCompatibility.RegisterExportAlias("yseries", "CloseApp", close_phone_app)
|
||||
SkyPhoneCompatibility.RegisterExportAlias("yseries", "GetDataLoaded", function()
|
||||
return true
|
||||
end)
|
||||
|
||||
CreateThread(function()
|
||||
TriggerServerEvent("sky_phone:compat:high:server:requestSnapshot")
|
||||
TriggerEvent("17mov_Phone:Client:Ready")
|
||||
|
||||
local provider_resources = {
|
||||
"lb-phone",
|
||||
"17mov_Phone",
|
||||
"high-phone",
|
||||
"qs-smartphone",
|
||||
"yseries",
|
||||
}
|
||||
for index = 1, #provider_resources do
|
||||
TriggerEvent("onResourceStart", provider_resources[index])
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -26,12 +26,24 @@ local active_app_id = nil
|
||||
local active_app_ready = false
|
||||
local phone_open = false
|
||||
local queued_messages = {}
|
||||
local default_icon_url = ("https://cfx-nui-%s/custom_apps/_sdk/default-icon.svg"):format(current_resource)
|
||||
local default_icon_url = ("https://cfx-nui-%s/img/custom-app.svg"):format(current_resource)
|
||||
|
||||
local function trim(value)
|
||||
return value:match("^%s*(.-)%s*$")
|
||||
end
|
||||
|
||||
local function is_callable(value)
|
||||
if type(value) == "function" then
|
||||
return true
|
||||
end
|
||||
if type(value) ~= "table" then
|
||||
return false
|
||||
end
|
||||
|
||||
local value_metatable = getmetatable(value)
|
||||
return type(value_metatable) == "table" and type(value_metatable.__call) == "function"
|
||||
end
|
||||
|
||||
local function validate_owner_resource(owner_resource, allow_stopping)
|
||||
if type(owner_resource) ~= "string"
|
||||
or #owner_resource == 0
|
||||
@@ -118,7 +130,7 @@ local function validate_asset_path(path)
|
||||
return true
|
||||
end
|
||||
|
||||
local function normalize_asset_url(value, original_owner, adapter_resource, required)
|
||||
local function normalize_asset_url(value, original_owner, adapter_resource, asset_resource, required)
|
||||
if value == nil and not required then
|
||||
return nil
|
||||
end
|
||||
@@ -137,6 +149,9 @@ local function normalize_asset_url(value, original_owner, adapter_resource, requ
|
||||
if adapter_resource then
|
||||
allowed_owners[adapter_resource] = true
|
||||
end
|
||||
if asset_resource then
|
||||
allowed_owners[asset_resource] = true
|
||||
end
|
||||
|
||||
local cfx_owner = url:match("^https://cfx%-nui%-([^/]+)/")
|
||||
if cfx_owner then
|
||||
@@ -177,6 +192,13 @@ local function normalize_asset_url(value, original_owner, adapter_resource, requ
|
||||
asset_path = url:sub(#adapter_prefix + 1)
|
||||
end
|
||||
end
|
||||
if asset_resource then
|
||||
local asset_prefix = asset_resource .. "/"
|
||||
if url:sub(1, #asset_prefix) == asset_prefix then
|
||||
asset_owner = asset_resource
|
||||
asset_path = url:sub(#asset_prefix + 1)
|
||||
end
|
||||
end
|
||||
|
||||
if not validate_asset_path(asset_path) then
|
||||
return nil, "invalid_asset_url"
|
||||
@@ -326,14 +348,42 @@ local function normalize_external_definition(owner_resource, adapter_resource, d
|
||||
return nil, "invalid_category"
|
||||
end
|
||||
|
||||
local ui, ui_error = normalize_asset_url(definition.ui, owner_resource, adapter_resource, true)
|
||||
local asset_resource = definition.assetResource
|
||||
if asset_resource ~= nil then
|
||||
if definition.compatibility == nil then
|
||||
return nil, "asset_resource_not_allowed"
|
||||
end
|
||||
local valid_asset_resource, asset_resource_error = validate_owner_resource(asset_resource, false)
|
||||
if not valid_asset_resource then
|
||||
return nil, asset_resource_error
|
||||
end
|
||||
end
|
||||
|
||||
local ui, ui_error = normalize_asset_url(
|
||||
definition.ui,
|
||||
owner_resource,
|
||||
adapter_resource,
|
||||
asset_resource,
|
||||
true
|
||||
)
|
||||
if not ui then
|
||||
return nil, ui_error
|
||||
end
|
||||
|
||||
local icon, icon_error = normalize_asset_url(definition.icon, owner_resource, adapter_resource, false)
|
||||
local icon, icon_error = normalize_asset_url(
|
||||
definition.icon,
|
||||
owner_resource,
|
||||
adapter_resource,
|
||||
asset_resource,
|
||||
false
|
||||
)
|
||||
if definition.icon ~= nil and not icon then
|
||||
return nil, icon_error
|
||||
if definition.compatibility == nil then
|
||||
return nil, icon_error
|
||||
end
|
||||
print((
|
||||
"[sky_phone] Custom app '%s' from '%s' uses an unavailable icon (%s); using the default icon."
|
||||
):format(definition.id, owner_resource, icon_error))
|
||||
end
|
||||
|
||||
local permissions, permissions_error = SkyPhoneApps.ValidatePermissions(definition.permissions)
|
||||
@@ -387,7 +437,7 @@ local function normalize_external_definition(owner_resource, adapter_resource, d
|
||||
local hooks = {}
|
||||
for hook_name in pairs(HOOK_NAMES) do
|
||||
local hook = definition[hook_name]
|
||||
if hook ~= nil and type(hook) ~= "function" then
|
||||
if hook ~= nil and not is_callable(hook) then
|
||||
return nil, "invalid_" .. hook_name
|
||||
end
|
||||
hooks[hook_name] = hook
|
||||
@@ -1280,6 +1330,12 @@ exports("SendCustomAppNotificationFromAdapter", send_custom_app_notification_fro
|
||||
exports("UpdateCustomApp", update_custom_app)
|
||||
exports("UpdateCustomAppFromAdapter", update_custom_app_from_adapter)
|
||||
|
||||
SkyPhoneCompatibility.RegisterExportAlias("lb-phone", "AddCustomApp", add_custom_app_export)
|
||||
SkyPhoneCompatibility.RegisterExportAlias("lb-phone", "RemoveCustomApp", remove_custom_app)
|
||||
SkyPhoneCompatibility.RegisterExportAlias("lb-phone", "SendCustomAppMessage", send_custom_app_message)
|
||||
SkyPhoneCompatibility.RegisterExportAlias("yseries", "AddCustomApp", add_custom_app_export)
|
||||
SkyPhoneCompatibility.RegisterExportAlias("yseries", "RemoveCustomApp", remove_custom_app)
|
||||
|
||||
RegisterNUICallback("custom-app:lifecycle", function(data, cb)
|
||||
if type(data) ~= "table" then
|
||||
cb({ success = false, error = "invalid_lifecycle_request" })
|
||||
@@ -1358,11 +1414,11 @@ RegisterNUICallback("custom-app:lifecycle", function(data, cb)
|
||||
clear_active_app(false)
|
||||
end
|
||||
|
||||
local hook_error = nil
|
||||
if not hook_success then
|
||||
hook_error = "hook_failed"
|
||||
if not hook_success and not app.catalog.compatibility then
|
||||
cb({ success = false, error = "hook_failed" })
|
||||
return
|
||||
end
|
||||
cb({ success = hook_success, error = hook_error })
|
||||
cb({ success = true })
|
||||
end)
|
||||
|
||||
AddEventHandler("onClientResourceStop", function(resource_name)
|
||||
|
||||
@@ -136,3 +136,4 @@ AddEventHandler("onResourceStop", function(resource_name)
|
||||
end)
|
||||
|
||||
exports("addApplication", add_high_application)
|
||||
SkyPhoneCompatibility.RegisterExportAlias("high-phone", "addApplication", add_high_application)
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
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(handler)
|
||||
end)
|
||||
end
|
||||
|
||||
SkyPhoneCompatibility.Providers = {
|
||||
lb = "lb_phone",
|
||||
seventeen = "17mov",
|
||||
@@ -26,6 +36,34 @@ local function normalize_at_resource_url(owner_resource, url, error_prefix)
|
||||
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 = {}
|
||||
@@ -87,6 +125,7 @@ function SkyPhoneCompatibility.BuildLbDefinition(owner_resource, app_data)
|
||||
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,
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
local registered_exports = {}
|
||||
local registered_event_handlers = {}
|
||||
local registered_nui_callbacks = {}
|
||||
local triggered_events = {}
|
||||
local nui_messages = {}
|
||||
local invoking_resource = nil
|
||||
|
||||
Config = {
|
||||
@@ -43,18 +47,31 @@ function GetInvokingResource()
|
||||
end
|
||||
|
||||
function GetResourceState(resource_name)
|
||||
if resource_name == "missing_resource" then
|
||||
if resource_name == "missing_resource" or resource_name == "ui" then
|
||||
return "missing"
|
||||
end
|
||||
return "started"
|
||||
end
|
||||
|
||||
function RegisterNUICallback() end
|
||||
function RegisterNUICallback(callback_name, handler)
|
||||
registered_nui_callbacks[callback_name] = handler
|
||||
end
|
||||
function RegisterNetEvent() end
|
||||
function AddEventHandler() end
|
||||
function SendNUIMessage() end
|
||||
function AddEventHandler(event_name, handler)
|
||||
local handlers = registered_event_handlers[event_name] or {}
|
||||
handlers[#handlers + 1] = handler
|
||||
registered_event_handlers[event_name] = handlers
|
||||
end
|
||||
function SendNUIMessage(message)
|
||||
nui_messages[#nui_messages + 1] = message
|
||||
end
|
||||
function TriggerServerEvent() end
|
||||
function TriggerEvent() end
|
||||
function TriggerEvent(event_name, ...)
|
||||
triggered_events[#triggered_events + 1] = {
|
||||
event_name = event_name,
|
||||
arguments = { ... },
|
||||
}
|
||||
end
|
||||
|
||||
function CreateThread(handler)
|
||||
handler()
|
||||
@@ -65,17 +82,110 @@ dofile("sky_phone/source/shared/custom_app_compat.lua")
|
||||
dofile("sky_phone/source/client/custom_apps.lua")
|
||||
dofile("sky_phone/source/client/custom_app_compat.lua")
|
||||
|
||||
local function get_alias_export(resource_name, export_name)
|
||||
local event_name = ("__cfx_export_%s_%s"):format(resource_name, export_name)
|
||||
local handlers = registered_event_handlers[event_name]
|
||||
assert(handlers and #handlers == 1, ("Missing %s:%s export alias"):format(resource_name, export_name))
|
||||
|
||||
local alias_handler
|
||||
local export_callback = setmetatable({
|
||||
__cfx_functionReference = "test-export-callback",
|
||||
}, {
|
||||
__call = function(_, handler)
|
||||
alias_handler = handler
|
||||
end,
|
||||
})
|
||||
handlers[1](export_callback)
|
||||
assert(type(alias_handler) == "function", ("Invalid %s:%s export alias"):format(resource_name, export_name))
|
||||
return alias_handler
|
||||
end
|
||||
|
||||
local function make_cfx_function_reference(reference, handler)
|
||||
return setmetatable({
|
||||
__cfx_functionReference = reference,
|
||||
}, {
|
||||
__call = function(_, ...)
|
||||
return handler(...)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
local lb_add_custom_app = get_alias_export("lb-phone", "AddCustomApp")
|
||||
local lb_remove_custom_app = get_alias_export("lb-phone", "RemoveCustomApp")
|
||||
local lb_send_custom_app_message = get_alias_export("lb-phone", "SendCustomAppMessage")
|
||||
local lb_open_app = get_alias_export("lb-phone", "OpenApp")
|
||||
local lb_close_app = get_alias_export("lb-phone", "CloseApp")
|
||||
local mov_add_application = get_alias_export("17mov_Phone", "AddApplication")
|
||||
local mov_remove_application = get_alias_export("17mov_Phone", "RemoveApplication")
|
||||
local mov_send_app_message = get_alias_export("17mov_Phone", "SendAppMessage")
|
||||
local high_add_application = get_alias_export("high-phone", "addApplication")
|
||||
local high_send_app_nui = get_alias_export("high-phone", "sendAppNui")
|
||||
local quasar_add_custom_app = get_alias_export("qs-smartphone", "addCustomApp")
|
||||
local quasar_add_custom_apps_batch = get_alias_export("qs-smartphone", "addCustomAppsBatch")
|
||||
local quasar_update_custom_app = get_alias_export("qs-smartphone", "updateCustomApp")
|
||||
local quasar_remove_custom_app = get_alias_export("qs-smartphone", "removeCustomApp")
|
||||
local quasar_get_custom_apps = get_alias_export("qs-smartphone", "getCustomApps")
|
||||
local quasar_open_phone_app = get_alias_export("qs-smartphone", "OpenPhoneApp")
|
||||
local yseries_add_custom_app = get_alias_export("yseries", "AddCustomApp")
|
||||
local yseries_remove_custom_app = get_alias_export("yseries", "RemoveCustomApp")
|
||||
local yseries_send_app_message = get_alias_export("yseries", "SendAppMessage")
|
||||
local yseries_close_app = get_alias_export("yseries", "CloseApp")
|
||||
local yseries_get_data_loaded = get_alias_export("yseries", "GetDataLoaded")
|
||||
|
||||
local expected_provider_resources = {
|
||||
["lb-phone"] = true,
|
||||
["17mov_Phone"] = true,
|
||||
["high-phone"] = true,
|
||||
["qs-smartphone"] = true,
|
||||
["yseries"] = true,
|
||||
}
|
||||
local resource_start_events = {}
|
||||
for index = 1, #triggered_events do
|
||||
local event = triggered_events[index]
|
||||
local resource_name = event.arguments[1]
|
||||
if event.event_name == "onResourceStart" then
|
||||
resource_start_events[resource_name] = true
|
||||
end
|
||||
end
|
||||
for resource_name in pairs(expected_provider_resources) do
|
||||
assert(resource_start_events[resource_name], ("Missing %s provider start signal"):format(resource_name))
|
||||
end
|
||||
assert(yseries_get_data_loaded(), "YSeries must see the compatibility provider as loaded")
|
||||
|
||||
invoking_resource = "lb_app"
|
||||
local lb_success, lb_error = registered_exports.AddCustomApp({
|
||||
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()
|
||||
error("vendor install failure")
|
||||
end),
|
||||
onOpen = make_cfx_function_reference("open-hook", function()
|
||||
error("vendor open failure")
|
||||
end),
|
||||
})
|
||||
assert(lb_success and lb_error == nil, "LB AddCustomApp must register through the shared export")
|
||||
|
||||
local lifecycle_callback = assert(registered_nui_callbacks["custom-app:lifecycle"])
|
||||
local lifecycle_response
|
||||
SkyPhoneApps.SetPhoneOpen(true)
|
||||
lifecycle_callback({ appId = "dispatch", event = "install" }, function(response)
|
||||
lifecycle_response = response
|
||||
end)
|
||||
assert(lifecycle_response.success, "a vendor install hook failure must not fail installation")
|
||||
lifecycle_callback({ appId = "dispatch", event = "open" }, function(response)
|
||||
lifecycle_response = response
|
||||
end)
|
||||
assert(lifecycle_response.success, "a vendor open hook failure must not prevent opening the app")
|
||||
lifecycle_callback({ appId = "dispatch", event = "ready" }, function(response)
|
||||
lifecycle_response = response
|
||||
end)
|
||||
assert(lifecycle_response.success, "ready must complete after a failed vendor open hook")
|
||||
SkyPhoneApps.SetPhoneOpen(false)
|
||||
|
||||
invoking_resource = "another_app"
|
||||
local duplicate_success, duplicate_error = registered_exports.AddCustomApp({
|
||||
local duplicate_success, duplicate_error = lb_add_custom_app({
|
||||
identifier = "dispatch",
|
||||
name = "Hijack",
|
||||
description = "Hijack",
|
||||
@@ -83,18 +193,61 @@ local duplicate_success, duplicate_error = registered_exports.AddCustomApp({
|
||||
})
|
||||
assert(not duplicate_success and duplicate_error == "duplicate_app_id", "cross-owner IDs must be rejected")
|
||||
|
||||
local remove_success, remove_error = registered_exports.RemoveCustomApp("dispatch")
|
||||
local remove_success, remove_error = lb_remove_custom_app("dispatch")
|
||||
assert(not remove_success and remove_error == "app_owner_mismatch", "cross-owner removal must be rejected")
|
||||
|
||||
invoking_resource = "lb_app"
|
||||
assert(registered_exports.RemoveCustomApp("dispatch"), "the LB owner must be able to remove its app")
|
||||
local inactive_message_success, inactive_message_error = lb_send_custom_app_message("dispatch", { type = "ping" })
|
||||
assert(not inactive_message_success and inactive_message_error == "app_not_active", "LB message alias must preserve app state checks")
|
||||
local closed_open_success, closed_open_error = lb_open_app("dispatch")
|
||||
assert(not closed_open_success and closed_open_error == "phone_closed", "LB open alias must preserve phone state checks")
|
||||
local inactive_close_success, inactive_close_error = lb_close_app({ app = "dispatch" })
|
||||
assert(not inactive_close_success and inactive_close_error == "app_not_active", "LB close alias must preserve app state checks")
|
||||
assert(lb_remove_custom_app("dispatch"), "the LB owner must be able to remove its app")
|
||||
|
||||
invoking_resource = "phone_adapter"
|
||||
assert(lb_add_custom_app({
|
||||
identifier = "manufacturer-app",
|
||||
name = "Manufacturer App",
|
||||
description = "Manufacturer-owned UI assets",
|
||||
ui = "manufacturer_app/ui/index.html",
|
||||
icon = "nui://manufacturer_app/ui/icon.png",
|
||||
}), "LB resource-prefixed assets must register through an adapter")
|
||||
SkyPhoneApps.SetPhoneOpen(true)
|
||||
SkyPhoneApps.SendCatalog()
|
||||
local catalog_message = nui_messages[#nui_messages]
|
||||
local manufacturer_app
|
||||
for index = 1, #catalog_message.data.apps do
|
||||
if catalog_message.data.apps[index].id == "manufacturer-app" then
|
||||
manufacturer_app = catalog_message.data.apps[index]
|
||||
break
|
||||
end
|
||||
end
|
||||
assert(manufacturer_app, "manufacturer app must be present in the catalog")
|
||||
assert(
|
||||
manufacturer_app.ui == "https://cfx-nui-manufacturer_app/ui/index.html",
|
||||
"LB resource-prefixed UI must preserve its asset resource"
|
||||
)
|
||||
assert(
|
||||
manufacturer_app.icon == "https://cfx-nui-manufacturer_app/ui/icon.png",
|
||||
"LB resource-prefixed icon must preserve its asset resource"
|
||||
)
|
||||
SkyPhoneApps.SetPhoneOpen(false)
|
||||
|
||||
invoking_resource = "yseries_app"
|
||||
assert(registered_exports.AddCustomApp({
|
||||
assert(yseries_add_custom_app({
|
||||
key = "slots",
|
||||
name = "Slots",
|
||||
ui = "https://cfx-nui-yseries_app/ui/index.html",
|
||||
icon = {
|
||||
yos = "https://cdn.example.com/slots.png",
|
||||
},
|
||||
}), "YSeries AddCustomApp must be selected from the key field")
|
||||
local yseries_message_success, yseries_message_error = yseries_send_app_message("slots", { type = "ping" })
|
||||
assert(not yseries_message_success and yseries_message_error == "app_not_active", "YSeries message alias must preserve app state checks")
|
||||
local yseries_close_success, yseries_close_error = yseries_close_app({ app = "slots" })
|
||||
assert(not yseries_close_success and yseries_close_error == "app_not_active", "YSeries close alias must preserve app state checks")
|
||||
assert(yseries_remove_custom_app("slots"), "YSeries RemoveCustomApp must use the provider alias")
|
||||
|
||||
local ambiguous_success, ambiguous_error = registered_exports.AddCustomApp({
|
||||
id = "ambiguous",
|
||||
@@ -105,14 +258,17 @@ local ambiguous_success, ambiguous_error = registered_exports.AddCustomApp({
|
||||
assert(not ambiguous_success and ambiguous_error == "ambiguous_app_provider", "ambiguous schemas must fail")
|
||||
|
||||
invoking_resource = "mov_app"
|
||||
assert(registered_exports.AddApplication({
|
||||
assert(mov_add_application({
|
||||
name = "market",
|
||||
label = "Market",
|
||||
ui = "https://cfx-nui-mov_app/ui/index.html",
|
||||
}), "17mov AddApplication must register")
|
||||
local mov_message_success, mov_message_error = mov_send_app_message("market", { type = "ping" })
|
||||
assert(not mov_message_success and mov_message_error == "app_not_active", "17mov message alias must preserve app state checks")
|
||||
assert(mov_remove_application("market"), "17mov RemoveApplication must use the provider alias")
|
||||
|
||||
invoking_resource = "high_app"
|
||||
assert(registered_exports.addApplication("bankingv2", {
|
||||
assert(high_add_application("bankingv2", {
|
||||
externalUrl = "@high_app/ui/index.html",
|
||||
}, {
|
||||
en = {
|
||||
@@ -120,9 +276,11 @@ assert(registered_exports.addApplication("bankingv2", {
|
||||
description = "Banking app",
|
||||
},
|
||||
}), "High addApplication must register")
|
||||
local high_message_success, high_message_error = high_send_app_nui("bankingv2", { type = "ping" })
|
||||
assert(not high_message_success and high_message_error == "app_not_active", "High message alias must preserve app state checks")
|
||||
|
||||
invoking_resource = "quasar_app"
|
||||
assert(registered_exports.addCustomApp({
|
||||
assert(quasar_add_custom_app({
|
||||
id = "services",
|
||||
label = "Services",
|
||||
iframe = {
|
||||
@@ -130,11 +288,14 @@ assert(registered_exports.addCustomApp({
|
||||
},
|
||||
}), "Quasar addCustomApp must register")
|
||||
|
||||
local quasar_apps = registered_exports.getCustomApps()
|
||||
local quasar_apps = quasar_get_custom_apps()
|
||||
assert(#quasar_apps == 1 and quasar_apps[1].id == "services", "Quasar getCustomApps must expose the registry")
|
||||
assert(registered_exports.updateCustomApp("services", {
|
||||
assert(quasar_update_custom_app("services", {
|
||||
label = "City Services",
|
||||
}), "Quasar updateCustomApp must update an owned app")
|
||||
assert(registered_exports.removeCustomApp("services"), "Quasar removeCustomApp must remove an owned app")
|
||||
local quasar_open_success, quasar_open_error = quasar_open_phone_app("services")
|
||||
assert(not quasar_open_success and quasar_open_error == "phone_closed", "Quasar open alias must preserve phone state checks")
|
||||
assert(quasar_remove_custom_app("services"), "Quasar removeCustomApp must remove an owned app")
|
||||
assert(quasar_add_custom_apps_batch({}), "Quasar batch alias must accept an empty batch")
|
||||
|
||||
print("Custom app compatibility client tests passed")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
local event_handlers = {}
|
||||
local registered_event_handlers = {}
|
||||
local registered_exports = {}
|
||||
local sent_events = {}
|
||||
local invoking_resource = nil
|
||||
@@ -25,7 +26,11 @@ function RegisterNetEvent(event_name, handler)
|
||||
event_handlers[event_name] = handler
|
||||
end
|
||||
|
||||
function AddEventHandler() end
|
||||
function AddEventHandler(event_name, handler)
|
||||
local handlers = registered_event_handlers[event_name] or {}
|
||||
handlers[#handlers + 1] = handler
|
||||
registered_event_handlers[event_name] = handlers
|
||||
end
|
||||
|
||||
function TriggerClientEvent(event_name, target, ...)
|
||||
sent_events[#sent_events + 1] = {
|
||||
@@ -38,8 +43,21 @@ end
|
||||
dofile("sky_phone/source/shared/custom_app_compat.lua")
|
||||
dofile("sky_phone/source/server/custom_app_compat.lua")
|
||||
|
||||
local alias_handlers = registered_event_handlers["__cfx_export_high-phone_addApplication"]
|
||||
assert(alias_handlers and #alias_handlers == 1, "Missing high-phone:addApplication export alias")
|
||||
local high_add_application
|
||||
local export_callback = setmetatable({
|
||||
__cfx_functionReference = "test-export-callback",
|
||||
}, {
|
||||
__call = function(_, handler)
|
||||
high_add_application = handler
|
||||
end,
|
||||
})
|
||||
alias_handlers[1](export_callback)
|
||||
assert(type(high_add_application) == "function", "Invalid high-phone:addApplication export alias")
|
||||
|
||||
invoking_resource = "high_server_app"
|
||||
local add_success, add_error = registered_exports.addApplication("bankingv2", {
|
||||
local add_success, add_error = high_add_application("bankingv2", {
|
||||
externalUrl = "@high_server_app/ui/index.html",
|
||||
}, {
|
||||
en = {
|
||||
@@ -53,7 +71,7 @@ assert(sent_events[1].event_name == "sky_phone:compat:high:client:syncApplicatio
|
||||
assert(sent_events[1].arguments[1] == "high_server_app", "High sync must preserve the owner")
|
||||
|
||||
invoking_resource = "other_server_app"
|
||||
local duplicate_success, duplicate_error = registered_exports.addApplication("bankingv2", {
|
||||
local duplicate_success, duplicate_error = high_add_application("bankingv2", {
|
||||
externalUrl = "@other_server_app/ui/index.html",
|
||||
})
|
||||
assert(not duplicate_success and duplicate_error == "duplicate_app_id", "High cross-owner replacement must fail")
|
||||
|
||||
Reference in New Issue
Block a user