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
@@ -0,0 +1,240 @@
|
||||
local migration_callback
|
||||
local client_events = {}
|
||||
local registered_callbacks = {}
|
||||
local players = {}
|
||||
local source_lookup_counts = {}
|
||||
local volatile_source
|
||||
|
||||
Config = {
|
||||
Sim = {
|
||||
NumberLength = 6,
|
||||
NumberPrefix = "",
|
||||
},
|
||||
}
|
||||
|
||||
local online_by_source = {
|
||||
[10] = {
|
||||
imei = "123456789012347",
|
||||
phoneNumber = "555010",
|
||||
source = 10,
|
||||
},
|
||||
[30] = {
|
||||
imei = "223456789012346",
|
||||
phoneNumber = "555030",
|
||||
source = 30,
|
||||
},
|
||||
}
|
||||
|
||||
Bridge = {
|
||||
Callbacks = {
|
||||
Register = function(name, handler)
|
||||
registered_callbacks[name] = handler
|
||||
end,
|
||||
},
|
||||
Database = {
|
||||
AfterMigration = function(name, callback)
|
||||
assert(name == "sky_phone")
|
||||
migration_callback = callback
|
||||
end,
|
||||
Query = function(query, parameters)
|
||||
assert(query:find("sky_phone_devices", 1, true), "device context query changed")
|
||||
local imei = parameters[1]
|
||||
local identities = {
|
||||
["123456789012347"] = {
|
||||
device_name = "Phone 10",
|
||||
imei = "123456789012347",
|
||||
settings = [[{"version":1,"source":10}]],
|
||||
},
|
||||
["223456789012346"] = {
|
||||
device_name = "Phone 30",
|
||||
imei = "223456789012346",
|
||||
settings = [[{"version":1,"source":30}]],
|
||||
},
|
||||
["323456789012345"] = {
|
||||
device_name = "Phone 40",
|
||||
imei = "323456789012345",
|
||||
settings = [[{"version":1,"source":40}]],
|
||||
},
|
||||
}
|
||||
return identities[imei] and { identities[imei] } or {}
|
||||
end,
|
||||
},
|
||||
Debug = function() end,
|
||||
Framework = {
|
||||
GetPlayers = function()
|
||||
return players
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
||||
SkyPhoneApps = {
|
||||
ValidateAppId = function(app_id)
|
||||
return type(app_id) == "string"
|
||||
and #app_id <= 64
|
||||
and app_id:match("^[a-z0-9][a-z0-9._-]+$") ~= nil
|
||||
end,
|
||||
}
|
||||
|
||||
SkyPhoneDeviceDirectory = {
|
||||
GetOnlineBySource = function(player_source)
|
||||
source_lookup_counts[player_source] = (source_lookup_counts[player_source] or 0) + 1
|
||||
if player_source == volatile_source and source_lookup_counts[player_source] >= 3 then
|
||||
return nil, "device_not_equipped"
|
||||
end
|
||||
return online_by_source[player_source]
|
||||
end,
|
||||
GetOnlineByPhoneNumber = function(phone_number)
|
||||
if phone_number == "555030" then
|
||||
return online_by_source[30]
|
||||
end
|
||||
if phone_number == "555099" then
|
||||
return {
|
||||
imei = "223456789012346",
|
||||
phoneNumber = "555099",
|
||||
source = 30,
|
||||
}
|
||||
end
|
||||
return nil, "player_unavailable"
|
||||
end,
|
||||
GetOnlineByImei = function(imei)
|
||||
if imei == "123456789012347" then
|
||||
return online_by_source[10]
|
||||
end
|
||||
return nil, "player_unavailable"
|
||||
end,
|
||||
}
|
||||
|
||||
function TriggerClientEvent(name, target, payload)
|
||||
client_events[#client_events + 1] = {
|
||||
name = name,
|
||||
payload = payload,
|
||||
target = target,
|
||||
}
|
||||
end
|
||||
|
||||
assert(loadfile("sky_phone/source/shared/imei.lua"))()
|
||||
assert(loadfile("sky_phone/source/shared/sim_number.lua"))()
|
||||
assert(loadfile("sky_phone/source/server/notifications.lua"))()
|
||||
assert(type(migration_callback) == "function", "notification router must wait for migrations")
|
||||
migration_callback()
|
||||
local self_notification = assert(registered_callbacks["sky_phone:notifications:self"],
|
||||
"self notification callback must be registered after migration")
|
||||
|
||||
local notification = {
|
||||
app_id = "messages",
|
||||
body = " Body ",
|
||||
title = " Title ",
|
||||
url = "https://invalid.example",
|
||||
}
|
||||
|
||||
local result, error_code = SkyPhoneNotifications.Send(
|
||||
{ kind = "source", value = "10" },
|
||||
notification
|
||||
)
|
||||
assert(result == nil and error_code == "invalid_target", "source targets must remain strictly typed")
|
||||
assert(#client_events == 0, "invalid targets must not deliver")
|
||||
|
||||
result, error_code = SkyPhoneNotifications.Send(
|
||||
{ kind = "number", value = "not-a-number" },
|
||||
notification
|
||||
)
|
||||
assert(result == nil and error_code == "invalid_target", "malformed numbers must be rejected")
|
||||
|
||||
result, error_code = SkyPhoneNotifications.Send(
|
||||
{ kind = "device", value = "invalid-imei" },
|
||||
notification
|
||||
)
|
||||
assert(result == nil and error_code == "invalid_target", "malformed IMEIs must be rejected")
|
||||
|
||||
result, error_code = SkyPhoneNotifications.Send(
|
||||
{ kind = "source", value = 10 },
|
||||
{
|
||||
appId = "messages",
|
||||
title = string.rep("x", 161),
|
||||
text = "Body",
|
||||
}
|
||||
)
|
||||
assert(result == nil and error_code == "invalid_title", "server payload bounds must be enforced")
|
||||
|
||||
source_lookup_counts = {}
|
||||
result = assert(SkyPhoneNotifications.Send({ kind = "source", value = 10 }, notification))
|
||||
assert(result.delivered == 1, "equipped source target must receive one notification")
|
||||
assert(client_events[1].name == "sky_phone:notifications:show", "notification event name changed")
|
||||
assert(client_events[1].target == 10, "notification source target changed")
|
||||
assert(client_events[1].payload.appId == "messages", "app_id alias was not normalized")
|
||||
assert(client_events[1].payload.title == "Title" and client_events[1].payload.text == "Body",
|
||||
"notification text was not normalized")
|
||||
assert(client_events[1].payload.url == nil, "unsafe fields must not cross the server boundary")
|
||||
assert(client_events[1].payload.device.imei == "123456789012347",
|
||||
"the final equipped IMEI must be attached")
|
||||
assert(client_events[1].payload.device.name == "Phone 10",
|
||||
"the final device name must be attached")
|
||||
assert(client_events[1].payload.device.settings == [[{"version":1,"source":10}]],
|
||||
"the final device settings must be attached")
|
||||
assert(source_lookup_counts[10] == 3, "delivery must revalidate after loading device context")
|
||||
|
||||
source_lookup_counts = {}
|
||||
local self_result = self_notification(10, notification)
|
||||
assert(self_result.success and self_result.data.delivered == 1,
|
||||
"self notifications must resolve the caller's equipped device")
|
||||
assert(client_events[#client_events].payload.device.imei == "123456789012347",
|
||||
"self notifications must carry the authoritative equipped IMEI")
|
||||
client_events[#client_events] = nil
|
||||
|
||||
local unavailable_self_result = self_notification(20, notification)
|
||||
assert(not unavailable_self_result.success and unavailable_self_result.error == "device_not_equipped",
|
||||
"self notifications without an equipped device must fail visibly")
|
||||
|
||||
source_lookup_counts = {}
|
||||
result = assert(SkyPhoneNotifications.Send({ kind = "source", value = 20 }, notification))
|
||||
assert(result.delivered == 0, "players without an equipped device must not receive notifications")
|
||||
assert(#client_events == 1, "no-delivery results must not emit an event")
|
||||
|
||||
source_lookup_counts = {}
|
||||
result = assert(SkyPhoneNotifications.Send({ kind = "number", value = "555099" }, notification))
|
||||
assert(result.delivered == 0, "changed phone-number ownership must fail revalidation")
|
||||
assert(#client_events == 1, "failed number revalidation must not deliver")
|
||||
|
||||
source_lookup_counts = {}
|
||||
result = assert(SkyPhoneNotifications.Send(
|
||||
{ kind = "device", value = "123456789012347" },
|
||||
notification
|
||||
))
|
||||
assert(result.delivered == 1, "equipped device targets must deliver")
|
||||
assert(client_events[2].target == 10, "device target resolved to the wrong source")
|
||||
assert(client_events[2].payload.device.imei == "123456789012347")
|
||||
|
||||
players = { 40 }
|
||||
online_by_source[40] = {
|
||||
imei = "323456789012345",
|
||||
phoneNumber = "555040",
|
||||
source = 40,
|
||||
}
|
||||
source_lookup_counts = {}
|
||||
volatile_source = 40
|
||||
result = assert(SkyPhoneNotifications.Send({ kind = "all" }, notification))
|
||||
assert(result.delivered == 0, "a device switch during delivery must fail final revalidation")
|
||||
assert(#client_events == 2, "stale broadcast candidates must not receive notifications")
|
||||
|
||||
players = { "10", 10, 20, 30, "30" }
|
||||
source_lookup_counts = {}
|
||||
volatile_source = nil
|
||||
result = assert(SkyPhoneNotifications.Send({ kind = "all" }, notification))
|
||||
assert(result.delivered == 2, "broadcast must deduplicate equipped online players")
|
||||
assert(#client_events == 4, "broadcast count must match emitted events")
|
||||
|
||||
local broadcast_targets = {}
|
||||
for index = 1, #client_events do
|
||||
local event = client_events[index]
|
||||
assert(event.target ~= -1, "notification broadcasts must never use blind -1 delivery")
|
||||
if index >= 3 then
|
||||
broadcast_targets[event.target] = (broadcast_targets[event.target] or 0) + 1
|
||||
end
|
||||
end
|
||||
assert(broadcast_targets[10] == 1 and broadcast_targets[30] == 1,
|
||||
"broadcast recipients must be unique and equipped")
|
||||
|
||||
result, error_code = SkyPhoneNotifications.Send({ kind = "all", value = true }, notification)
|
||||
assert(result == nil and error_code == "invalid_target", "all targets must reject extra values")
|
||||
|
||||
print("Server notification router tests passed")
|
||||
Reference in New Issue
Block a user