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

@@ -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 });
}
}