mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-29 01:08:59 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1da9fee997 | |||
| 49c5964f7e | |||
| 3a0a9c0093 | |||
| 24ebe8c3ff |
@@ -0,0 +1,75 @@
|
|||||||
|
import { readFileSync } from 'node:fs'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import {
|
||||||
|
PHONE_APPS,
|
||||||
|
isExternalPhoneApp,
|
||||||
|
isLaunchablePhoneApp,
|
||||||
|
} from '@/config/apps'
|
||||||
|
|
||||||
|
const repositoryRoot = join(import.meta.dirname, '../..')
|
||||||
|
const source = (path: string) =>
|
||||||
|
readFileSync(join(repositoryRoot, path), 'utf8')
|
||||||
|
|
||||||
|
const app = source('frontend/src/App.vue')
|
||||||
|
const appStore = source('frontend/src/stores/app-store.ts')
|
||||||
|
const appStoreView = source('frontend/src/views/apps/AppStoreApp.vue')
|
||||||
|
const appWindow = source('frontend/src/views/PhoneAppWindow.vue')
|
||||||
|
const client = source('sky_phone/source/client/main.lua')
|
||||||
|
const config = source('sky_phone/config/config.lua')
|
||||||
|
const configDefault = source('sky_phone/source/shared/config_default.lua')
|
||||||
|
const crypto = source('sky_phone/source/server/crypto.lua')
|
||||||
|
const phone = source('sky_phone/source/server/phone.lua')
|
||||||
|
const router = source('frontend/src/router/index.ts')
|
||||||
|
|
||||||
|
describe('configured app availability contract', () => {
|
||||||
|
it('keeps every built-in app in the file and runtime defaults', () => {
|
||||||
|
const builtInAppIds = PHONE_APPS.filter(
|
||||||
|
(app) =>
|
||||||
|
isLaunchablePhoneApp(app) && !app.adminOnly && !isExternalPhoneApp(app),
|
||||||
|
).map((app) => app.id)
|
||||||
|
|
||||||
|
for (const appId of builtInAppIds) {
|
||||||
|
const entry = appId.includes('-')
|
||||||
|
? `["${appId}"] = true`
|
||||||
|
: `${appId} = true`
|
||||||
|
expect(config).toContain(entry)
|
||||||
|
expect(configDefault).toContain(entry)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends disabled apps and reapplies them to an open phone after live saves', () => {
|
||||||
|
expect(phone).toContain('disabledApps = SkyPhone.GetDisabledApps()')
|
||||||
|
expect(phone).toMatch(
|
||||||
|
/function SkyPhone\.GetDisabledApps\(\)[\s\S]*?enabled == false/,
|
||||||
|
)
|
||||||
|
expect(client).toMatch(
|
||||||
|
/configurator:updated[\s\S]*?apply_disabled_apps\(device_payload\)[\s\S]*?device:updated/,
|
||||||
|
)
|
||||||
|
expect(app).toContain(
|
||||||
|
'appStore.hydrate(payload.device?.data.apps?.payload, payload.disabledApps)',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('removes disabled apps from every launch path without changing device installs', () => {
|
||||||
|
expect(appStore).toContain('disabledApps: [] as LaunchablePhoneAppId[]')
|
||||||
|
expect(appStore).toContain('if (!this.isAvailable(appId)) return false')
|
||||||
|
expect(appStoreView).toContain('appStore.isAvailable(app.id)')
|
||||||
|
expect(router).toContain('useAppStoreStore().isInstalled(to.params.appId)')
|
||||||
|
expect(appWindow).toContain('appStore.isInstalled(app.id)')
|
||||||
|
expect(app).toMatch(
|
||||||
|
/isPhoneAppId\(currentAppId\)[\s\S]*?!appStore\.isInstalled\(currentAppId\)[\s\S]*?router\.push\('\/'\)/,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rejects crypto operations after the app is disabled at runtime', () => {
|
||||||
|
expect(crypto).toMatch(
|
||||||
|
/local function require_phone\(source\)[\s\S]*?not Config\.Crypto\.Enabled or not SkyPhone\.IsAppEnabled\("crypto"\)/,
|
||||||
|
)
|
||||||
|
expect(crypto).toMatch(
|
||||||
|
/local function refresh_crypto_runtime\(\)[\s\S]*?not Config\.Crypto\.Enabled or not SkyPhone\.IsAppEnabled\("crypto"\)[\s\S]*?sessions = \{\}/,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -46,10 +46,16 @@ describe('fixed server permissions', () => {
|
|||||||
expect(configurator).toContain(`["${path}"] = true`)
|
expect(configurator).toContain(`["${path}"] = true`)
|
||||||
}
|
}
|
||||||
expect(configurator).toContain(
|
expect(configurator).toContain(
|
||||||
'Phone Configurator enabled: file-based settings from config.lua',
|
'SKY PHONE CONFIGURATION FILES ARE DISABLED',
|
||||||
)
|
)
|
||||||
expect(configurator).toContain(
|
expect(configurator).toContain(
|
||||||
'(except Config.CommandPermissions) and media.lua are disabled',
|
'^1 Runtime settings from config.lua and media.lua are DISABLED.^0',
|
||||||
|
)
|
||||||
|
expect(configurator).toContain(
|
||||||
|
'^1 Configure all phone and media settings IN GAME through /phonepanel.^0',
|
||||||
|
)
|
||||||
|
expect(configurator).toContain(
|
||||||
|
'^1 Only Config.PhoneConfigurator.Enabled and Config.CommandPermissions remain file-based.^0',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
} from 'vue-router'
|
} from 'vue-router'
|
||||||
|
|
||||||
import { isPhoneAppId } from '@/config/apps'
|
import { isPhoneAppId } from '@/config/apps'
|
||||||
|
import { useAppStoreStore } from '@/stores/app-store'
|
||||||
import PhoneAppWindow from '@/views/PhoneAppWindow.vue'
|
import PhoneAppWindow from '@/views/PhoneAppWindow.vue'
|
||||||
import SpringboardView from '@/views/SpringboardView.vue'
|
import SpringboardView from '@/views/SpringboardView.vue'
|
||||||
|
|
||||||
@@ -34,7 +35,9 @@ export default createRouter({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
beforeEnter: (to) =>
|
beforeEnter: (to) =>
|
||||||
typeof to.params.appId === 'string' && isPhoneAppId(to.params.appId)
|
typeof to.params.appId === 'string' &&
|
||||||
|
isPhoneAppId(to.params.appId) &&
|
||||||
|
useAppStoreStore().isInstalled(to.params.appId)
|
||||||
? true
|
? true
|
||||||
: '/',
|
: '/',
|
||||||
component: PhoneAppWindow,
|
component: PhoneAppWindow,
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ const EMPTY_STATS: AdminStats = {
|
|||||||
online: 0,
|
online: 0,
|
||||||
simDevices: 0,
|
simDevices: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
function disabledAppsFromConfigurator(
|
function disabledAppsFromConfigurator(
|
||||||
configurator: AdminConfigurator,
|
configurator: AdminConfigurator,
|
||||||
): string[] {
|
): string[] {
|
||||||
|
|||||||
@@ -276,6 +276,31 @@ describe('app store', () => {
|
|||||||
expect(apps.isInstalled('phone')).toBe(true)
|
expect(apps.isInstalled('phone')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('temporarily blocks server-disabled apps without changing their installation', () => {
|
||||||
|
const apps = useAppStoreStore()
|
||||||
|
apps.hydrate({ claimedApps: ['crypto'] }, [
|
||||||
|
'crypto',
|
||||||
|
'citywarn',
|
||||||
|
'not-an-app',
|
||||||
|
])
|
||||||
|
mocks.phone.saveDeviceNamespace.mockClear()
|
||||||
|
|
||||||
|
expect(apps.disabledApps).toEqual(['crypto', 'citywarn'])
|
||||||
|
expect(apps.isAvailable('crypto')).toBe(false)
|
||||||
|
expect(apps.isInstalled('crypto')).toBe(false)
|
||||||
|
expect(apps.isInstalled('citywarn')).toBe(false)
|
||||||
|
expect(apps.claimedApps).toEqual(['crypto'])
|
||||||
|
|
||||||
|
apps.installApp('crypto')
|
||||||
|
expect(apps.installingApps).toEqual({})
|
||||||
|
expect(mocks.phone.saveDeviceNamespace).not.toHaveBeenCalled()
|
||||||
|
|
||||||
|
apps.hydrate({ claimedApps: ['crypto'] })
|
||||||
|
expect(apps.isAvailable('crypto')).toBe(true)
|
||||||
|
expect(apps.isInstalled('crypto')).toBe(true)
|
||||||
|
expect(apps.isInstalled('citywarn')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
it('protects every default app from full uninstallation', () => {
|
it('protects every default app from full uninstallation', () => {
|
||||||
const apps = useAppStoreStore()
|
const apps = useAppStoreStore()
|
||||||
apps.hydrate(null)
|
apps.hydrate(null)
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import { useRoute } from 'vue-router'
|
|||||||
|
|
||||||
import CustomAppFrame from '@/components/CustomAppFrame.vue'
|
import CustomAppFrame from '@/components/CustomAppFrame.vue'
|
||||||
import { getPhoneApp, isExternalPhoneApp } from '@/config/apps'
|
import { getPhoneApp, isExternalPhoneApp } from '@/config/apps'
|
||||||
|
import { useAppStoreStore } from '@/stores/app-store'
|
||||||
import { usePhoneStore } from '@/stores/phone'
|
import { usePhoneStore } from '@/stores/phone'
|
||||||
import { getCustomAppFrameKey } from '@/utils/customAppLifecycle'
|
import { getCustomAppFrameKey } from '@/utils/customAppLifecycle'
|
||||||
import AppStoreApp from '@/views/apps/AppStoreApp.vue'
|
import AppStoreApp from '@/views/apps/AppStoreApp.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const appStore = useAppStoreStore()
|
||||||
const phone = usePhoneStore()
|
const phone = usePhoneStore()
|
||||||
const app = computed(() => getPhoneApp(route.params.appId))
|
const app = computed(() => getPhoneApp(route.params.appId))
|
||||||
const builtinAppComponent = computed(() =>
|
const builtinAppComponent = computed(() =>
|
||||||
@@ -28,7 +30,7 @@ const launchStyle = computed(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="app && !app.adminOnly"
|
v-if="app && !app.adminOnly && appStore.isInstalled(app.id)"
|
||||||
class="app-window"
|
class="app-window"
|
||||||
:class="{ 'app-window--citywarn': app.id === 'citywarn' }"
|
:class="{ 'app-window--citywarn': app.id === 'citywarn' }"
|
||||||
:style="launchStyle"
|
:style="launchStyle"
|
||||||
|
|||||||
@@ -371,6 +371,9 @@ local function initialize_markets()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function require_phone(source)
|
local function require_phone(source)
|
||||||
|
if not Config.Crypto.Enabled or not SkyPhone.IsAppEnabled("crypto") then
|
||||||
|
return nil, nil, { success = false, error = "service_unavailable" }
|
||||||
|
end
|
||||||
local phone_session, error_response = SkyPhone.RequireSession(source)
|
local phone_session, error_response = SkyPhone.RequireSession(source)
|
||||||
if not phone_session then
|
if not phone_session then
|
||||||
return nil, nil, error_response
|
return nil, nil, error_response
|
||||||
@@ -661,7 +664,7 @@ local function with_exchange_lock(callback)
|
|||||||
end
|
end
|
||||||
|
|
||||||
Bridge.Callbacks.Register("sky_phone:crypto:bootstrap", function(source)
|
Bridge.Callbacks.Register("sky_phone:crypto:bootstrap", function(source)
|
||||||
if not Config.Crypto.Enabled then
|
if not Config.Crypto.Enabled or not SkyPhone.IsAppEnabled("crypto") then
|
||||||
return { success = false, error = "service_unavailable" }
|
return { success = false, error = "service_unavailable" }
|
||||||
end
|
end
|
||||||
local profile, error_response = authenticated_profile(source)
|
local profile, error_response = authenticated_profile(source)
|
||||||
@@ -1647,6 +1650,9 @@ end
|
|||||||
local function refresh_crypto_runtime()
|
local function refresh_crypto_runtime()
|
||||||
initialize_markets()
|
initialize_markets()
|
||||||
start_crypto_schedulers()
|
start_crypto_schedulers()
|
||||||
|
if not Config.Crypto.Enabled or not SkyPhone.IsAppEnabled("crypto") then
|
||||||
|
sessions = {}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
AddEventHandler("sky_phone:configurator:serverUpdated", refresh_crypto_runtime)
|
AddEventHandler("sky_phone:configurator:serverUpdated", refresh_crypto_runtime)
|
||||||
|
|||||||
@@ -26,10 +26,16 @@ local FIXED_CONFIG_PATHS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if configurator_enabled then
|
if configurator_enabled then
|
||||||
print(
|
local border = "======================================================================"
|
||||||
"[sky_phone] Phone Configurator enabled: file-based settings from config.lua " ..
|
print(([[
|
||||||
"(except Config.CommandPermissions) and media.lua are disabled; SQL configuration is active."
|
^1%s^0
|
||||||
)
|
^1 SKY PHONE CONFIGURATION FILES ARE DISABLED ^0
|
||||||
|
^1%s^0
|
||||||
|
^1 The Phone Configurator is ENABLED.^0
|
||||||
|
^1 Runtime settings from config.lua and media.lua are DISABLED.^0
|
||||||
|
^1 Configure all phone and media settings IN GAME through /phonepanel.^0
|
||||||
|
^1 Only Config.PhoneConfigurator.Enabled and Config.CommandPermissions remain file-based.^0
|
||||||
|
^1%s^0]]):format(border, border, border))
|
||||||
end
|
end
|
||||||
|
|
||||||
local CLIENT_CONFIG_KEYS = {
|
local CLIENT_CONFIG_KEYS = {
|
||||||
|
|||||||
Reference in New Issue
Block a user