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

@@ -131,9 +131,15 @@ export async function lookup_site_domain_v3({
delete guest_api_cfg.account_id;
const search_query = {
q: fqdn
and: [{ field: 'fqdn', op: 'eq', value: fqdn }]
};
if (log_lvl) {
console.log(`BOOTSTRAP SEARCH: fqdn=${fqdn}`);
console.log(`BOOTSTRAP HEADERS:`, guest_api_cfg.headers);
console.log(`BOOTSTRAP QUERY:`, JSON.stringify(search_query));
}
// We use search because we are looking up by a unique field (fqdn) rather than ID.
// The backend should return a list, but since FQDN is unique, it will have 1 item.
const result_li = await api.search_ae_obj_v3({
@@ -147,6 +153,8 @@ export async function lookup_site_domain_v3({
log_lvl
});
if (log_lvl) console.log(`BOOTSTRAP RESULT:`, result_li);
if (result_li && result_li.length > 0) {
const result = result_li[0];
// Standardize and save to cache

View File

@@ -21,7 +21,7 @@ export async function load_ae_obj_id__event({
inc_device_li = false,
inc_location_li = false,
inc_session_li = false,
inc_template_li = false,
inc_template_li = false,
try_cache = true,
log_lvl = 0
}: {
@@ -104,7 +104,7 @@ export async function load_ae_obj_id__event({
*/
async function _handle_nested_loads(event_obj: any, { api_cfg, inc_device_li, inc_location_li, inc_session_li, inc_template_li, log_lvl }: any) {
const current_event_id = event_obj.event_id || event_obj.id;
if (inc_device_li) {
event_obj.event_device_obj_li = await load_ae_obj_li__event_device({
api_cfg,
@@ -171,7 +171,7 @@ export async function load_ae_obj_li__event({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Event[]> {
// Check if offline
if (typeof navigator !== 'undefined' && !navigator.onLine) {
if (log_lvl) console.log('Browser is offline. Skipping API and attempting cache load.');
@@ -182,7 +182,7 @@ export async function load_ae_obj_li__event({
}
let promise;
if (qry_conference !== null) {
// V3 Search now permits 'conference' field.
const search_query: any = {
@@ -193,7 +193,7 @@ export async function load_ae_obj_li__event({
if (for_obj_id) {
search_query.and.push({ field: 'account_id_random', op: 'eq', value: for_obj_id });
}
promise = api.search_ae_obj_v3({
api_cfg,
obj_type: 'event',
@@ -443,7 +443,7 @@ export async function qry_ae_obj_li__event({
log_lvl?: number;
}) {
const search_query: any = { and: [] };
if (qry_str) {
// Use reserved 'q' property for global full-text search as per V3 Guide
search_query.q = qry_str;
@@ -498,11 +498,11 @@ export async function qry_ae_obj_li__event({
if (qry_physical === true || qry_virtual === true) {
const ev_physical = ev.physical === true || ev.physical === 1 || ev.physical === '1';
const ev_virtual = ev.virtual === true || ev.virtual === 1 || ev.virtual === '1';
let match = false;
if (qry_physical === true && ev_physical) match = true;
if (qry_virtual === true && ev_virtual) match = true;
if (!match) return false;
}
@@ -624,16 +624,16 @@ export async function qry_ae_obj_li__event_v2({
}
// Location Filtering (Inclusive OR logic)
// If either filter is explicitly true, we restrict results.
// If either filter is explicitly true, we restrict results.
// If both are false or null, we show everything.
if (qry_physical === true || qry_virtual === true) {
const ev_physical = ev.physical === true || ev.physical === 1 || ev.physical === '1';
const ev_virtual = ev.virtual === true || ev.virtual === 1 || ev.virtual === '1';
let match = false;
if (qry_physical === true && ev_physical) match = true;
if (qry_virtual === true && ev_virtual) match = true;
if (!match) return false;
}
@@ -855,7 +855,7 @@ export function sync_config__event_pres_mgmt({
pres_mgmt_cfg_remote?.hide__presentation_code ?? false;
pres_mgmt_cfg_local.hide__presentation_datetime =
pres_mgmt_cfg_remote?.hide__presentation_datetime ?? false;
prev_mgmt_cfg_local.show_content__presentation_description =
pres_mgmt_cfg_local.show_content__presentation_description =
pres_mgmt_cfg_remote?.show_content__presentation_description ?? false;
pres_mgmt_cfg_local.hide__presenter_code =
pres_mgmt_cfg_remote?.hide__presenter_code ?? false;

View File

@@ -489,7 +489,7 @@
// WARNING: This function returns a list. We only want the first one. There should be no more than 1 record returned.
// WARNING: This function returns a list. We only want the first one. There should be no more than 1 record returned.
// We use enabled: 'all' and hidden: 'all' to ensure we find the person record even if
// We use enabled: 'all' and hidden: 'all' to ensure we find the person record even if
// technical fields like 'hide' are NULL or the record is temporarily disabled.
ae_promises['person'] = core_func
.load_ae_obj_li__person({
@@ -596,7 +596,7 @@
// WARNING: This function returns a list. We only want the first one. There should be no more than 1 record returned.
// WARNING: This function returns a list. We only want the first one. There should be no more than 1 record returned.
// We use enabled: 'all' and hidden: 'all' to ensure we find the person record even if
// We use enabled: 'all' and hidden: 'all' to ensure we find the person record even if
// technical fields like 'hide' are NULL or the record is temporarily disabled.
ae_promises['person'] = core_func
.load_ae_obj_li__person({
@@ -833,7 +833,7 @@
class="top-center bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg border-gray-200 dark:border-gray-700 divide-gray-200 dark:divide-gray-700 shadow-md relative mx-auto w-full divide-y"
>
<div class="modal-box flex flex-col gap-2 items-center justify-center">
<!-- If the user is a global Manger or Super then they can change the password for any user. Otherwise, they can only change their own password. Show email address field for a quick lookup to get the user.id. -->
<!-- If the user is a global Manager or Super then they can change the password for any user. Otherwise, they can only change their own password. Show email address field for a quick lookup to get the user.id. -->
<div class="flex flex-col flex-wrap gap-2">
<span class="text-sm text-gray-500">
Your Username: {$ae_loc?.user?.username ?? '-- not set --'}