Environment & Bootstrap Stability: Fix Ghost Account and Modernize PWA Manifest

- Resolved 'Ghost Account' warning by updating layout hydration to align with V3 ID Vision (account_id vs account_id_random).
- Improved site lookup reliability using Agent API Key and structured EQ filters for exact FQDN matching (including ports).
- Modernized PWA manifest with maskable icons (PNG/WebP), app shortcuts, and unique installation IDs.
- Implemented automatic Electron 'Native' mode detection in root layout.
- Fixed stale API URLs in Launcher native file download logic.
- Added V3 migration documentation and JWT verification test scripts.
This commit is contained in:
Scott Idem
2026-01-19 16:44:20 -05:00
parent 08d0d9ca45
commit 25d6503afe
11 changed files with 341 additions and 83 deletions

View File

@@ -86,10 +86,21 @@
console.log(`$ae_api = `, $ae_api);
}
$ae_loc = {
...$ae_loc,
...(ae_acct.loc || {})
};
// FORCE UPDATE: If the incoming data is a valid site (not a fallback ghost),
// we must ensure the ae_loc store is updated regardless of what's in localStorage.
if (ae_acct.loc?.account_id && ae_acct.loc.account_id !== 'ghost') {
$ae_loc = {
...$ae_loc,
...(ae_acct.loc || {})
};
} else {
// If it IS a ghost, we still update it to show the correct fallback message
$ae_loc = {
...$ae_loc,
...(ae_acct.loc || {})
};
}
if (log_lvl > 1) {
console.log(`$ae_loc = `, $ae_loc);
}
@@ -118,6 +129,7 @@
// Connection Status
let is_offline = $derived(browser && online.current === false);
let api_unreachable = $derived($ae_loc?.account_id === 'ghost');
let api_error_msg = $derived($ae_loc?.account_name || 'API Server Unreachable');
let show_connection_details = $state(true);
// BEGIN: Sanity Checks:
@@ -631,6 +643,14 @@
$slct.sponsorship_cfg_id = data.url.searchParams.get('sponsorship_cfg_id');
$ae_loc.mod.sponsorships.cfg_id = data.url.searchParams.get('sponsorship_cfg_id');
}
// *** Electron Native Mode Detection ***
// If window.native_app exists, we are running inside the Electron bridge
// @ts-ignore - native_app is injected by the Electron preload script
if (window.native_app) {
console.log('ELECTRON: Native environment detected. Switching to native app_mode.');
$events_loc.launcher.app_mode = 'native';
}
}
// We want to loop through all of the data store (ds) key value pairs and set them to localStorage
@@ -805,7 +825,7 @@
Connection Offline
{:else}
<span class="fas fa-server mr-2"></span>
API Server Unreachable
{api_error_msg}
{/if}
</span>
<span class="hidden md:inline">Viewing cached data. Changes may not be saved.</span>

View File

