mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-09-04 09:13:24 +00:00
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>
This commit is contained in:
co-authored by
DerEchteAlec
DerEchteAlec
parent
9aec3eefef
commit
30559048fd
@@ -1,8 +1,22 @@
|
||||
local event_handlers = {}
|
||||
local migration_callbacks = {}
|
||||
local registered_event_handlers = {}
|
||||
local registered_exports = {}
|
||||
local sent_events = {}
|
||||
local triggered_events = {}
|
||||
local invoking_resource = nil
|
||||
local seam_calls = {
|
||||
equipped_number = {},
|
||||
formatted_number = {},
|
||||
source_from_number = {},
|
||||
}
|
||||
local directory_calls = {
|
||||
online_by_identifier = {},
|
||||
online_by_imei = {},
|
||||
online_by_source = {},
|
||||
stored_by_imei = {},
|
||||
stored_by_phone_number = {},
|
||||
}
|
||||
|
||||
exports = setmetatable({}, {
|
||||
__call = function(_, export_name, handler)
|
||||
@@ -40,21 +54,362 @@ function TriggerClientEvent(event_name, target, ...)
|
||||
}
|
||||
end
|
||||
|
||||
dofile("sky_phone/source/shared/custom_app_compat.lua")
|
||||
dofile("sky_phone/source/server/custom_app_compat.lua")
|
||||
function TriggerEvent(event_name, ...)
|
||||
triggered_events[#triggered_events + 1] = {
|
||||
arguments = { ... },
|
||||
event_name = event_name,
|
||||
}
|
||||
end
|
||||
|
||||
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
|
||||
dofile("sky_phone/source/shared/imei.lua")
|
||||
dofile("sky_phone/source/bridge/shared.lua")
|
||||
dofile("sky_phone/source/bridge/phones/shared.lua")
|
||||
dofile("sky_phone/source/bridge/phones/shared/lb.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/shared/quasar.lua")
|
||||
dofile("sky_phone/source/bridge/phones/shared/yseries.lua")
|
||||
Bridge.Database = {
|
||||
AfterMigration = function(_, callback)
|
||||
migration_callbacks[#migration_callbacks + 1] = callback
|
||||
end,
|
||||
})
|
||||
alias_handlers[1](export_callback)
|
||||
assert(type(high_add_application) == "function", "Invalid high-phone:addApplication export alias")
|
||||
}
|
||||
local phone_core = {
|
||||
FormatNumber = function(value)
|
||||
seam_calls.formatted_number[#seam_calls.formatted_number + 1] = value
|
||||
return "formatted:" .. value
|
||||
end,
|
||||
GetEquippedPhoneNumber = function(player)
|
||||
seam_calls.equipped_number[#seam_calls.equipped_number + 1] = player
|
||||
if player == 42 then
|
||||
return "5550000042"
|
||||
end
|
||||
if player == 73 then
|
||||
return "5550000073"
|
||||
end
|
||||
if player == "char1:phone-owner" then
|
||||
return "5550000099"
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
GetSourceFromNumber = function(phone_number)
|
||||
seam_calls.source_from_number[#seam_calls.source_from_number + 1] = phone_number
|
||||
if tostring(phone_number) == "5550000073" then
|
||||
return 73
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
}
|
||||
SkyPhoneDeviceDirectory = {
|
||||
GetOnlineByIdentifier = function(identifier)
|
||||
directory_calls.online_by_identifier[#directory_calls.online_by_identifier + 1] = identifier
|
||||
if identifier == "char1:directory-owner" then
|
||||
return { source = 85 }
|
||||
end
|
||||
return nil, "player_unavailable"
|
||||
end,
|
||||
GetOnlineByImei = function(phone_imei)
|
||||
directory_calls.online_by_imei[#directory_calls.online_by_imei + 1] = phone_imei
|
||||
if phone_imei == "123456789012347" then
|
||||
return { source = 84 }
|
||||
end
|
||||
return nil, "player_unavailable"
|
||||
end,
|
||||
GetOnlineBySource = function(player_source)
|
||||
directory_calls.online_by_source[#directory_calls.online_by_source + 1] = player_source
|
||||
if player_source == 88 then
|
||||
return { imei = "523456789012343" }
|
||||
end
|
||||
return nil, "player_unavailable"
|
||||
end,
|
||||
GetStoredDeviceByImei = function(phone_imei)
|
||||
directory_calls.stored_by_imei[#directory_calls.stored_by_imei + 1] = phone_imei
|
||||
if phone_imei == "223456789012346" then
|
||||
return { phoneNumber = "5550000086" }
|
||||
end
|
||||
return nil, "device_not_found"
|
||||
end,
|
||||
GetStoredDeviceByPhoneNumber = function(phone_number)
|
||||
directory_calls.stored_by_phone_number[#directory_calls.stored_by_phone_number + 1]
|
||||
= phone_number
|
||||
if phone_number == "5550000087" then
|
||||
return { imei = "423456789012344" }
|
||||
end
|
||||
return nil, "device_not_found"
|
||||
end,
|
||||
}
|
||||
dofile("sky_phone/source/bridge/phones/server/core.lua")
|
||||
dofile("sky_phone/source/bridge/phones/server/lb.lua")
|
||||
dofile("sky_phone/source/bridge/phones/server/seventeen.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/yseries.lua")
|
||||
dofile("sky_phone/source/bridge/phones/server/lifecycle.lua")
|
||||
|
||||
assert(SkyPhone == nil, "all provider adapters must load before the phone core is ready")
|
||||
assert(
|
||||
SkyPhoneCompatibilityServer.Phone == nil,
|
||||
"provider adapters must not cache the phone core before migration"
|
||||
)
|
||||
SkyPhone = phone_core
|
||||
for index = 1, #migration_callbacks do
|
||||
migration_callbacks[index]()
|
||||
end
|
||||
assert(
|
||||
SkyPhoneCompatibilityServer.Phone == phone_core,
|
||||
"all provider adapters must bind the migrated phone core"
|
||||
)
|
||||
|
||||
local function get_alias_export(resource_name, export_name)
|
||||
local alias_handlers = registered_event_handlers[
|
||||
("__cfx_export_%s_%s"):format(resource_name, export_name)
|
||||
]
|
||||
assert(alias_handlers and #alias_handlers == 1, ("Missing %s:%s export alias"):format(
|
||||
resource_name,
|
||||
export_name
|
||||
))
|
||||
|
||||
local export_handler
|
||||
local export_callback = setmetatable({
|
||||
__cfx_functionReference = "test-export-callback",
|
||||
}, {
|
||||
__call = function(_, handler)
|
||||
export_handler = handler
|
||||
end,
|
||||
})
|
||||
alias_handlers[1](export_callback)
|
||||
assert(type(export_handler) == "function", ("Invalid %s:%s export alias"):format(
|
||||
resource_name,
|
||||
export_name
|
||||
))
|
||||
return export_handler
|
||||
end
|
||||
|
||||
local function invoke_event_handlers(event_name, ...)
|
||||
local handlers = registered_event_handlers[event_name]
|
||||
assert(handlers and #handlers > 0, ("Missing %s event handler"):format(event_name))
|
||||
for index = 1, #handlers do
|
||||
handlers[index](...)
|
||||
end
|
||||
end
|
||||
|
||||
assert(triggered_events[1].event_name == "onServerResourceStop", "LB server export caches must be invalidated")
|
||||
assert(triggered_events[1].arguments[1] == "lb-phone", "LB server stop must use the provided resource name")
|
||||
assert(triggered_events[2].event_name == "onResourceStop", "LB generic stop listeners must be notified")
|
||||
assert(triggered_events[3].event_name == "onServerResourceStart", "LB server start listeners must be notified")
|
||||
assert(triggered_events[4].event_name == "onResourceStart", "LB generic start listeners must be notified")
|
||||
|
||||
local lifecycle_event_offset = #triggered_events
|
||||
invoke_event_handlers("sky_phone:server:phoneNumberChanged", 42, "5550000042")
|
||||
invoke_event_handlers("sky_phone:server:phoneNumberGenerated", 42, "5550000043")
|
||||
invoke_event_handlers("sky_phone:server:factoryReset", 42, "5550000044")
|
||||
invoke_event_handlers(
|
||||
"sky_phone:server:galleryMediaDeleted",
|
||||
42,
|
||||
"5550000042",
|
||||
"https://media.example/deleted.png"
|
||||
)
|
||||
assert(
|
||||
triggered_events[lifecycle_event_offset + 1].event_name == "lb-phone:numberChanged",
|
||||
"LB number-change observer event must be preserved"
|
||||
)
|
||||
assert(
|
||||
triggered_events[lifecycle_event_offset + 2].event_name == "lb-phone:phoneNumberGenerated",
|
||||
"LB number-generation observer event must be preserved"
|
||||
)
|
||||
assert(
|
||||
triggered_events[lifecycle_event_offset + 3].event_name == "lb-phone:factoryReset",
|
||||
"LB factory-reset observer event must be preserved"
|
||||
)
|
||||
assert(
|
||||
triggered_events[lifecycle_event_offset + 4].event_name == "lb-phone:deletedFromGallery",
|
||||
"LB gallery-deletion observer event must be preserved"
|
||||
)
|
||||
assert(
|
||||
triggered_events[lifecycle_event_offset + 4].arguments[3]
|
||||
== "https://media.example/deleted.png",
|
||||
"LB gallery observer must preserve the deleted link"
|
||||
)
|
||||
|
||||
local lb_get_equipped_number = get_alias_export("lb-phone", "GetEquippedPhoneNumber")
|
||||
assert(
|
||||
lb_get_equipped_number(42) == "5550000042",
|
||||
"LB source lookup must use the equipped-number seam"
|
||||
)
|
||||
assert(
|
||||
lb_get_equipped_number("char1:phone-owner") == "5550000099",
|
||||
"LB identifier lookup must preserve its documented string contract"
|
||||
)
|
||||
assert(next(registered_exports) == nil, "Vendor exports must only be registered as provider aliases")
|
||||
|
||||
local seventeen_get_number_from_player = get_alias_export("17mov_Phone", "GetNumberFromPlayer")
|
||||
local seventeen_get_number_from_identifier = get_alias_export(
|
||||
"17mov_Phone",
|
||||
"GetNumberFromIdentifier"
|
||||
)
|
||||
local seventeen_get_source_from_number = get_alias_export(
|
||||
"17mov_Phone",
|
||||
"GetPlayerSrcFromActiveNumber"
|
||||
)
|
||||
assert(
|
||||
seventeen_get_number_from_player(42) == "5550000042",
|
||||
"17Movement source lookup must use the equipped-number seam"
|
||||
)
|
||||
assert(
|
||||
seventeen_get_number_from_identifier("char1:phone-owner") == "5550000099",
|
||||
"17Movement identifier lookup must use the equipped-number seam"
|
||||
)
|
||||
assert(
|
||||
seventeen_get_source_from_number("5550000073") == 73,
|
||||
"17Movement active-number lookup must use the source seam"
|
||||
)
|
||||
|
||||
local equipped_calls_before_invalid = #seam_calls.equipped_number
|
||||
assert(seventeen_get_number_from_player("42") == nil, "17Movement must reject string sources")
|
||||
assert(
|
||||
seventeen_get_number_from_identifier(" ") == nil,
|
||||
"17Movement must reject blank identifiers"
|
||||
)
|
||||
assert(
|
||||
#seam_calls.equipped_number == equipped_calls_before_invalid,
|
||||
"Invalid 17Movement identity arguments must not reach the phone core"
|
||||
)
|
||||
|
||||
local source_calls_before_invalid = #seam_calls.source_from_number
|
||||
assert(
|
||||
seventeen_get_source_from_number({}) == nil,
|
||||
"17Movement must reject non-scalar phone numbers"
|
||||
)
|
||||
assert(
|
||||
#seam_calls.source_from_number == source_calls_before_invalid,
|
||||
"Invalid 17Movement phone numbers must not reach the phone core"
|
||||
)
|
||||
|
||||
local high_get_player_phone_number = get_alias_export("high-phone", "getPlayerPhoneNumber")
|
||||
local high_format_number = get_alias_export("high-phone", "formatNumber")
|
||||
assert(
|
||||
high_get_player_phone_number(42) == "5550000042",
|
||||
"High source lookup must use the equipped-number seam"
|
||||
)
|
||||
assert(
|
||||
high_format_number("5550000042") == "formatted:5550000042",
|
||||
"High number formatting must use the configured formatting seam"
|
||||
)
|
||||
local formatted_calls_before_invalid = #seam_calls.formatted_number
|
||||
assert(high_format_number(5550000042) == nil, "High formatNumber must require a string")
|
||||
assert(
|
||||
#seam_calls.formatted_number == formatted_calls_before_invalid,
|
||||
"Invalid High numbers must not reach the formatter"
|
||||
)
|
||||
|
||||
local quasar_get_current_phone_number = get_alias_export(
|
||||
"qs-smartphone",
|
||||
"GetCurrentPhoneNumber"
|
||||
)
|
||||
assert(
|
||||
quasar_get_current_phone_number(42) == "5550000042",
|
||||
"Quasar source lookup must use the equipped-number seam"
|
||||
)
|
||||
assert(quasar_get_current_phone_number(0) == nil, "Quasar must reject invalid sources")
|
||||
|
||||
local yseries_get_source = get_alias_export("yseries", "GetPlayerSourceIdByPhoneNumber")
|
||||
local yseries_get_number = get_alias_export("yseries", "GetPhoneNumberBySourceId")
|
||||
local yseries_get_source_by_imei = get_alias_export(
|
||||
"yseries",
|
||||
"GetPlayerSourceIdByPhoneImei"
|
||||
)
|
||||
local yseries_get_source_by_identifier = get_alias_export(
|
||||
"yseries",
|
||||
"GetPlayerSourceIdByIdentifier"
|
||||
)
|
||||
local yseries_get_number_by_imei = get_alias_export("yseries", "GetPhoneNumberByImei")
|
||||
local yseries_get_imei_by_number = get_alias_export(
|
||||
"yseries",
|
||||
"GetPhoneImeiByPhoneNumber"
|
||||
)
|
||||
local yseries_get_imei_by_source = get_alias_export("yseries", "GetPhoneImeiBySourceId")
|
||||
assert(
|
||||
yseries_get_source(5550000073) == 73,
|
||||
"YSeries number lookup must accept its documented scalar number form"
|
||||
)
|
||||
assert(
|
||||
yseries_get_number(73) == "5550000073",
|
||||
"YSeries source lookup must use the equipped-number seam"
|
||||
)
|
||||
assert(yseries_get_source(math.huge) == nil, "YSeries must reject non-finite phone numbers")
|
||||
assert(yseries_get_number(73.5) == nil, "YSeries must reject fractional sources")
|
||||
assert(
|
||||
yseries_get_source_by_imei("123456789012347") == 84,
|
||||
"YSeries IMEI-to-source lookup must use the online device directory"
|
||||
)
|
||||
assert(
|
||||
yseries_get_source_by_identifier(" char1:directory-owner ") == 85,
|
||||
"YSeries identifier-to-source lookup must use the normalized online directory identity"
|
||||
)
|
||||
assert(
|
||||
yseries_get_number_by_imei("223456789012346") == "5550000086",
|
||||
"YSeries IMEI-to-number lookup must use the stored device directory"
|
||||
)
|
||||
assert(
|
||||
yseries_get_imei_by_number("5550000087") == "423456789012344",
|
||||
"YSeries number-to-IMEI lookup must use the stored device directory"
|
||||
)
|
||||
assert(
|
||||
yseries_get_imei_by_source(88) == "523456789012343",
|
||||
"YSeries source-to-IMEI lookup must use the online device directory"
|
||||
)
|
||||
|
||||
assert(
|
||||
yseries_get_source_by_imei("999999999999994") == nil,
|
||||
"YSeries IMEI-to-source lookup must preserve a missing directory result as nil"
|
||||
)
|
||||
assert(
|
||||
yseries_get_source_by_identifier("char1:missing") == nil,
|
||||
"YSeries identifier-to-source lookup must preserve a missing directory result as nil"
|
||||
)
|
||||
assert(
|
||||
yseries_get_number_by_imei("888888888888885") == nil,
|
||||
"YSeries IMEI-to-number lookup must preserve a missing directory result as nil"
|
||||
)
|
||||
assert(
|
||||
yseries_get_imei_by_number("5550000999") == nil,
|
||||
"YSeries number-to-IMEI lookup must preserve a missing directory result as nil"
|
||||
)
|
||||
assert(
|
||||
yseries_get_imei_by_source(999) == nil,
|
||||
"YSeries source-to-IMEI lookup must preserve a missing directory result as nil"
|
||||
)
|
||||
|
||||
local directory_calls_before_invalid = {
|
||||
online_by_identifier = #directory_calls.online_by_identifier,
|
||||
online_by_imei = #directory_calls.online_by_imei,
|
||||
online_by_source = #directory_calls.online_by_source,
|
||||
stored_by_imei = #directory_calls.stored_by_imei,
|
||||
stored_by_phone_number = #directory_calls.stored_by_phone_number,
|
||||
}
|
||||
assert(yseries_get_source_by_imei(123456789012345) == nil, "YSeries must reject numeric IMEIs")
|
||||
assert(
|
||||
yseries_get_source_by_identifier(string.rep("x", 81)) == nil,
|
||||
"YSeries must reject overlong identifiers"
|
||||
)
|
||||
assert(yseries_get_number_by_imei("invalid-imei") == nil, "YSeries must reject invalid IMEIs")
|
||||
assert(
|
||||
yseries_get_imei_by_number(5550000087) == nil,
|
||||
"YSeries number-to-IMEI must require its documented string argument"
|
||||
)
|
||||
assert(yseries_get_imei_by_source(88.5) == nil, "YSeries must reject fractional sources")
|
||||
assert(
|
||||
#directory_calls.online_by_imei == directory_calls_before_invalid.online_by_imei
|
||||
and #directory_calls.online_by_identifier
|
||||
== directory_calls_before_invalid.online_by_identifier
|
||||
and #directory_calls.stored_by_imei == directory_calls_before_invalid.stored_by_imei
|
||||
and #directory_calls.stored_by_phone_number
|
||||
== directory_calls_before_invalid.stored_by_phone_number
|
||||
and #directory_calls.online_by_source == directory_calls_before_invalid.online_by_source,
|
||||
"Invalid YSeries identity arguments must not reach the device directory"
|
||||
)
|
||||
|
||||
local high_add_application = get_alias_export("high-phone", "addApplication")
|
||||
|
||||
invoking_resource = "high_server_app"
|
||||
local add_success, add_error = high_add_application("bankingv2", {
|
||||
@@ -83,4 +438,26 @@ assert(sent_events[2].event_name == "sky_phone:compat:high:client:replaceSnapsho
|
||||
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")
|
||||
|
||||
event_handlers["sky_phone:compat:high:server:requestSnapshot"]()
|
||||
assert(#sent_events == 2, "High snapshot rate limiting must remain unchanged")
|
||||
|
||||
invoke_event_handlers("onResourceStop", "high_server_app")
|
||||
assert(#sent_events == 3, "Stopping a High app owner must broadcast one removal")
|
||||
assert(
|
||||
sent_events[3].event_name == "sky_phone:compat:high:client:removeApplication",
|
||||
"High owner cleanup must preserve the removal event"
|
||||
)
|
||||
assert(sent_events[3].arguments[2] == "bankingv2", "High owner cleanup must remove its app")
|
||||
|
||||
local shutdown_event_offset = #triggered_events
|
||||
invoke_event_handlers("onResourceStop", "sky_phone")
|
||||
assert(
|
||||
triggered_events[shutdown_event_offset + 1].event_name == "onServerResourceStop",
|
||||
"Sky Phone shutdown must invalidate the LB server cache"
|
||||
)
|
||||
assert(
|
||||
triggered_events[shutdown_event_offset + 2].event_name == "onResourceStop",
|
||||
"Sky Phone shutdown must notify LB generic stop listeners"
|
||||
)
|
||||
|
||||
print("Custom app compatibility server tests passed")
|
||||
|
||||
Reference in New Issue
Block a user