mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-28 17:01:18 +00:00
ADD - open admin panel by command
This commit is contained in:
@@ -630,6 +630,8 @@ function loadUnlockedPhoneData(): void {
|
||||
}
|
||||
|
||||
function completePhoneSetup(): void {
|
||||
const requestedRoute = pendingUnlockRoute.value
|
||||
pendingUnlockRoute.value = null
|
||||
setupPreviewDismissed.value = true
|
||||
setupAppearanceSelected.value = false
|
||||
isLocked.value = false
|
||||
@@ -637,7 +639,7 @@ function completePhoneSetup(): void {
|
||||
passcodeVisible.value = false
|
||||
passcodeRequired.value = false
|
||||
controlCenterOpened.value = false
|
||||
void router.replace('/')
|
||||
void router.replace(requestedRoute ?? '/')
|
||||
loadUnlockedPhoneData()
|
||||
}
|
||||
|
||||
@@ -766,7 +768,12 @@ function onMessage(event: MessageEvent<AppMessage>): void {
|
||||
isPhoneAppId(data.appId) &&
|
||||
appStore.isInstalled(data.appId)
|
||||
) {
|
||||
void router.push(`/apps/${data.appId}`)
|
||||
const requestedRoute = `/apps/${data.appId}`
|
||||
if (setupRequired.value || isLocked.value) {
|
||||
pendingUnlockRoute.value = requestedRoute
|
||||
} else {
|
||||
void router.push(requestedRoute)
|
||||
}
|
||||
} else {
|
||||
console.error('[Navigation] Ignored an unavailable app target.')
|
||||
}
|
||||
|
||||
@@ -29,4 +29,10 @@ describe('neutral phone navigation contract', () => {
|
||||
expect(navigationSource).toContain('if not installed_apps[normalized_app_id] then')
|
||||
expect(navigationSource).toContain('if current_app_id ~= normalized_app_id then')
|
||||
})
|
||||
|
||||
it('defers command-driven app routes until setup or device unlock completes', () => {
|
||||
expect(appSource).toContain('if (setupRequired.value || isLocked.value)')
|
||||
expect(appSource).toContain('pendingUnlockRoute.value = requestedRoute')
|
||||
expect(appSource).toContain("void router.replace(requestedRoute ?? '/')")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,6 +11,14 @@ const server = readFileSync(
|
||||
new URL('../../../../sky_phone/source/server/admin.lua', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
const phoneServer = readFileSync(
|
||||
new URL('../../../../sky_phone/source/server/phone.lua', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
const phoneClient = readFileSync(
|
||||
new URL('../../../../sky_phone/source/client/main.lua', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
const bridge = readFileSync(
|
||||
new URL(
|
||||
'../../../../sky_phone/source/client/nui_server_bridge.lua',
|
||||
@@ -71,6 +79,24 @@ describe('admin command center contracts', () => {
|
||||
expect(config).toContain('Config.AdminPanel = {')
|
||||
})
|
||||
|
||||
it('opens directly from a server-authorized configurable command', () => {
|
||||
expect(config).toContain('Command = "phoneadmin"')
|
||||
expect(phoneServer).toContain(
|
||||
'RegisterCommand(Config.AdminPanel.Command, function(command_source)',
|
||||
)
|
||||
expect(phoneServer).toContain(
|
||||
'Bridge.Framework.HasAdminGroup(player_source, Config.AdminPanel.AdminGroups)',
|
||||
)
|
||||
expect(phoneServer).toContain(
|
||||
'TriggerClientEvent("sky_phone:admin:launch", player_source)',
|
||||
)
|
||||
expect(phoneServer).toContain('if not sessions[player_source] then')
|
||||
expect(phoneClient).toContain(
|
||||
'RegisterNetEvent("sky_phone:admin:launch"',
|
||||
)
|
||||
expect(phoneClient).toContain('requested_app_id = "admin"')
|
||||
})
|
||||
|
||||
it('validates ownership and app policy before remote device mutation', () => {
|
||||
expect(server).toContain('find_owned_device(target_source, data.imei)')
|
||||
expect(server).toContain('app_metadata(data.appId)')
|
||||
|
||||
@@ -63,6 +63,7 @@ Config.Security = {
|
||||
|
||||
Config.AdminPanel = {
|
||||
Enabled = true,
|
||||
Command = "phoneadmin",
|
||||
AdminGroups = { "admin", "superadmin" },
|
||||
MaximumPlayers = 128,
|
||||
ReadRequestsPerMinute = 60,
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
Locales["de"] = {
|
||||
CommandDescription = "Öffne dein Handy.",
|
||||
AdminCommand = {
|
||||
CommandDescription = "Öffne das geschützte Handy-Admin-Panel.",
|
||||
Errors = {
|
||||
disabled = "Das Handy-Admin-Panel ist deaktiviert.",
|
||||
not_authorized = "Du hast keinen Zugriff auf das Handy-Admin-Panel.",
|
||||
default = "Das Handy-Admin-Panel konnte nicht geöffnet werden.",
|
||||
},
|
||||
},
|
||||
Controls = {
|
||||
OpenPhone = "Handy öffnen",
|
||||
},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
Locales["en"] = {
|
||||
CommandDescription = "Open your phone.",
|
||||
AdminCommand = {
|
||||
CommandDescription = "Open the protected phone admin panel.",
|
||||
Errors = {
|
||||
disabled = "The phone admin panel is disabled.",
|
||||
not_authorized = "You do not have access to the phone admin panel.",
|
||||
default = "The phone admin panel could not be opened.",
|
||||
},
|
||||
},
|
||||
Controls = {
|
||||
OpenPhone = "Open phone",
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ local equipped_phone_number = nil
|
||||
local nui_generation = 0
|
||||
local live_activity_active = false
|
||||
local open_home_requested = false
|
||||
local requested_app_id = nil
|
||||
|
||||
local function get_equipped_phone_number()
|
||||
if not device_payload or not device_payload.device.sim then
|
||||
@@ -60,6 +61,24 @@ end
|
||||
SkyPhoneClient.GetState = get_phone_state
|
||||
SkyPhoneClient.GetEquippedPhoneNumber = get_authoritative_phone_number
|
||||
|
||||
local function open_requested_app()
|
||||
if not requested_app_id then
|
||||
return
|
||||
end
|
||||
|
||||
local app_id = requested_app_id
|
||||
requested_app_id = nil
|
||||
local opened, error_code = SkyPhoneNavigation.Open(app_id)
|
||||
if not opened then
|
||||
Bridge.Debug(
|
||||
"warn",
|
||||
"[sky_phone] Could not open requested app '%s': %s.",
|
||||
app_id,
|
||||
tostring(error_code)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
Bridge.Debug("debug", "[sky_phone] Client script initialized.", { always = true })
|
||||
|
||||
local locale, locale_name = SkyPhoneLocales.Resolve(Config.Bridge.Locale)
|
||||
@@ -95,6 +114,7 @@ local function close_phone(close_device_session)
|
||||
local was_open = is_open
|
||||
open_requested = false
|
||||
open_without_focus = false
|
||||
requested_app_id = nil
|
||||
TriggerEvent("sky_phone:animation:phone", false)
|
||||
is_open = false
|
||||
if was_open then
|
||||
@@ -266,9 +286,27 @@ RegisterNUICallback("ui:opened", function(data, cb)
|
||||
end
|
||||
SkyPhoneFocus.SetPhone(true, open_without_focus)
|
||||
TriggerEvent("sky_phone:animation:phone", true)
|
||||
open_requested_app()
|
||||
cb({ success = true })
|
||||
end)
|
||||
|
||||
RegisterNetEvent("sky_phone:admin:launch", function()
|
||||
requested_app_id = "admin"
|
||||
if is_open then
|
||||
open_requested_app()
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("sky_phone:admin:command-error", function(error_code)
|
||||
local messages = locale.AdminCommand.Errors
|
||||
Bridge.Framework.Notify(
|
||||
"iFruit",
|
||||
messages[error_code] or messages.default,
|
||||
"error",
|
||||
5000
|
||||
)
|
||||
end)
|
||||
|
||||
RegisterNUICallback("ui:input-focus", function(data, cb)
|
||||
if type(data) ~= "table" or type(data.active) ~= "boolean" then
|
||||
cb({ success = false, error = "invalid_request" })
|
||||
@@ -336,9 +374,11 @@ RegisterNetEvent("sky_phone:device:invalidated", function()
|
||||
update_equipped_phone_number(nil)
|
||||
live_activity_active = false
|
||||
open_home_requested = false
|
||||
requested_app_id = nil
|
||||
end)
|
||||
|
||||
RegisterNetEvent("sky_phone:device:error", function(error_code)
|
||||
requested_app_id = nil
|
||||
if not is_open and not open_requested then
|
||||
open_without_focus = false
|
||||
end
|
||||
@@ -359,6 +399,13 @@ CreateThread(function()
|
||||
if Config.TestData.Enabled then
|
||||
TriggerEvent("chat:addSuggestion", "/" .. Config.TestData.Command, locale.TestData.CommandDescription)
|
||||
end
|
||||
if Config.AdminPanel.Enabled then
|
||||
TriggerEvent(
|
||||
"chat:addSuggestion",
|
||||
"/" .. Config.AdminPanel.Command,
|
||||
locale.AdminCommand.CommandDescription
|
||||
)
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler("onResourceStop", function(resource_name)
|
||||
@@ -369,6 +416,7 @@ AddEventHandler("onResourceStop", function(resource_name)
|
||||
is_open = false
|
||||
open_requested = false
|
||||
open_without_focus = false
|
||||
requested_app_id = nil
|
||||
|
||||
TriggerEvent("sky_phone:animation:reset")
|
||||
SkyPhoneCalls.Reset()
|
||||
@@ -381,4 +429,7 @@ AddEventHandler("onResourceStop", function(resource_name)
|
||||
if Config.TestData.Enabled then
|
||||
TriggerEvent("chat:removeSuggestion", "/" .. Config.TestData.Command)
|
||||
end
|
||||
if Config.AdminPanel.Enabled then
|
||||
TriggerEvent("chat:removeSuggestion", "/" .. Config.AdminPanel.Command)
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -2,6 +2,10 @@ local phone_open_handler
|
||||
local pending_phone_opens = {}
|
||||
local server_started = false
|
||||
|
||||
if type(Config.AdminPanel.Command) ~= "string" or Config.AdminPanel.Command == "" then
|
||||
error("[sky_phone] Config.AdminPanel.Command must be a non-empty command name.")
|
||||
end
|
||||
|
||||
local function flush_pending_phone_opens()
|
||||
if not server_started or not phone_open_handler then
|
||||
return
|
||||
@@ -915,6 +919,32 @@ end
|
||||
phone_open_handler = open_phone
|
||||
flush_pending_phone_opens()
|
||||
|
||||
RegisterCommand(Config.AdminPanel.Command, function(command_source)
|
||||
local player_source = tonumber(command_source)
|
||||
if not player_source or player_source < 1 then
|
||||
Bridge.Debug("warn", "[sky_phone] The admin panel command can only be used by a player.")
|
||||
return
|
||||
end
|
||||
if not Config.AdminPanel.Enabled then
|
||||
TriggerClientEvent("sky_phone:admin:command-error", player_source, "disabled")
|
||||
return
|
||||
end
|
||||
if not Bridge.Framework.HasAdminGroup(player_source, Config.AdminPanel.AdminGroups) then
|
||||
Bridge.Debug(
|
||||
"warn",
|
||||
"[sky_phone] Rejected admin panel command from source %s.",
|
||||
tostring(player_source)
|
||||
)
|
||||
TriggerClientEvent("sky_phone:admin:command-error", player_source, "not_authorized")
|
||||
return
|
||||
end
|
||||
|
||||
TriggerClientEvent("sky_phone:admin:launch", player_source)
|
||||
if not sessions[player_source] then
|
||||
open_phone(player_source, nil)
|
||||
end
|
||||
end, false)
|
||||
|
||||
function SkyPhone.OpenDeviceForCall(source, imei)
|
||||
local matches = find_device_slots(source, imei)
|
||||
if not matches[1] then
|
||||
|
||||
@@ -23,7 +23,11 @@ Bridge = {
|
||||
},
|
||||
Debug = function()
|
||||
end,
|
||||
Framework = {},
|
||||
Framework = {
|
||||
HasAdminGroup = function()
|
||||
return true
|
||||
end,
|
||||
},
|
||||
Inventory = {
|
||||
GetResourceName = function()
|
||||
return "test_inventory"
|
||||
@@ -36,6 +40,11 @@ Bridge = {
|
||||
}
|
||||
|
||||
Config = {
|
||||
AdminPanel = {
|
||||
AdminGroups = { "admin" },
|
||||
Command = "phoneadmin",
|
||||
Enabled = true,
|
||||
},
|
||||
Phone = {
|
||||
DevelopmentCommand = false,
|
||||
DeviceName = "Test Phone",
|
||||
@@ -79,6 +88,12 @@ function AddEventHandler(_, callback)
|
||||
assert(type(callback) == "function")
|
||||
end
|
||||
|
||||
function RegisterCommand(name, callback, restricted)
|
||||
assert(name == "phoneadmin")
|
||||
assert(type(callback) == "function")
|
||||
assert(restricted == false)
|
||||
end
|
||||
|
||||
function TriggerClientEvent()
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user