@@ -2,7 +2,7 @@
// console.log(`ae_root +layout.ts: start`);
import { error } from '@sveltejs/kit';
import { lookup_site_domain } from '$lib/ae_core/ae_core__site';
import { lookup_site_domain_v3 } from '$lib/ae_core/ae_core__site';
import type { key_val } from '$lib/stores/ae_stores';
import type { ae_SiteDomain } from '$lib/types/ae_types';
@@ -91,6 +91,7 @@ export async function load({ fetch, params, parent, route, url }) {
ae_m_sponsorships: {},
ae_m_events: {},
ae_m_events_speakers: {},
ae_m_idaa: {},
ae_slct: {},
iframe: false,
ae_root_layout_ts: true,
@@ -108,28 +109,41 @@ export async function load({ fetch, params, parent, route, url }) {
const fqdn = url.host;
let result: any = null;
let api_error = false;
try {
if (log_lvl) console.log(`ROOT LOAD: Starting site lookup for ${fqdn}...`);
result = await lookup_site_domain({
api_cfg: ae_api_init,
if (log_lvl) console.log(`ROOT LOAD: Starting site lookup V3 for ${fqdn}...`);
// Use dedicated Agent Key for Bootstrap if available, otherwise fallback to standard key
const bootstrap_api_cfg = {
...ae_api_init,
api_secret_key: 'IDF68Em5X4HTZlswRNgepQ', // Dedicated Agent Bootstrap Key
headers: {
...ae_api_init.headers,
'x-aether-api-key': 'IDF68Em5X4HTZlswRNgepQ'
}
};
result = await lookup_site_domain_v3({
api_cfg: bootstrap_api_cfg,
fqdn,
view: 'base',
log_lvl
});
if (log_lvl) console.log('ROOT LOAD: Site lookup result:', result);
if (log_lvl) console.log(`ROOT LOAD: Site lookup result for ${fqdn}:`, result);
} catch (err) {
console.error('ROOT LOAD: Site lookup critical failure.', err);
console.error(`ROOT LOAD: Site lookup critical failure for ${fqdn}.`, err);
api_error = true;
}
// Defensive check: if result is false (common from API helper) or null, use emergency ghost
if (!result || typeof result !== 'object') {
console.warn('ROOT LOAD: Result was falsy or non-object. Forcing ghost fallback.');
if (!result || typeof result !== 'object' || result.account_id === 'ghost') {
console.warn(`ROOT LOAD: Falsy or Ghost result for ${fqdn}. Forcing fallback message.`);
result = {
id: 'ghost',
id_random: 'ghost',
account_id_random: 'ghost',
account_code: 'ghost',
account_name: 'Ghost Account',
account_name: api_error ? 'API Connection Failed' : 'Domain Not Registered',
site_id_random: 'ghost',
site_domain_id_random: 'ghost',
enable: '1',
@@ -141,7 +155,8 @@ export async function load({ fetch, params, parent, route, url }) {
const json_data = result;
// CRITICAL: SvelteKit hydration can fail if these are undefined
account_id = json_data.account_id_random || 'ghost';
// V3 ID Vision: Use account_id (random string) instead of account_id_random
account_id = json_data.account_id || json_data.account_id_random || 'ghost';
data_struct.account_id = account_id;
ae_acct.account_id = account_id;
@@ -156,8 +171,8 @@ export async function load({ fetch, params, parent, route, url }) {
ae_loc_init['account_code'] = json_data.account_code || 'ghost';
ae_loc_init['account_name'] = json_data.account_name || 'Ghost Account';
ae_loc_init['site_id'] = json_data.site_id_random || 'ghost';
ae_loc_init['site_domain_id'] = json_data.site_domain_id_random || 'ghost';
ae_loc_init['site_id'] = json_data.site_id || json_data.site_id_random || 'ghost';
ae_loc_init['site_domain_id'] = json_data.site_domain_id || json_data.site_domain_id_random || 'ghost';
ae_loc_init['site_enable'] = json_data.enable || '1';
ae_loc_init['site_header_image_path'] = json_data.header_image_path || '';
ae_loc_init['site_style_href'] = json_data.style_href || '';

View File

@@ -187,14 +187,14 @@
console.log('Cached hash file found.');
} else if (check_hash_file_cache_result == null) {
console.log(
`Cached hash file not found. Need to download from API server. Base URL ${$events_loc.launcher.api.base_url}`
`Cached hash file not found. Need to download from API server. Base URL ${$ae_api.base_url}`
);
open_file_status = 'downloading_file';
open_file_status_message = 'Downloading file...';
let download_hash_file_to_cache_result =
await window.native_app.download_hash_file_to_cache_v2({
api_base_url: $events_loc.launcher.api.base_url,
api_base_url_backup: $events_loc.launcher.api.base_url_backup,
api_base_url: $ae_api.base_url,
api_base_url_backup: $ae_api.base_url_bak,
local_file_cache_path: $events_loc.launcher.local_file_cache_path,
event_file_id: event_file_id,
hash: event_file_obj.hash_sha256,

View File

@@ -1,21 +1,19 @@
import { json } from '@sveltejs/kit';
import { lookup_site_domain } from '$lib/ae_core/ae_core__site';
import { api } from '$lib/api/api';
import * as public_env from '$env/static/public';
import type { RequestHandler } from './$types';
/**
* Dynamic Web Manifest Generator
* Generates PWA metadata based on the requesting domain.
* Modern Dynamic Web Manifest Generator
* Reference: https://web.dev/articles/add-manifest
*/
export const GET: RequestHandler = async ({ url, fetch }) => {
const fqdn = url.hostname;
const fqdn = url.host;
// Construct api_cfg from public env vars for the server-side lookup
const protocol = public_env.PUBLIC_AE_API_PROTOCOL || 'https';
const server = public_env.PUBLIC_AE_API_SERVER || 'api.oneskyit.com';
const port = public_env.PUBLIC_AE_API_PORT || '443';
const path = public_env.PUBLIC_AE_API_PATH || '';
const api_base_url = `${protocol}://${server}${port === '443' || port === '80' ? '' : ':' + port}${path}`;
const api_cfg = {
@@ -29,58 +27,86 @@ export const GET: RequestHandler = async ({ url, fetch }) => {
let site_domain = null;
try {
site_domain = await lookup_site_domain({
// Use structured filter for exact matching
const search_query = {
and: [{ field: 'fqdn', op: 'eq', value: fqdn }]
};
const result_li = await api.search_ae_obj_v3({
api_cfg,
fqdn,
obj_type: 'site_domain',
search_query,
view: 'base',
limit: 1,
log_lvl: 0
});
} catch (e) {
console.error(`PWA Manifest: Lookup failed for domain ${fqdn}:`, e);
}
// Default branding values (Fallback to OSIT Aether)
let name = "One Sky IT - One Sky IT Aether PWA";
let short_name = "Aether PWA";
let background_color = "hsl(220, 65%, 31%)";
let theme_color = "#3a5997";
if (site_domain) {
// If site_domain has account_name like "Danger Zone", name becomes "One Sky IT - Danger Zone Aether PWA"
const branding_name = site_domain.account_name || site_domain.name || "Aether";
name = `One Sky IT - ${branding_name} Aether PWA`;
short_name = `${site_domain.account_code || site_domain.code || 'Aether'} PWA`;
if (site_domain.cfg_json?.pwa_background_color) {
background_color = site_domain.cfg_json.pwa_background_color;
if (result_li && result_li.length > 0) {
site_domain = result_li[0];
}
} catch (e) {
console.error(`PWA Manifest: Lookup failed for domain ${fqdn}`);
}
// Default branding
const branding_name = site_domain?.account_name || site_domain?.name || "Aether";
const name = `One Sky IT - ${branding_name} Aether PWA`;
const short_name = `${site_domain?.account_code || site_domain?.code || 'Aether'} PWA`;
const background_color = site_domain?.cfg_json?.pwa_background_color || "hsl(220, 65%, 31%)";
const theme_color = "#3a5997";
const manifest = {
"background_color": background_color,
"description": `The ${name} Progressive Web App`,
"display": "fullscreen",
"icons": [
{ "sizes": "24x24", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_24px.png", "type": "image/png" },
{ "sizes": "48x48", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_48px.png", "type": "image/png" },
{ "sizes": "88x88", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_88px.webp", "type": "image/webp" },
{ "sizes": "88x88", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_88px.png", "type": "image/png" },
{ "sizes": "120x120", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_120px.png", "type": "image/png" },
{ "sizes": "144x144", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_144px.png", "type": "image/png" },
{ "sizes": "180x180", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_180px.png", "type": "image/png" },
{ "sizes": "192x192", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_192px.webp", "type": "image/webp" },
{ "sizes": "192x192", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_192px.png", "type": "image/png" },
{ "sizes": "256x256", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_256px.webp", "type": "image/webp" },
{ "sizes": "256x256", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_256px.png", "type": "image/png" },
{ "sizes": "300x300", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_300px.png", "type": "image/png" },
{ "sizes": "512x512", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_512px.webp", "type": "image/webp" },
{ "sizes": "512x512", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_512px.png", "type": "image/png" },
{ "sizes": "1024x1024", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_1024px.webp", "type": "image/webp" },
{ "sizes": "1024x1024", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_1024px.png", "type": "image/png" }
],
"id": `ae-pwa-${fqdn}`, // Unique ID for this installation
"name": name,
"short_name": short_name,
"description": `The ${name} platform for unified event and documentation management.`,
"start_url": "/",
"theme_color": theme_color
"scope": "/",
"display": "fullscreen",
"background_color": background_color,
"theme_color": theme_color,
"orientation": "any",
"categories": ["business", "productivity", "utilities"],
"icons": [
// Standard Icons (Small/Med)
{ "sizes": "24x24", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_24px.png", "type": "image/png" },
{ "sizes": "48x48", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_48px.png", "type": "image/png" },
{ "sizes": "96x96", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_96px.png", "type": "image/png" },
{ "sizes": "144x144", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_144px.png", "type": "image/png" },
{ "sizes": "180x180", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_180px.png", "type": "image/png" },
// High-res Maskable Icons (WebP preferred for efficiency)
{ "sizes": "192x192", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_192px.webp", "type": "image/webp", "purpose": "any maskable" },
{ "sizes": "192x192", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_192px.png", "type": "image/png", "purpose": "any maskable" },
{ "sizes": "512x512", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_512px.webp", "type": "image/webp", "purpose": "any maskable" },
{ "sizes": "512x512", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_512px.png", "type": "image/png", "purpose": "any maskable" },
{ "sizes": "1024x1024", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_1024px.webp", "type": "image/webp", "purpose": "any maskable" },
{ "sizes": "1024x1024", "src": "https://static.oneskyit.com/images/OSIT_logo_2022_1024px.png", "type": "image/png", "purpose": "any maskable" }
],
// App Shortcuts (Long-press icon features)
"shortcuts": [
{
"name": "Journals",
"short_name": "Journals",
"description": "View and manage journal entries",
"url": "/journals",
"icons": [{ "src": "https://static.oneskyit.com/images/OSIT_logo_2022_192px.png", "sizes": "192x192" }]
},
{
"name": "Events",
"short_name": "Events",
"description": "Access active event management",
"url": "/events",
"icons": [{ "src": "https://static.oneskyit.com/images/OSIT_logo_2022_192px.png", "sizes": "192x192" }]
},
{
"name": "Testing",
"short_name": "Testing",
"description": "System diagnostic dashboard",
"url": "/testing",
"icons": [{ "src": "https://static.oneskyit.com/images/OSIT_logo_2022_192px.png", "sizes": "192x192" }]
}
],
"testing": "One Sky IT"
};
return json(manifest, {