Implemented offline-first fast-paths and hardened API/Layout resilience. Added reactive offline banner, root error page, and ghost site fallbacks to handle server downtime and connection loss without crashing.

This commit is contained in:
Scott Idem
2026-01-16 16:41:32 -05:00
parent 8b611e7875
commit a10accfaaf
14 changed files with 536 additions and 564 deletions

View File

@@ -51,6 +51,12 @@ export const get_object = async function get_object({
return false;
}
// FAIL FAST: Check if we are explicitly offline to avoid long browser timeouts
if (typeof navigator !== 'undefined' && !navigator.onLine) {
if (log_lvl) console.log('get_object: Browser is offline. Failing fast to allow cache fallback.');
return false;
}
const url = new URL(endpoint, api_cfg['base_url']);
Object.keys(params).forEach((key) => url.searchParams.append(key, params[key]));
@@ -110,6 +116,12 @@ export const get_object = async function get_object({
}
for (let attempt = 1; attempt <= retry_count; attempt++) {
// FAIL FAST: Check if we are explicitly offline to avoid long browser timeouts
if (typeof navigator !== 'undefined' && !navigator.onLine) {
if (log_lvl) console.log(`get_object: Browser is offline (attempt ${attempt}). Failing fast to allow cache fallback.`);
return false;
}
try {
const response = await fetch_method(url.toString(), fetchOptions).catch(function (
error: any
@@ -118,9 +130,17 @@ export const get_object = async function get_object({
'API GET Object *fetch* request was aborted or failed in an unexpected way.',
error
);
// Return the error so we can check it
return error;
});
clearTimeout(timeoutId);
// If the catch returned an Error object instead of a Response
if (response instanceof Error || (response && response.name === 'TypeError')) {
console.log('API GET Object: Detected NetworkError or TypeError. Failing fast.');
return false; // Skip retries for dead servers/DNS
}
if (!response) {
if (log_lvl > 1) {
console.log(

View File

@@ -26,23 +26,69 @@ export async function lookup_site_domain({
console.log(`*** lookup_site_domain() *** fqdn=${fqdn}`);
}
// We use get_ae_obj_id_crud because we are looking up by a unique field (fqdn) rather than ID.
// This is the older method that uses the /crud/site/domain/:id endpoint.
const result = await api.get_ae_obj_id_crud({
api_cfg,
no_account_id: true,
obj_type: 'site_domain',
obj_id: fqdn,
use_alt_table: true,
use_alt_base: true,
log_lvl
});
try {
// We use get_ae_obj_id_crud because we are looking up by a unique field (fqdn) rather than ID.
// This is the older method that uses the /crud/site/domain/:id endpoint.
const result = await api.get_ae_obj_id_crud({
api_cfg,
no_account_id: true,
obj_type: 'site_domain',
obj_id: fqdn,
use_alt_table: true,
use_alt_base: true,
log_lvl
});
if (result) {
return result;
if (result) {
// Standardize and save to cache
const processed_obj_li = await process_ae_obj__site_domain_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'site_domain',
obj_li: processed_obj_li,
properties_to_save: properties_to_save__site_domain,
log_lvl
});
return result;
}
} catch (error: any) {
console.log('Site domain lookup failed (API Error).', error);
}
return null;
if (log_lvl) console.log('Attempting to load site domain from local cache...');
const cached = await db_core.site_domain.where('fqdn').equals(fqdn).first();
if (cached) {
return cached as any;
}
// CRITICAL FALLBACK: If both API and Cache fail, return a "Ghost" site domain object
// to prevent the 403 error from blocking the UI. Page components will handle empty data.
console.error('AE_SITE_CRITICAL: Site domain lookup failed API and CACHE. Returning ghost object.');
return {
id: 'ghost',
id_random: 'ghost',
site_domain_id: 'ghost',
site_domain_id_random: 'ghost',
site_id: 'ghost',
site_id_random: 'ghost',
account_id: 'ghost',
account_id_random: 'ghost',
account_code: 'ghost',
account_name: 'Ghost Account (Offline)',
fqdn: fqdn,
enable: '1',
header_image_path: '',
style_href: '',
google_tracking_id: '',
access_code_kv_json: {},
cfg_json: {},
access_key: '',
site_domain_access_key: ''
} as any;
}
// Updated 2026-01-07
@@ -61,50 +107,69 @@ export async function lookup_site_domain_v3({
console.log(`*** lookup_site_domain_v3() *** fqdn=${fqdn}`);
}
// CRITICAL: For the unauthenticated Bootstrap lookup, we must NOT send
// any existing auth tokens or account IDs that might be in the global config.
const guest_api_cfg = { ...api_cfg };
guest_api_cfg.headers = { ...api_cfg.headers };
const auth_props = [
'x-account-id',
'x-aether-api-token',
'Authorization',
'authorization',
'jwt',
'JWT'
];
auth_props.forEach(prop => {
delete guest_api_cfg.headers[prop];
delete guest_api_cfg.headers[prop.toLowerCase()];
delete guest_api_cfg.headers[prop.replaceAll('-', '_')];
});
delete guest_api_cfg.jwt;
delete guest_api_cfg.account_id;
try {
// CRITICAL: For the unauthenticated Bootstrap lookup, we must NOT send
// any existing auth tokens or account IDs that might be in the global config.
const guest_api_cfg = { ...api_cfg };
guest_api_cfg.headers = { ...api_cfg.headers };
const auth_props = [
'x-account-id',
'x-aether-api-token',
'Authorization',
'authorization',
'jwt',
'JWT'
];
auth_props.forEach(prop => {
delete guest_api_cfg.headers[prop];
delete guest_api_cfg.headers[prop.toLowerCase()];
delete guest_api_cfg.headers[prop.replaceAll('-', '_')];
});
delete guest_api_cfg.jwt;
delete guest_api_cfg.account_id;
const search_query = {
q: fqdn
};
const search_query = {
q: fqdn
};
// 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({
api_cfg: guest_api_cfg,
obj_type: 'site_domain',
search_query,
view, // This view should ideally join with site and account for the root lookup
enabled: 'enabled',
hidden: 'all',
limit: 1,
log_lvl
});
// 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({
api_cfg: guest_api_cfg,
obj_type: 'site_domain',
search_query,
view, // This view should ideally join with site and account for the root lookup
enabled: 'enabled',
hidden: 'all',
limit: 1,
log_lvl
});
if (result_li && result_li.length > 0) {
return result_li[0];
if (result_li && result_li.length > 0) {
const result = result_li[0];
// Standardize and save to cache
const processed_obj_li = await process_ae_obj__site_domain_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'site_domain',
obj_li: processed_obj_li,
properties_to_save: properties_to_save__site_domain,
log_lvl
});
return result;
}
} catch (error: any) {
console.log('Site domain lookup V3 failed.', error);
}
return null;
if (log_lvl) console.log('Attempting to load site domain from local cache (V3 fallback)...');
const cached = await db_core.site_domain.where('fqdn').equals(fqdn).first();
return (cached as any) || null;
}
export async function load_ae_obj_id__site({

View File

@@ -385,180 +385,49 @@ async function update_ae_obj_id_crud_v2({
new_field_value
);
}
let patch_result: any = null;
ae_promises.api_update__ae_obj = await api
.update_ae_obj_id_crud({
api_cfg: api_cfg,
obj_type: object_type,
obj_id: object_id,
field_name: field_name,
field_value: new_field_value,
// fields: data,
key: api_cfg.api_crud_super_key,
// jwt: null,
// params: params,
// data: patch_data,
log_lvl: log_lvl
})
.then(function (results) {
console.log('PATCH Promise', results);
const results = await api.update_ae_obj_id_crud({
api_cfg: api_cfg,
obj_type: object_type,
obj_id: object_id,
field_name: field_name,
field_value: new_field_value,
key: api_cfg.api_crud_super_key,
log_lvl: log_lvl
});
if (results) {
console.log(
`Patched - Field Name: ${field_name} with new Field Value: ${new_field_value}`
);
patch_result = 'PATCH complete';
if (results) {
if (log_lvl) {
console.log(`Patched - Field Name: ${field_name} with new Field Value: ${new_field_value}`);
}
if (object_reload) {
if (log_lvl) {
console.log(`Reloading the object after patching...`);
}
// Reload the object to get the latest data. There is a special case for each type.
if (object_type == 'person') {
const load_person_obj = load_ae_obj_id__person({
api_cfg: api_cfg,
person_id: object_id,
log_lvl: log_lvl
});
return load_person_obj;
}
// if (object_type == 'user') {
// let load_user_obj = load_ae_obj_id__user({
// api_cfg: api_cfg,
// user_id: object_id,
// log_lvl: log_lvl
// });
// return load_user_obj;
// }
if (object_type == 'archive') {
const load_archive_obj = load_ae_obj_id__archive({
api_cfg: api_cfg,
archive_id: object_id,
log_lvl: log_lvl
});
return load_archive_obj;
}
if (object_type == 'archive_content') {
const load_archive_content_obj = load_ae_obj_id__archive_content({
api_cfg: api_cfg,
archive_content_id: object_id,
log_lvl: log_lvl
});
return load_archive_content_obj;
}
if (object_type == 'journal') {
const load_journal_obj = load_ae_obj_id__journal({
api_cfg: api_cfg,
journal_id: object_id,
log_lvl: log_lvl
});
return load_journal_obj;
}
if (object_type == 'journal_entry') {
const load_journal_entry_obj = load_ae_obj_id__journal_entry({
api_cfg: api_cfg,
journal_entry_id: object_id,
log_lvl: log_lvl
});
return load_journal_entry_obj;
}
if (object_type == 'event') {
const load_event_obj = load_ae_obj_id__event({
api_cfg: api_cfg,
event_id: object_id,
log_lvl: log_lvl
});
return load_event_obj;
}
if (object_type == 'event_device') {
const load_event_device_obj = load_ae_obj_id__event_device({
api_cfg: api_cfg,
event_device_id: object_id,
log_lvl: log_lvl
});
return load_event_device_obj;
}
if (object_type == 'event_file') {
const load_event_file_obj = load_ae_obj_id__event_file({
api_cfg: api_cfg,
event_file_id: object_id,
log_lvl: log_lvl
});
return load_event_file_obj;
}
if (object_type == 'event_location') {
const load_event_location_obj = load_ae_obj_id__event_location({
api_cfg: api_cfg,
event_location_id: object_id,
log_lvl: log_lvl
});
return load_event_location_obj;
}
if (object_type == 'event_presentation') {
const load_event_presentation_obj = load_ae_obj_id__event_presentation({
api_cfg: api_cfg,
event_presentation_id: object_id,
log_lvl: log_lvl
});
return load_event_presentation_obj;
}
if (object_type == 'event_presenter') {
const load_event_presenter_obj = load_ae_obj_id__event_presenter({
api_cfg: api_cfg,
event_presenter_id: object_id,
log_lvl: log_lvl
});
return load_event_presenter_obj;
}
if (object_type == 'event_session') {
const load_event_session_obj = load_ae_obj_id__event_session({
api_cfg: api_cfg,
event_session_id: object_id,
log_lvl: log_lvl
});
return load_event_session_obj;
}
if (object_type == 'post') {
const load_post_obj = load_ae_obj_id__post({
api_cfg: api_cfg,
post_id: object_id,
log_lvl: log_lvl
});
return load_post_obj;
}
if (object_type == 'post_comment') {
const load_post_comment_obj = load_ae_obj_id__post_comment({
api_cfg: api_cfg,
post_comment_id: object_id,
log_lvl: log_lvl
});
return load_post_comment_obj;
}
}
} else {
console.log(
`Not Patched - Field Name: ${field_name} with new Field Value: ${new_field_value}; Account ID: ${api_cfg.account_id}`
);
patch_result = 'PATCH failed';
return null;
if (object_reload) {
if (log_lvl) {
console.log(`Reloading the object after patching...`);
}
return null;
})
.catch(function (error: any) {
console.log('Something went wrong patching the record.');
console.log(error);
return null;
})
.finally(function () {
console.log('PATCH Promise finally');
});
// Trigger reloads based on object type. These are fire-and-forget or awaited internally by the library functions.
if (object_type == 'person') load_ae_obj_id__person({ api_cfg, person_id: object_id, log_lvl });
if (object_type == 'archive') load_ae_obj_id__archive({ api_cfg, archive_id: object_id, log_lvl });
if (object_type == 'archive_content') load_ae_obj_id__archive_content({ api_cfg, archive_content_id: object_id, log_lvl });
if (object_type == 'journal') load_ae_obj_id__journal({ api_cfg, journal_id: object_id, log_lvl });
if (object_type == 'journal_entry') load_ae_obj_id__journal_entry({ api_cfg, journal_entry_id: object_id, log_lvl });
if (object_type == 'event') load_ae_obj_id__event({ api_cfg, event_id: object_id, log_lvl });
if (object_type == 'event_device') load_ae_obj_id__event_device({ api_cfg, event_device_id: object_id, log_lvl });
if (object_type == 'event_file') load_ae_obj_id__event_file({ api_cfg, event_file_id: object_id, log_lvl });
if (object_type == 'event_location') load_ae_obj_id__event_location({ api_cfg, event_location_id: object_id, log_lvl });
if (object_type == 'event_presentation') load_ae_obj_id__event_presentation({ api_cfg, event_presentation_id: object_id, log_lvl });
if (object_type == 'event_presenter') load_ae_obj_id__event_presenter({ api_cfg, event_presenter_id: object_id, log_lvl });
if (object_type == 'event_session') load_ae_obj_id__event_session({ api_cfg, event_session_id: object_id, log_lvl });
if (object_type == 'post') load_ae_obj_id__post({ api_cfg, post_id: object_id, log_lvl });
if (object_type == 'post_comment') load_ae_obj_id__post_comment({ api_cfg, post_comment_id: object_id, log_lvl });
}
} else {
if (log_lvl) {
console.log(`PATCH failed for ${object_type} ${object_id}`);
}
}
return ae_promises.api_update__ae_obj;
return results;
}
async function download_export__obj_type({

View File

@@ -38,8 +38,9 @@ export interface GenericCrudArgs {
inc_obj_type_li?: string[]; // Optional list of object types to include
data_kv?: key_val;
enabled?: 'enabled' | 'disabled' | 'all';
enabled?: 'enabled' | 'not_enabled' | 'all';
hidden?: 'not_hidden' | 'hidden' | 'all';
method?: string;
limit?: number;
offset?: number;
order_by_li?: Record<string, 'ASC' | 'DESC'> | Record<string, 'ASC' | 'DESC'>[] | null;
@@ -52,6 +53,11 @@ export interface GenericCrudArgs {
export async function load_ae_obj_id(args: GenericCrudArgs): Promise<any> {
const { api_cfg, obj_type, obj_id, log_lvl = 0 } = args;
if (!obj_id) {
if (log_lvl) console.warn('load_ae_obj_id called without obj_id');
return null;
}
if (log_lvl) {
console.log(`*** load_ae_obj_id() *** obj_type=${obj_type} obj_id=${obj_id}`);
}
@@ -97,7 +103,7 @@ export async function load_ae_obj_li(args: GenericCrudArgs): Promise<any> {
api_cfg,
obj_type,
for_obj_type,
for_obj_id,
for_obj_id: for_obj_id ?? '',
enabled,
hidden,
order_by_li,
@@ -136,6 +142,11 @@ export async function create_ae_obj(args: GenericCrudArgs): Promise<any> {
export async function update_ae_obj(args: GenericCrudArgs): Promise<any> {
const { api_cfg, obj_type, obj_id, data_kv, log_lvl = 0 } = args;
if (!obj_id) {
if (log_lvl) console.warn('update_ae_obj called without obj_id');
return null;
}
if (log_lvl) {
console.log(`*** update_ae_obj() *** obj_type=${obj_type} obj_id=${obj_id}`, data_kv);
}
@@ -158,6 +169,11 @@ export async function update_ae_obj(args: GenericCrudArgs): Promise<any> {
export async function delete_ae_obj_id(args: GenericCrudArgs): Promise<any> {
const { api_cfg, obj_type, obj_id, method = 'delete', log_lvl = 0 } = args;
if (!obj_id) {
if (log_lvl) console.warn('delete_ae_obj_id called without obj_id');
return null;
}
if (log_lvl) {
console.log(`*** delete_ae_obj_id() *** obj_type=${obj_type} obj_id=${obj_id}`);
}

View File

@@ -12,7 +12,7 @@ import { load_ae_obj_li__event_badge_template } from '$lib/ae_events/ae_events__
const ae_promises: key_val = {};
// Updated 2026-01-06
// Updated 2026-01-16
export async function load_ae_obj_id__event({
api_cfg,
event_id,
@@ -38,6 +38,16 @@ export async function load_ae_obj_id__event({
console.log(`*** load_ae_obj_id__event() *** event_id=${event_id}`);
}
// Check if offline
if (typeof navigator !== 'undefined' && !navigator.onLine) {
if (log_lvl) console.log('Browser is offline. Skipping API and attempting cache load.');
ae_promises.load__event_obj = await db_events.event.get(event_id);
if (ae_promises.load__event_obj) {
return await _handle_nested_loads(ae_promises.load__event_obj, { api_cfg, inc_device_li, inc_location_li, inc_session_li, inc_template_li, log_lvl });
}
return null;
}
try {
const event_obj_get_result = await api.get_ae_obj_v3({
api_cfg: api_cfg,
@@ -81,44 +91,52 @@ export async function load_ae_obj_id__event({
}
}
if (ae_promises.load__event_obj) {
const current_event_id = ae_promises.load__event_obj.event_id || ae_promises.load__event_obj.id;
if (inc_device_li) {
ae_promises.load__event_obj.event_device_obj_li = await load_ae_obj_li__event_device({
api_cfg,
for_obj_type: 'event',
for_obj_id: current_event_id,
log_lvl
});
}
if (inc_location_li) {
ae_promises.load__event_obj.event_location_obj_li = await load_ae_obj_li__event_location({
api_cfg,
for_obj_type: 'event',
for_obj_id: current_event_id,
log_lvl
});
}
if (inc_session_li) {
ae_promises.load__event_obj.event_session_obj_li = await load_ae_obj_li__event_session({
api_cfg,
for_obj_type: 'event',
for_obj_id: current_event_id,
log_lvl
});
}
if (inc_template_li) {
ae_promises.load__event_obj.event_badge_template_obj_li =
await load_ae_obj_li__event_badge_template({
api_cfg,
event_id: current_event_id,
log_lvl
});
}
if (!ae_promises?.load__event_obj) {
return null;
}
return ae_promises.load__event_obj;
return await _handle_nested_loads(ae_promises.load__event_obj, { api_cfg, inc_device_li, inc_location_li, inc_session_li, inc_template_li, log_lvl });
}
/**
* Shared logic for loading nested child collections
*/
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,
for_obj_type: 'event',
for_obj_id: current_event_id,
log_lvl
});
}
if (inc_location_li) {
event_obj.event_location_obj_li = await load_ae_obj_li__event_location({
api_cfg,
for_obj_type: 'event',
for_obj_id: current_event_id,
log_lvl
});
}
if (inc_session_li) {
event_obj.event_session_obj_li = await load_ae_obj_li__event_session({
api_cfg,
for_obj_type: 'event',
for_obj_id: current_event_id,
log_lvl
});
}
if (inc_template_li) {
event_obj.event_badge_template_obj_li =
await load_ae_obj_li__event_badge_template({
api_cfg,
event_id: current_event_id,
log_lvl
});
}
return event_obj;
}
// Updated 2026-01-06
@@ -152,6 +170,16 @@ 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.');
ae_promises.load__event_obj_li = await db_events.event
.where('account_id').equals(for_obj_id)
.toArray();
return ae_promises.load__event_obj_li || [];
}
let promise;
if (qry_conference !== null) {
@@ -548,259 +576,98 @@ async function _process_generic_props<T extends Record<string, any>>({
}
// Updated 2025-11-13
export async function process_ae_obj__event_props({
obj_li,
log_lvl = 0
}: {
obj_li: any[];
log_lvl?: number;
}) {
return _process_generic_props({
obj_li,
obj_type: 'event',
log_lvl,
specific_processor: (obj) => {
if (obj.event_code) {
obj.code = obj.event_code;
}
return obj;
}
});
}
// This function will process the event config, specifically for presentation management.
export function sync_config__event_pres_mgmt({
pres_mgmt_cfg_remote, // This is the remote config that will be compared.
pres_mgmt_cfg_local, // This is the local config that will be updated.
pres_mgmt_cfg_remote,
pres_mgmt_cfg_local,
log_lvl = 0
}: {
pres_mgmt_cfg_remote: key_val;
pres_mgmt_cfg_local: key_val;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(
`*** sync_config__event_pres_mgmt() *** pres_mgmt_cfg_remote:`,
pres_mgmt_cfg_remote
);
}
// Deal with things that can not be overridden first:
// Labels:
pres_mgmt_cfg_local.label__person_external_id =
pres_mgmt_cfg_remote?.label__person_external_id ?? 'External ID';
pres_mgmt_cfg_local.label__presenter_external_id =
pres_mgmt_cfg_remote?.label__presenter_external_id ?? 'External ID';
pres_mgmt_cfg_local.label__session_poc_type =
pres_mgmt_cfg_remote?.label__session_poc_type ?? 'poc';
pres_mgmt_cfg_local.label__session_poc_name =
pres_mgmt_cfg_remote?.label__session_poc_name_short ?? 'POC';
pres_mgmt_cfg_local.label__session_poc_name =
pres_mgmt_cfg_remote?.label__session_poc_name ?? 'Point of Contact';
// Hide content:
pres_mgmt_cfg_local.hide__session_poc = pres_mgmt_cfg_remote?.hide__session_poc ?? false;
// Required fields or options (agreements):
pres_mgmt_cfg_local.require__presenter_agree =
pres_mgmt_cfg_remote?.require__presenter_agree ?? false; // In use
pres_mgmt_cfg_remote?.require__presenter_agree ?? false;
pres_mgmt_cfg_local.require__session_agree =
pres_mgmt_cfg_remote?.require__session_agree ?? false; // New and in progress
// Show content:
pres_mgmt_cfg_remote?.require__session_agree ?? false;
pres_mgmt_cfg_local.show__copy_access_link =
pres_mgmt_cfg_remote?.show__copy_access_link ?? false;
pres_mgmt_cfg_local.show__email_access_link =
pres_mgmt_cfg_remote?.show__email_access_link ?? false;
pres_mgmt_cfg_local.file_purpose_option_kv =
pres_mgmt_cfg_remote?.file_purpose_option_kv ?? null;
// Deal with things that can be overridden:
// Locking the config is targeted at the trusted staff level and below. It is more or less ignored at the global manager and super levels. It may be enforced at the staff admin level?
if (pres_mgmt_cfg_local.lock_config) {
console.log(`The config should be locked! Forcing the sync!`);
// This is to forcibly sync the local config with the remote config.
pres_mgmt_cfg_local.sync_local_config = true;
} else {
// Do not override the preference for syncing the local config with the remote config.
console.log(
`The config is not locked. Currently set to sync? ${pres_mgmt_cfg_local.sync_local_config}`
);
}
if (pres_mgmt_cfg_local?.sync_local_config) {
if (log_lvl) {
console.log(`Syncing the local config with the remote config!!!`);
}
// Hide content:
pres_mgmt_cfg_local.hide__location_code =
pres_mgmt_cfg_remote?.hide__location_code ?? false;
pres_mgmt_cfg_local.hide__presentation_code =
pres_mgmt_cfg_remote?.hide__presentation_code ?? false;
pres_mgmt_cfg_local.hide__presentation_datetime =
pres_mgmt_cfg_remote?.hide__presentation_datetime ?? false;
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;
pres_mgmt_cfg_local.hide__presenter_biography =
pres_mgmt_cfg_remote?.hide__presenter_biography ?? false;
pres_mgmt_cfg_local.hide__session_code = pres_mgmt_cfg_remote?.hide__session_code ?? false;
pres_mgmt_cfg_local.hide__session_description =
pres_mgmt_cfg_remote?.hide__session_description ?? false;
pres_mgmt_cfg_local.hide__session_location =
pres_mgmt_cfg_remote?.hide__session_location ?? false;
pres_mgmt_cfg_local.hide__session_msg = pres_mgmt_cfg_remote?.hide__session_msg ?? false;
pres_mgmt_cfg_local.hide__session_poc_profile =
pres_mgmt_cfg_remote?.hide__session_poc_profile ?? false; // This should still allow the POC name to be shown.
pres_mgmt_cfg_remote?.hide__session_poc_profile ?? false;
pres_mgmt_cfg_local.hide__session_poc_biography =
pres_mgmt_cfg_remote?.hide__session_poc_biography ?? false; // New and in progress
pres_mgmt_cfg_remote?.hide__session_poc_biography ?? false;
pres_mgmt_cfg_local.hide__session_poc_profile_pic =
pres_mgmt_cfg_remote?.hide__session_poc_profile_pic ?? false; // New and in progress
pres_mgmt_cfg_remote?.hide__session_poc_profile_pic ?? false;
pres_mgmt_cfg_local.hide_launcher_link = pres_mgmt_cfg_remote?.hide_launcher_link ?? false;
pres_mgmt_cfg_local.hide_launcher_link_legacy =
pres_mgmt_cfg_remote?.hide_launcher_link_legacy ?? false;
}
if (log_lvl) {
console.log(`pres_mgmt_cfg_local:`, pres_mgmt_cfg_local);
}
return pres_mgmt_cfg_local;
}
}

View File

@@ -11,10 +11,11 @@ import { load_ae_obj_li__event_session } from './ae_events__event_session';
const ae_promises: key_val = {};
// Updated 2025-05-23
// Updated 2026-01-16
export async function load_ae_obj_id__event_location({
api_cfg,
event_location_id,
inc_device_li = false,
inc_file_li = false,
inc_session_li = false,
try_cache = true,
@@ -22,6 +23,7 @@ export async function load_ae_obj_id__event_location({
}: {
api_cfg: any;
event_location_id: string;
inc_device_li?: boolean;
inc_file_li?: boolean;
inc_session_li?: boolean;
try_cache?: boolean;
@@ -35,6 +37,16 @@ export async function load_ae_obj_id__event_location({
const params = {};
// Check if offline
if (typeof navigator !== 'undefined' && !navigator.onLine) {
if (log_lvl) console.log('Browser is offline. Skipping API and attempting cache load.');
ae_promises.load__event_location_obj = await db_events.location.get(event_location_id);
if (ae_promises.load__event_location_obj) {
return await _handle_nested_loads(ae_promises.load__event_location_obj, { api_cfg, inc_device_li, inc_file_li, inc_session_li, try_cache, log_lvl });
}
return null;
}
try {
ae_promises.load__event_location_obj = await api
.get_ae_obj_id_crud({
@@ -78,19 +90,31 @@ export async function load_ae_obj_id__event_location({
}
}
if (log_lvl) {
console.log('ae_promises.load__event_location_obj:', ae_promises.load__event_location_obj);
}
if (!ae_promises?.load__event_location_obj) {
return null;
}
const current_location_id = ae_promises.load__event_location_obj.event_location_id || ae_promises.load__event_location_obj.id;
return await _handle_nested_loads(ae_promises.load__event_location_obj, { api_cfg, inc_device_li, inc_file_li, inc_session_li, try_cache, log_lvl });
}
/**
* Helper to handle nested collection loads for a location
*/
async function _handle_nested_loads(location_obj: any, { api_cfg, inc_device_li, inc_file_li, inc_session_li, try_cache, log_lvl }: any) {
const current_location_id = location_obj.event_location_id || location_obj.id;
if (inc_device_li) {
location_obj.event_device_obj_li = await load_ae_obj_li__event_device({
api_cfg: api_cfg,
for_obj_type: 'event_location',
for_obj_id: current_location_id,
log_lvl: log_lvl
});
}
if (inc_file_li) {
// Load the files for the location
ae_promises.load__event_location_obj.event_file_li = await load_ae_obj_li__event_file({
location_obj.event_file_li = await load_ae_obj_li__event_file({
api_cfg: api_cfg,
for_obj_type: 'event_location',
for_obj_id: current_location_id,
@@ -103,7 +127,7 @@ export async function load_ae_obj_id__event_location({
if (inc_session_li) {
// Load the sessions for the location
ae_promises.load__event_location_obj.event_session_li = await load_ae_obj_li__event_session({
location_obj.event_session_li = await load_ae_obj_li__event_session({
api_cfg: api_cfg,
for_obj_type: 'event_location',
for_obj_id: current_location_id,
@@ -114,7 +138,7 @@ export async function load_ae_obj_id__event_location({
});
}
return ae_promises.load__event_location_obj;
return location_obj;
}
// Updated 2025-05-23
@@ -164,6 +188,20 @@ export async function load_ae_obj_li__event_location({
const params_json: key_val = {};
// Check if offline
if (typeof navigator !== 'undefined' && !navigator.onLine) {
if (log_lvl) console.log('Browser is offline. Skipping API and attempting cache load.');
ae_promises.load__event_location_obj_li = await db_events.location
.where('event_id').equals(for_obj_id)
.toArray();
if (ae_promises.load__event_location_obj_li) {
for (let i = 0; i < ae_promises.load__event_location_obj_li.length; i++) {
await _handle_nested_loads(ae_promises.load__event_location_obj_li[i], { api_cfg, inc_device_li, inc_file_li, inc_session_li, try_cache, log_lvl });
}
}
return ae_promises.load__event_location_obj_li || [];
}
try {
ae_promises.load__event_location_obj_li = await api
.get_ae_obj_li_for_obj_id_crud_v2({
@@ -220,54 +258,8 @@ export async function load_ae_obj_li__event_location({
}
if (ae_promises.load__event_location_obj_li) {
if (inc_device_li) {
for (let i = 0; i < ae_promises.load__event_location_obj_li.length; i++) {
const event_location_obj = ae_promises.load__event_location_obj_li[i];
const current_location_id = event_location_obj.event_location_id || event_location_obj.id;
event_location_obj.event_device_obj_li = await load_ae_obj_li__event_device({
api_cfg: api_cfg,
for_obj_type: 'event_location',
for_obj_id: current_location_id,
params: { qry__enabled: enabled, qry__limit: limit },
try_cache: try_cache,
log_lvl: log_lvl
});
}
}
if (inc_file_li) {
for (let i = 0; i < ae_promises.load__event_location_obj_li.length; i++) {
const event_location_obj = ae_promises.load__event_location_obj_li[i];
const current_location_id = event_location_obj.event_location_id || event_location_obj.id;
event_location_obj.event_file_li = await load_ae_obj_li__event_file({
api_cfg: api_cfg,
for_obj_type: 'event_location',
for_obj_id: current_location_id,
enabled: enabled,
limit: limit,
try_cache: try_cache,
log_lvl: log_lvl
});
}
}
if (inc_session_li) {
for (let i = 0; i < ae_promises.load__event_location_obj_li.length; i++) {
const event_location_obj = ae_promises.load__event_location_obj_li[i];
const current_location_id = event_location_obj.event_location_id || event_location_obj.id;
event_location_obj.event_session_li = await load_ae_obj_li__event_session({
api_cfg: api_cfg,
for_obj_type: 'event_location',
for_obj_id: current_location_id,
enabled: enabled,
limit: limit,
try_cache: try_cache,
log_lvl: log_lvl
});
}
for (let i = 0; i < ae_promises.load__event_location_obj_li.length; i++) {
await _handle_nested_loads(ae_promises.load__event_location_obj_li[i], { api_cfg, inc_device_li, inc_file_li, inc_session_li, try_cache, log_lvl });
}
}

View File

@@ -44,6 +44,16 @@ export async function load_ae_obj_id__event_session({
const params = {};
// Check if we are explicitly offline to avoid long browser timeouts
if (typeof navigator !== 'undefined' && !navigator.onLine) {
if (log_lvl) console.log('Browser is offline. Skipping API and attempting cache load.');
ae_promises.load__event_session_obj = await db_events.session.get(event_session_id);
if (ae_promises.load__event_session_obj) {
return await _handle_nested_loads(ae_promises.load__event_session_obj, { api_cfg, inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li, enabled, hidden, limit, offset, try_cache, log_lvl });
}
return null;
}
try {
ae_promises.load__event_session_obj = await api.get_ae_obj_id_crud({
api_cfg: api_cfg,
@@ -94,11 +104,15 @@ export async function load_ae_obj_id__event_session({
return null;
}
const current_session_id = ae_promises.load__event_session_obj.event_session_id || ae_promises.load__event_session_obj.id;
return await _handle_nested_loads(ae_promises.load__event_session_obj, { api_cfg, inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li, enabled, hidden, limit, offset, try_cache, log_lvl });
}
async function _handle_nested_loads(session_obj: any, { api_cfg, inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li, enabled, hidden, limit, offset, try_cache, log_lvl }: any) {
const current_session_id = session_obj.event_session_id || session_obj.id;
if (inc_file_li) {
// Load the files for the session
ae_promises.load__event_session_obj.event_file_li = await load_ae_obj_li__event_file({
session_obj.event_file_li = await load_ae_obj_li__event_file({
api_cfg: api_cfg,
for_obj_type: 'event_session',
for_obj_id: current_session_id,
@@ -111,7 +125,7 @@ export async function load_ae_obj_id__event_session({
if (inc_presentation_li) {
// Load the presentations for the session
ae_promises.load__event_session_obj.event_presentation_li = await load_ae_obj_li__event_presentation({
session_obj.event_presentation_li = await load_ae_obj_li__event_presentation({
api_cfg: api_cfg,
for_obj_type: 'event_session',
for_obj_id: current_session_id,
@@ -127,7 +141,7 @@ export async function load_ae_obj_id__event_session({
});
}
return ae_promises.load__event_session_obj;
return session_obj;
}
// Updated 2025-05-22
@@ -179,6 +193,21 @@ export async function load_ae_obj_li__event_session({
const params_json: key_val = {};
// Check if offline
if (typeof navigator !== 'undefined' && !navigator.onLine) {
if (log_lvl) console.log('Browser is offline. Skipping API and attempting cache load.');
ae_promises.load__event_session_obj_li = await db_events.session
.where('for_id').equals(for_obj_id)
.toArray();
// Trigger nested loads if requested
if (ae_promises.load__event_session_obj_li) {
for (let i = 0; i < ae_promises.load__event_session_obj_li.length; i++) {
await _handle_nested_loads(ae_promises.load__event_session_obj_li[i], { api_cfg, inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li, enabled, hidden, limit, offset, try_cache, log_lvl });
}
}
return ae_promises.load__event_session_obj_li || [];
}
try {
ae_promises.load__event_session_obj_li = await api
.get_ae_obj_li_for_obj_id_crud_v2({
@@ -234,42 +263,9 @@ export async function load_ae_obj_li__event_session({
}
}
if (inc_file_li && ae_promises.load__event_session_obj_li) {
if (ae_promises.load__event_session_obj_li) {
for (let i = 0; i < ae_promises.load__event_session_obj_li.length; i++) {
const event_session_obj = ae_promises.load__event_session_obj_li[i];
const current_session_id = event_session_obj.event_session_id || event_session_obj.id;
event_session_obj.event_file_li = await load_ae_obj_li__event_file({
api_cfg: api_cfg,
for_obj_type: 'event_session',
for_obj_id: current_session_id,
enabled: enabled,
limit: limit,
try_cache: try_cache,
log_lvl: log_lvl
});
}
}
if (inc_presentation_li && ae_promises.load__event_session_obj_li) {
for (let i = 0; i < ae_promises.load__event_session_obj_li.length; i++) {
const event_session_obj = ae_promises.load__event_session_obj_li[i];
const current_session_id = event_session_obj.event_session_id || event_session_obj.id;
event_session_obj.event_presentation_li = await load_ae_obj_li__event_presentation({
api_cfg: api_cfg,
for_obj_type: 'event_session',
for_obj_id: current_session_id,
inc_file_li: inc_all_file_li,
inc_presenter_li: inc_presenter_li,
enabled: enabled,
hidden: hidden,
limit: limit,
offset: offset,
params: params,
try_cache: try_cache,
log_lvl: log_lvl
});
await _handle_nested_loads(ae_promises.load__event_session_obj_li[i], { api_cfg, inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li, enabled, hidden, limit, offset, try_cache, log_lvl });
}
}

63
src/routes/+error.svelte Normal file
View File

@@ -0,0 +1,63 @@
<script lang="ts">
import { page } from '$app/state';
import { RefreshCw, Home, AlertTriangle } from '@lucide/svelte';
import { browser } from '$app/environment';
let status = $derived(page.status);
let message = $derived(page.error?.message || 'An unexpected error occurred');
// Check if it looks like a connection/API failure
let is_connection_error = $derived(
message.toLowerCase().includes('site lookup failed') ||
message.toLowerCase().includes('fetch') ||
status === 500
);
</script>
<div class="flex flex-col items-center justify-center min-h-screen p-4 bg-surface-50 dark:bg-surface-900 text-center">
<div class="max-w-md p-8 rounded-2xl shadow-xl bg-white dark:bg-surface-800 border border-surface-200 dark:border-surface-700">
<div class="mb-6 flex justify-center">
<div class="p-4 rounded-full bg-error-100 dark:bg-error-900 text-error-600 dark:text-error-400">
<AlertTriangle size={48} />
</div>
</div>
<h1 class="text-6xl font-black mb-2 text-surface-900 dark:text-white">{status}</h1>
<h2 class="text-2xl font-bold mb-4 text-surface-700 dark:text-surface-200">
{#if is_connection_error}
Connection Failure
{:else}
Something went wrong
{/if}
</h2>
<p class="text-surface-600 dark:text-surface-400 mb-8 leading-relaxed">
{message}
</p>
<div class="flex flex-col gap-3">
<button
onclick={() => browser && window.location.reload()}
class="btn btn-lg preset-filled-primary-500 hover:preset-filled-primary-600 w-full font-bold"
>
<RefreshCw class="mr-2 size-5" />
Retry Connection
</button>
<a
href="/"
class="btn btn-lg preset-outlined-surface-500 w-full font-bold"
>
<Home class="mr-2 size-5" />
Return Home
</a>
</div>
{#if is_connection_error}
<div class="mt-8 pt-6 border-t border-surface-100 dark:border-surface-700 text-sm text-surface-500">
<p>If you are onsite at an event, please check your network connection or contact the registration desk.</p>
</div>
{/if}
</div>
</div>

View File

@@ -31,6 +31,7 @@
import 'highlight.js/styles/github-dark.css';
import { browser } from '$app/environment';
import { online } from 'svelte/reactivity/window';
import xml from 'highlight.js/lib/languages/xml'; // for HTML
import css from 'highlight.js/lib/languages/css';
import javascript from 'highlight.js/lib/languages/javascript';
@@ -110,6 +111,10 @@
let flag_denied: boolean = $state(false); // Access Denied
// let flag_reason: string = $state(''); // Reason: New version, Expired Cache, Access Denied
// Connection Status
let is_offline = $derived(browser && online.current === false);
let api_unreachable = $derived($ae_loc?.account_id === 'ghost');
// BEGIN: Sanity Checks:
// Added 2025-07-15
if (!$ae_loc?.sys_menu) {
@@ -773,6 +778,30 @@
<!-- <link rel="manifest" href="/manifest.json"> -->
</svelte:head>
{#if browser && (is_offline || api_unreachable)}
<div
class="fixed top-0 left-0 right-0 z-[100] p-4 bg-orange-600 text-white text-center shadow-2xl flex flex-row items-center justify-center gap-4"
>
<span class="text-xl font-bold">
{#if is_offline}
<span class="fas fa-wifi-slash mr-2"></span>
Connection Offline
{:else}
<span class="fas fa-server mr-2"></span>
API Server Unreachable
{/if}
</span>
<span class="hidden md:inline">Viewing cached data. Changes may not be saved.</span>
<button
class="btn btn-sm variant-filled-white text-orange-600 font-bold"
onclick={() => window.location.reload()}
>
<RefreshCw class="inline-block mr-1 size-4" />
Retry Connection
</button>
</div>
{/if}
{#if $ae_loc?.site_google_tracking_id && $ae_loc?.site_google_tracking_id.length > 0}
<Analytics bind:site_google_tracking_id={$ae_loc.site_google_tracking_id} />
{/if}

View File

@@ -6,6 +6,9 @@ import { lookup_site_domain } 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';
export const ssr = false;
export const prerender = false;
import {
PUBLIC_AE_API_PROTOCOL,
PUBLIC_AE_API_SERVER,
@@ -104,25 +107,41 @@ export async function load({ fetch, params, parent, route, url }) {
const fqdn = url.host;
const result = await lookup_site_domain({
api_cfg: ae_api_init,
fqdn,
view: 'base',
log_lvl
}).catch((err) => {
console.log('Site lookup failed in root layout.', err);
error(500, {
message: 'Site lookup aborted or failed! Check network and API.'
});
});
if (result === null) {
error(403, {
message: 'The site lookup failed! Check that the domain name is configured and enabled.'
let result: ae_SiteDomain | null = null;
try {
result = await lookup_site_domain({
api_cfg: ae_api_init,
fqdn,
view: 'base',
log_lvl
});
} catch (err) {
console.error('Site lookup critical failure in root layout.', err);
}
const json_data = result;
if (result === null) {
console.warn('Site lookup returned null. Attempting emergency ghost fallback.');
// This is a last resort if the internal library fallback also failed
result = {
id: 'ghost',
id_random: 'ghost',
account_id_random: 'ghost',
account_code: 'ghost',
account_name: 'Ghost Account',
site_id_random: 'ghost',
site_domain_id_random: 'ghost',
enable: '1',
header_image_path: '',
style_href: '',
google_tracking_id: '',
access_code_kv_json: {},
cfg_json: {},
access_key: '',
site_domain_access_key: ''
} as any;
}
const json_data = result as any;
account_id = json_data.account_id_random;
data_struct.account_id = json_data.account_id_random;
ae_acct.account_id = json_data.account_id_random;

View File

@@ -14,7 +14,17 @@ export async function load({ params, parent, url }) {
data.log_lvl = log_lvl;
const account_id = data.account_id;
const ae_acct = data[account_id];
let ae_acct = data[account_id];
if (!ae_acct) {
console.warn(`ae Events - [event_id] launcher +layout.ts: Account ${account_id} not found in data. Initializing ghost acct.`);
ae_acct = {
api: data.ae_api || {},
slct: {
account_id: account_id
}
};
}
// console.log(`ae_acct = `, ae_acct);
const event_id = params.event_id;

View File

@@ -14,19 +14,29 @@ export async function load({ params, parent, url }) {
data.log_lvl = log_lvl;
const account_id = data.account_id;
const ae_acct = data[account_id];
let ae_acct = data[account_id];
if (!ae_acct) {
console.warn(`ae Events - [event_id] launcher [event_location_id] +page.ts: Account ${account_id} not found in data. Initializing ghost acct.`);
ae_acct = {
api: data.ae_api || {},
slct: {
account_id: account_id
}
};
}
const event_location_id = params.event_location_id;
if (!event_location_id) {
console.log(
console.warn(
`ae Events - [event_id] launcher [event_location_id] +page.ts: The event_location_id was not found in the params.event_location_id!!!`
);
error(404, {
message: 'Events Pres Mgmt - Event Location ID not found'
});
// error(404, {
// message: 'Events Pres Mgmt - Event Location ID not found'
// });
}
if (browser) {
if (browser && event_location_id) {
if (log_lvl) {
console.log(
`ae_events launcher [event_location_id] +page.ts: event_location_id = `,
@@ -85,7 +95,7 @@ export async function load({ params, parent, url }) {
// ae_acct.slct.event_session_obj = load_event_session_obj;
// }
} else {
console.log(`ae pres_mgmt launcher [slug] +page.ts: browser = false`);
console.log(`ae pres_mgmt launcher [slug] +page.ts: browser = false or location_id missing`);
}
// WARNING: Precaution against shared data between sites and sessions.

View File

@@ -584,13 +584,17 @@
)} mx-0.5"
></span>
{event_file_obj.extension}
{#if result === null}
<span>
{#if result === null || result === false}
<span class="text-error-500">
<span class="fas fa-exclamation-triangle mx-1"></span>
Download failed!
Failed!
</span>
{/if}
<!-- </span> -->
{:catch error}
<span class="text-error-500" title={error?.message}>
<span class="fas fa-exclamation-circle mx-0.5"></span>
Error!
</span>
{/await}
</span>

View File

@@ -15,7 +15,17 @@ export async function load({ params, parent, url }) {
data.log_lvl = log_lvl;
const account_id = data.account_id;
const ae_acct = data[account_id];
let ae_acct = data[account_id];
if (!ae_acct) {
console.warn(`ae Events - [event_id] +layout.ts: Account ${account_id} not found in data. Initializing ghost acct.`);
ae_acct = {
api: data.ae_api || {},
slct: {
account_id: account_id
}
};
}
const event_id = params.event_id;
if (!event_id) {
@@ -54,16 +64,18 @@ export async function load({ params, parent, url }) {
// }
});
if (!load_event_obj) {
error(404, {
message: 'Events - Event not found'
});
console.warn(`Events - [event_id] +layout.ts: Event ${event_id} not found via API or Cache.`);
// error(404, {
// message: 'Events - Event not found'
// });
} else {
console.log(`load_event_obj = `, load_event_obj);
ae_acct.slct.event_obj = load_event_obj;
// ae_acct.slct.event_device_obj_li = load_event_obj.event_device_obj_li;
ae_acct.slct.event_location_obj_li = load_event_obj.event_location_obj_li;
ae_acct.slct.event_session_obj_li = load_event_obj.event_session_obj_li;
ae_acct.slct.badge_template_obj_li = load_event_obj.event_badge_template_obj_li;
}
console.log(`load_event_obj = `, load_event_obj);
ae_acct.slct.event_obj = load_event_obj;
// ae_acct.slct.event_device_obj_li = load_event_obj.event_device_obj_li;
ae_acct.slct.event_location_obj_li = load_event_obj.event_location_obj_li;
ae_acct.slct.event_session_obj_li = load_event_obj.event_session_obj_li;
ae_acct.slct.badge_template_obj_li = load_event_obj.event_badge_template_obj_li;
}
// WARNING: Precaution against shared data between sites and sessions.