mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-28 23:01:37 +00:00
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:
@@ -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")
|
||||
@@ -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")
|
||||
@@ -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")
|
||||
Reference in New Issue
Block a user