Files
sky_phone/tests/provider_server_compat.lua
T
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

293 lines
11 KiB
Lua

local event_handlers = {}
local migration_callbacks = {}
local ended_sources = {}
local notification_deliveries = {}
local active_calls = {
[10] = {
id = "550e8400-e29b-41d4-a716-446655440010",
caller = { source = 10, number = "5550101" },
callee = { source = 11, number = "5550111" },
startedAt = 1700000000,
anonymous = false,
video = false,
},
[20] = {
id = "550e8400-e29b-41d4-a716-446655440020",
caller = { source = 20, number = "5550102" },
callee = { source = 21, number = "5550121" },
startedAt = 1700000001,
anonymous = true,
companyId = "police",
video = false,
},
}
Bridge = {
Database = {
AfterMigration = function(name, callback)
assert(name == "sky_phone")
migration_callbacks[#migration_callbacks + 1] = callback
end,
},
Debug = function() end,
}
local phone_core = {
FormatNumber = function(phone_number)
return phone_number
end,
GetEquippedPhoneNumber = function(player_source)
return player_source == 10 and "5550101" or nil
end,
GetSourceFromNumber = function(phone_number)
return ({ ["5550101"] = 10, ["5550102"] = 20 })[tostring(phone_number)]
end,
}
SkyPhoneApps = {}
SkyPhoneCalls = nil
function AddEventHandler(event_name, handler)
local handlers = event_handlers[event_name] or {}
handlers[#handlers + 1] = handler
event_handlers[event_name] = handlers
end
function RegisterNetEvent(event_name, handler)
AddEventHandler(event_name, handler)
end
function GetCurrentResourceName()
return "sky_phone"
end
function GetInvokingResource()
return "provider_test"
end
function TriggerClientEvent()
end
dofile("sky_phone/source/bridge/phones/shared.lua")
dofile("sky_phone/source/bridge/phones/shared/seventeen.lua")
dofile("sky_phone/source/bridge/phones/shared/high.lua")
dofile("sky_phone/source/bridge/phones/server/core.lua")
dofile("sky_phone/source/bridge/phones/server/high.lua")
dofile("sky_phone/source/bridge/phones/server/quasar.lua")
dofile("sky_phone/source/bridge/phones/server/seventeen.lua")
assert(SkyPhone == nil, "provider bridge must load before the migrated phone core exists")
assert(
SkyPhoneCompatibilityServer.Phone == nil,
"provider bridge must not cache a phone service before migration"
)
SkyPhone = phone_core
for index = 1, #migration_callbacks do
migration_callbacks[index]()
end
assert(
SkyPhoneCompatibilityServer.Phone == phone_core,
"provider bridge must bind the migrated phone service"
)
SkyPhoneCalls = {
EndForSource = function(player_source)
ended_sources[#ended_sources + 1] = player_source
if not active_calls[player_source] then
return false, "call_not_found"
end
active_calls[player_source] = nil
return true
end,
GetById = function(call_id)
for _, call in pairs(active_calls) do
if call.id == call_id then
return call
end
end
return nil, "call_not_found"
end,
GetForSource = function(player_source)
local call = active_calls[player_source]
if not call then
return nil, "call_not_found"
end
return call
end,
IsActiveForSource = function(player_source)
return active_calls[player_source] ~= nil
end,
TerminateForSource = function(player_source)
ended_sources[#ended_sources + 1] = player_source
if not active_calls[player_source] then
return false, "call_not_found"
end
active_calls[player_source] = nil
return true
end,
}
SkyPhoneNotifications = {
Send = function(target, notification)
notification_deliveries[#notification_deliveries + 1] = {
notification = notification,
target = target,
}
return { delivered = target.kind == "all" and 2 or 1 }
end,
}
local function get_alias(resource_name, export_name)
local event_name = ("__cfx_export_%s_%s"):format(resource_name, export_name)
local handlers = assert(event_handlers[event_name], "missing alias event: " .. event_name)
local alias_handler
handlers[#handlers](function(handler)
alias_handler = handler
end)
return assert(alias_handler, "missing alias handler: " .. event_name)
end
local function assert_no_return(handler, ...)
assert(table.pack(handler(...)).n == 0, "documented no-return export leaked a return value")
end
local high_end_call = get_alias("high-phone", "endCall")
assert_no_return(high_end_call, 10)
assert(ended_sources[#ended_sources] == 10)
local ended_count = #ended_sources
assert_no_return(high_end_call, "10")
assert(#ended_sources == ended_count, "High Phone must reject string player sources")
active_calls[10] = {
id = "550e8400-e29b-41d4-a716-446655440011",
caller = { source = 10, number = "5550101" },
callee = { source = 11, number = "5550111" },
startedAt = 1700000002,
anonymous = false,
video = false,
}
local quasar_is_in_call = get_alias("qs-smartphone", "isPlayerInCall")
local quasar_end_call = get_alias("qs-smartphone", "endCallBySource")
assert(quasar_is_in_call(10) == true)
assert(quasar_is_in_call("10") == false)
assert_no_return(quasar_end_call, 10)
assert(quasar_is_in_call(10) == false)
ended_count = #ended_sources
assert_no_return(quasar_end_call, 0)
assert(#ended_sources == ended_count, "Quasar must reject invalid player sources")
active_calls[10] = {
id = "550e8400-e29b-41d4-a716-446655440012",
caller = { source = 10, number = "5550101" },
callee = { source = 11, number = "5550111" },
startedAt = 1700000003,
anonymous = false,
video = false,
}
active_calls[20] = {
id = "550e8400-e29b-41d4-a716-446655440020",
caller = { source = 20, number = "5550102" },
callee = { source = 21, number = "5550121" },
startedAt = 1700000001,
anonymous = true,
companyId = "police",
video = false,
}
local mov_end_by_source = get_alias("17mov_Phone", "PhoneApp_EndCallBySrc")
local mov_end_by_number = get_alias("17mov_Phone", "PhoneApp_EndCallByNumber")
local mov_is_in_call_by_source = get_alias("17mov_Phone", "PhoneApp_IsInCallBySrc")
local mov_is_in_call_by_number = get_alias("17mov_Phone", "PhoneApp_IsInCallByNumber")
local mov_get_id_by_source = get_alias("17mov_Phone", "PhoneApp_GetCallIdFromSrc")
local mov_get_id_by_number = get_alias("17mov_Phone", "PhoneApp_GetCallIdFromNumber")
local mov_get_data_by_source = get_alias("17mov_Phone", "PhoneApp_GetCallDataFromSrc")
local mov_get_data_by_number = get_alias("17mov_Phone", "PhoneApp_GetCallDataFromNumber")
local mov_get_data_by_id = get_alias("17mov_Phone", "PhoneApp_GetCallDataFromCallId")
assert(mov_is_in_call_by_source(10) == true)
assert(mov_is_in_call_by_source("10") == false)
assert(mov_is_in_call_by_number("5550102") == true)
assert(mov_is_in_call_by_number("5550999") == false)
assert(mov_get_id_by_source(10) == "550e8400-e29b-41d4-a716-446655440012")
assert(mov_get_id_by_source(30) == nil)
assert(mov_get_id_by_number("5550102") == "550e8400-e29b-41d4-a716-446655440020")
assert(mov_get_id_by_number("5550999") == nil)
local source_data = assert(mov_get_data_by_source(10))
assert(source_data.callId == "550e8400-e29b-41d4-a716-446655440012")
assert(source_data.fromId == 10 and source_data.fromNumber == "5550101")
assert(source_data.toId == 11 and source_data.toNumber == "5550111")
assert(source_data.callTime == 1700000003 and source_data.inCall == true)
assert(source_data.type == "phone" and source_data.isNumberHidden == false)
assert(source_data.isCompanyCall == false)
local number_data = assert(mov_get_data_by_number("5550102"))
assert(number_data.callId == "550e8400-e29b-41d4-a716-446655440020")
assert(number_data.isNumberHidden == true and number_data.isCompanyCall == true)
local id_data = assert(mov_get_data_by_id("550e8400-e29b-41d4-a716-446655440020"))
assert(id_data.callId == number_data.callId and id_data.fromId == 20 and id_data.toId == 21)
assert(mov_get_data_by_id("550e8400-e29b-41d4-a716-446655440099") == nil)
assert(mov_get_data_by_id("not-a-uuid") == nil)
assert(mov_end_by_number("5550102") == true)
assert(mov_end_by_number("5550102") == false)
assert(mov_end_by_source(10) == true)
assert(mov_end_by_source(10) == false)
assert(mov_end_by_source(0) == false)
local mov_notify_source = get_alias("17mov_Phone", "SendNotificationToSrc")
local mov_notify_number = get_alias("17mov_Phone", "SendNotificationToNumber")
local mov_notify_everyone = get_alias("17mov_Phone", "SendNotificationToEveryone")
local mov_notification = {
app = "BANK",
message = "Paid",
title = "Bank",
}
assert_no_return(mov_notify_source, 10, mov_notification)
assert(notification_deliveries[#notification_deliveries].target.kind == "source")
assert(notification_deliveries[#notification_deliveries].target.value == 10)
assert(notification_deliveries[#notification_deliveries].notification.appId == "banking")
assert_no_return(mov_notify_number, "5550102", mov_notification)
assert(notification_deliveries[#notification_deliveries].target.kind == "number")
assert(notification_deliveries[#notification_deliveries].target.value == "5550102")
assert_no_return(mov_notify_everyone, mov_notification)
assert(notification_deliveries[#notification_deliveries].target.kind == "all")
local notification_count = #notification_deliveries
assert_no_return(mov_notify_everyone, {
app = "SYSTEM",
message = { key = "System:Message" },
title = "System",
})
assert(#notification_deliveries == notification_count,
"17Movement locale-object notifications must remain a documented partial")
local high_notify = get_alias("high-phone", "sendNotification")
local high_notification = {
application = { name = "messages" },
content = "High",
duration = 5000,
title = "Phone",
}
assert_no_return(high_notify, -1, high_notification)
assert(notification_deliveries[#notification_deliveries].target.kind == "all")
assert_no_return(high_notify, 10, high_notification)
assert(notification_deliveries[#notification_deliveries].target.kind == "source")
assert(notification_deliveries[#notification_deliveries].target.value == 10)
assert_no_return(high_notify, "5550102", high_notification)
assert(notification_deliveries[#notification_deliveries].target.kind == "number")
assert(notification_deliveries[#notification_deliveries].notification.appId == "messages")
notification_count = #notification_deliveries
assert_no_return(high_notify, true, high_notification)
assert(#notification_deliveries == notification_count, "High must reject ambiguous receiver types")
local quasar_notify = get_alias("qs-smartphone", "sendPhoneNotification")
assert_no_return(quasar_notify, 10, {
appId = "messages",
text = "Ready",
title = "Order",
})
local quasar_delivery = notification_deliveries[#notification_deliveries]
assert(quasar_delivery.target.kind == "source" and quasar_delivery.target.value == 10)
assert(quasar_delivery.notification.appId == "messages")
assert(quasar_delivery.notification.text == "Ready")
print("Provider server compatibility tests passed")