ADD - expand phone application platform

Add the Companies app, resilient call handling, custom app registry, vendor compatibility, storage policies, frontend bridge, tests, documentation, config, locale, and SQL migrations.
This commit is contained in:
Leon.Schmidt
2026-08-11 00:22:39 +02:00
parent 72e5f91e02
commit 42ac955052
77 changed files with 17630 additions and 333 deletions
+80
View File
@@ -0,0 +1,80 @@
dofile("sky_phone/source/shared/custom_app_compat.lua")
local lb_definition = assert(SkyPhoneCompatibility.BuildLbDefinition("lb_app", {
identifier = "dispatch",
name = "Dispatch",
description = "Dispatch terminal",
ui = "ui/index.html",
defaultApp = true,
landscape = true,
onUse = function() end,
}))
assert(lb_definition.id == "dispatch", "LB identifier must map to the Sky app ID")
assert(lb_definition.ui == "ui/index.html", "LB relative UI must remain owner-relative")
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")
local invalid_lb, invalid_lb_error = SkyPhoneCompatibility.BuildLbDefinition("lb_app", {
identifier = "dispatch",
name = "Dispatch",
ui = "@another_resource/ui/index.html",
})
assert(invalid_lb == nil, "LB must reject an @ URL owned by another resource")
assert(invalid_lb_error == "ui_owner_mismatch", "LB owner mismatch must be explicit")
local mov_definition = assert(SkyPhoneCompatibility.Build17MovDefinition({
name = "market",
label = "Market",
ui = "https://cfx-nui-market_app/ui/index.html",
iconBackground = "#112233",
preInstalled = true,
}))
assert(mov_definition.id == "market", "17mov name must map to the Sky app ID")
assert(mov_definition.iconBackground == "#112233", "17mov icon background must be preserved")
assert(mov_definition.defaultInstalled, "17mov preInstalled must be preserved")
local high_definition = assert(SkyPhoneCompatibility.BuildHighDefinition("high_app", "bankingv2", {
externalUrl = "@high_app/ui/index.html",
icon = {
imageUrl = "ui/icon.png",
background = "#1b1b1b",
},
preAdded = true,
}, {
en = {
label = "Banking",
description = "Banking app",
},
}))
assert(high_definition.ui == "high_app/ui/index.html", "High @ URL must normalize to its owner")
assert(high_definition.name.en == "Banking", "High locale labels must be preserved")
assert(high_definition.iconBackground == "#1b1b1b", "High icon background must be preserved")
local quasar_data = {
id = "services",
label = "Services",
iframe = {
url = "https://cfx-nui-services/ui/index.html",
},
}
local quasar_definition = assert(SkyPhoneCompatibility.BuildQuasarDefinition(quasar_data))
local quasar_copy = SkyPhoneCompatibility.CopyQuasarData(quasar_data)
quasar_copy.iframe.url = "changed"
assert(quasar_definition.id == "services", "Quasar ID must be preserved")
assert(quasar_data.iframe.url ~= quasar_copy.iframe.url, "Quasar iframe data must be copied")
local yseries_definition = assert(SkyPhoneCompatibility.BuildYSeriesDefinition({
key = "slots",
name = "Slots",
ui = "https://cfx-nui-slots/ui/index.html",
icon = {
yos = "ui/yos.png",
humanoid = "ui/humanoid.png",
},
game = true,
}))
assert(yseries_definition.id == "slots", "YSeries key must map to the Sky app ID")
assert(yseries_definition.icon == "ui/yos.png", "YSeries must prefer the YOS icon")
assert(yseries_definition.category == "games", "YSeries game flag must be preserved")
print("Custom app compatibility mapping tests passed")
+140
View File
@@ -0,0 +1,140 @@
local registered_exports = {}
local invoking_resource = nil
Config = {
Bridge = {
Locale = "en",
},
CustomApps = {
AllowRemoteOrigins = {},
BundledApps = false,
Enabled = true,
ExternalApps = true,
MaximumMessageBytes = 65536,
MaximumStorageBytesPerApp = 262144,
MaximumStorageKeyLength = 64,
MaximumStorageValueBytes = 65536,
ReadyTimeoutMs = 8000,
TrustedAdapters = {},
},
}
json = {
decode = function()
return {}
end,
encode = function()
return "{}"
end,
}
exports = setmetatable({}, {
__call = function(_, export_name, handler)
registered_exports[export_name] = handler
end,
})
function GetCurrentResourceName()
return "sky_phone"
end
function GetInvokingResource()
return invoking_resource
end
function GetResourceState(resource_name)
if resource_name == "missing_resource" then
return "missing"
end
return "started"
end
function RegisterNUICallback() end
function RegisterNetEvent() end
function AddEventHandler() end
function SendNUIMessage() end
function TriggerServerEvent() end
function TriggerEvent() end
function CreateThread(handler)
handler()
end
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")
dofile("sky_phone/source/client/custom_app_compat.lua")
invoking_resource = "lb_app"
local lb_success, lb_error = registered_exports.AddCustomApp({
identifier = "dispatch",
name = "Dispatch",
description = "Dispatch terminal",
ui = "ui/index.html",
})
assert(lb_success and lb_error == nil, "LB AddCustomApp must register through the shared export")
invoking_resource = "another_app"
local duplicate_success, duplicate_error = registered_exports.AddCustomApp({
identifier = "dispatch",
name = "Hijack",
description = "Hijack",
ui = "ui/index.html",
})
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")
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")
invoking_resource = "yseries_app"
assert(registered_exports.AddCustomApp({
key = "slots",
name = "Slots",
ui = "https://cfx-nui-yseries_app/ui/index.html",
}), "YSeries AddCustomApp must be selected from the key field")
local ambiguous_success, ambiguous_error = registered_exports.AddCustomApp({
id = "ambiguous",
identifier = "ambiguous",
name = "Ambiguous",
ui = "ui/index.html",
})
assert(not ambiguous_success and ambiguous_error == "ambiguous_app_provider", "ambiguous schemas must fail")
invoking_resource = "mov_app"
assert(registered_exports.AddApplication({
name = "market",
label = "Market",
ui = "https://cfx-nui-mov_app/ui/index.html",
}), "17mov AddApplication must register")
invoking_resource = "high_app"
assert(registered_exports.addApplication("bankingv2", {
externalUrl = "@high_app/ui/index.html",
}, {
en = {
label = "Banking",
description = "Banking app",
},
}), "High addApplication must register")
invoking_resource = "quasar_app"
assert(registered_exports.addCustomApp({
id = "services",
label = "Services",
iframe = {
url = "https://cfx-nui-quasar_app/ui/index.html",
},
}), "Quasar addCustomApp must register")
local quasar_apps = registered_exports.getCustomApps()
assert(#quasar_apps == 1 and quasar_apps[1].id == "services", "Quasar getCustomApps must expose the registry")
assert(registered_exports.updateCustomApp("services", {
label = "City Services",
}), "Quasar updateCustomApp must update an owned app")
assert(registered_exports.removeCustomApp("services"), "Quasar removeCustomApp must remove an owned app")
print("Custom app compatibility client tests passed")
+68
View File
@@ -0,0 +1,68 @@
local event_handlers = {}
local registered_exports = {}
local sent_events = {}
local invoking_resource = nil
exports = setmetatable({}, {
__call = function(_, export_name, handler)
registered_exports[export_name] = handler
end,
})
function GetCurrentResourceName()
return "sky_phone"
end
function GetInvokingResource()
return invoking_resource
end
function GetGameTimer()
return 5000
end
function RegisterNetEvent(event_name, handler)
event_handlers[event_name] = handler
end
function AddEventHandler() end
function TriggerClientEvent(event_name, target, ...)
sent_events[#sent_events + 1] = {
arguments = { ... },
event_name = event_name,
target = target,
}
end
dofile("sky_phone/source/shared/custom_app_compat.lua")
dofile("sky_phone/source/server/custom_app_compat.lua")
invoking_resource = "high_server_app"
local add_success, add_error = registered_exports.addApplication("bankingv2", {
externalUrl = "@high_server_app/ui/index.html",
}, {
en = {
label = "Banking",
description = "Banking app",
},
})
assert(add_success and add_error == nil, "High server addApplication must register")
assert(#sent_events == 1, "High server registration must broadcast an update")
assert(sent_events[1].event_name == "sky_phone:compat:high:client:syncApplication", "High sync event must be used")
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", {
externalUrl = "@other_server_app/ui/index.html",
})
assert(not duplicate_success and duplicate_error == "duplicate_app_id", "High cross-owner replacement must fail")
source = 42
event_handlers["sky_phone:compat:high:server:requestSnapshot"]()
assert(#sent_events == 2, "High snapshot request must emit one response")
assert(sent_events[2].event_name == "sky_phone:compat:high:client:replaceSnapshot", "High snapshot event must be used")
assert(sent_events[2].target == 42, "High snapshot must target only the requester")
assert(#sent_events[2].arguments[1] == 1, "High snapshot must include the registered app")
print("Custom app compatibility server tests passed")