Files
Leon.Schmidt 30559048fd FIX - complete LB custom app compatibility (#11)
* FIX - preserve LB custom app lifecycle

* FIX - reset LB export aliases before startup

* FIX - report competing custom app providers

* FIX - identify LB app frames as live NUI

* FIX - complete LB custom app compatibility

* FIX - provide LB PicChat runtime exports

* FIX - route LB PicChat through Sky Phone

* FIX - return cached LB equipped phone number

* FIX - harden PicChat compatibility migration

* ENH - complete modular phone provider and Creator APIs

* DOC - document the Sky Phone Creator API

---------

Co-authored-by: DerEchteAlec <bycraky@gmail.com>
Co-authored-by: DerEchteAlec <alec.schitzkat@luwan.io>
2026-08-20 18:19:16 +02:00

152 lines
4.8 KiB
Lua

local invoking_resource = "creator_resource"
local client_events = {}
local encoded_value
local notification_requests = {}
Config = {
CustomApps = {
BundledApps = false,
Enabled = true,
ExternalApps = true,
MaximumMessageBytes = 64,
TrustedAdapters = {},
},
}
json = {
decode = function(encoded)
if encoded:sub(1, 1) == '"' then
return encoded:sub(2, -2)
end
return encoded_value
end,
encode = function(value)
if type(value) == "function" then
error("unsupported")
end
if type(value) == "string" then
return '"' .. value .. '"'
end
encoded_value = value
return "{}"
end,
}
SkyPhoneNotifications = {
Send = function(target, notification)
notification_requests[#notification_requests + 1] = {
notification = notification,
target = target,
}
return { delivered = target.value == 10 and 1 or 0 }
end,
}
Bridge = {
Debug = function() end,
Framework = {
GetIdentifier = function(source)
return source == 10 and "license:online" or nil
end,
},
}
function AddEventHandler() end
function GetCurrentResourceName() return "sky_phone" end
function GetInvokingResource() return invoking_resource end
function GetResourceState() return "started" end
function TriggerClientEvent(name, target, ...)
client_events[#client_events + 1] = {
arguments = { ... },
name = name,
target = target,
}
end
assert(loadfile("sky_phone/source/shared/custom_apps.lua"))()
assert(loadfile("sky_phone/source/server/custom_apps.lua"))()
local api = assert(SkyPhoneApps.ServerPublicApi)
local capabilities = api.GetCustomAppCapabilities()
assert(capabilities.enabled and capabilities.externalApps)
assert(capabilities.messageDispatch and capabilities.notificationDispatch)
assert(api.AddCustomAppPolicy({
id = "creator-app",
permissions = { "app.open", "notifications" },
}))
assert(api.SendAppMessage(10, "creator-app", {
action = "refresh",
revision = 2,
}))
assert(#client_events == 1, "valid server app messages must emit exactly one client event")
assert(client_events[1].name == "sky_phone:custom-app:message")
assert(client_events[1].target == 10)
assert(client_events[1].arguments[1] == "creator_resource",
"the server must bind messages to the invoking owner resource")
assert(client_events[1].arguments[2] == "creator-app")
assert(client_events[1].arguments[3].action == "refresh")
local notification_success, notification_result = api.SendCustomAppNotification(
10,
"creator-app",
{
title = "Creator",
content = "Updated",
}
)
assert(notification_success and notification_result.delivered == 1)
assert(#notification_requests == 1)
assert(notification_requests[1].target.kind == "source")
assert(notification_requests[1].target.value == 10)
assert(notification_requests[1].notification.appId == "creator-app")
assert(notification_requests[1].notification.text == "Updated")
notification_success, notification_result = api.SendCustomAppNotification(
20,
"creator-app",
{
title = "Creator",
text = "Offline",
}
)
assert(not notification_success and notification_result == "device_not_equipped")
notification_success, notification_result = api.SendCustomAppNotification(
10,
"creator-app",
{
appId = "other-app",
title = "Creator",
text = "Spoofed",
}
)
assert(not notification_success and notification_result == "invalid_app_id")
local success, error_code = api.SendCustomAppMessage("10", "creator-app", {})
assert(not success and error_code == "invalid_source", "message targets must remain strictly typed")
success, error_code = api.SendCustomAppMessage(20, "creator-app", {})
assert(not success and error_code == "player_unavailable", "offline message targets must fail visibly")
success, error_code = api.SendCustomAppMessage(10, "creator-app", string.rep("x", 65))
assert(not success and error_code == "payload_too_large", "message payload limits must be server enforced")
success, error_code = api.SendCustomAppMessage(10, "creator-app", function() end)
assert(not success and error_code == "invalid_payload", "non-JSON message payloads must be rejected")
invoking_resource = "other_resource"
success, error_code = api.SendCustomAppMessage(10, "creator-app", {})
assert(not success and error_code == "app_owner_mismatch",
"a different resource must not message another owner's app")
assert(#client_events == 1, "rejected messages must not reach a client")
notification_success, notification_result = api.SendCustomAppNotification(
10,
"creator-app",
{ title = "Creator", text = "Spoofed" }
)
assert(not notification_success and notification_result == "app_owner_mismatch")
print("Custom app server messaging tests passed")