From 79917edffce614377efb02669331ea0102fe03d7 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Mon, 26 Jan 2026 11:23:12 -0500 Subject: [PATCH] feat(launcher): implement device heartbeat and background sync engine - Hardened 'find_object_id' in Dexie to support 'event_' prefixed IDs. - Implemented singleton 'LauncherBackgroundSync' in root launcher layout. - Added V3-compliant heartbeat and room refresh cycles. - Fixed redundant component imports and Skeleton UI prop errors. - Added 'inc_file_li' support to event API loader. --- src/lib/ae_core/core__idb_dexie.ts | 19 +++++++--- src/lib/ae_events/ae_events__event.ts | 18 ++++++++-- .../ae_events/ae_events__event_location.ts | 1 + .../app_components/e_app_codemirror_v5.svelte | 2 +- .../elements/element_codemirror_editor.svelte | 2 +- .../(launcher)/launcher/+layout.svelte | 7 +--- .../launcher/[event_location_id]/+page.svelte | 2 +- .../launcher_background_sync.svelte | 35 ++++++++++++++----- src/routes/events/[event_id]/+page.svelte | 2 +- 9 files changed, 61 insertions(+), 27 deletions(-) diff --git a/src/lib/ae_core/core__idb_dexie.ts b/src/lib/ae_core/core__idb_dexie.ts index 17e5f9ef..a5460229 100644 --- a/src/lib/ae_core/core__idb_dexie.ts +++ b/src/lib/ae_core/core__idb_dexie.ts @@ -13,7 +13,14 @@ function find_object_id( table_name: string, log_lvl: number ): string | number | undefined { - const potential_keys = ['id', 'id_random', `${table_name}_id`, `${table_name}_id_random`]; + const potential_keys = [ + 'id', + 'id_random', + `${table_name}_id`, + `${table_name}_id_random`, + `event_${table_name}_id`, + `event_${table_name}_id_random` + ]; for (const key of potential_keys) { if (obj[key]) { @@ -27,10 +34,12 @@ function find_object_id( } } - console.error( - `Object is missing a valid ID for table "${table_name}". It will be skipped.`, - obj - ); + if (log_lvl) { + console.error( + `Object is missing a valid ID for table "${table_name}". It will be skipped. Checked keys: ${potential_keys.join(', ')}`, + obj + ); + } return undefined; } diff --git a/src/lib/ae_events/ae_events__event.ts b/src/lib/ae_events/ae_events__event.ts index 857e1142..47fea0f9 100644 --- a/src/lib/ae_events/ae_events__event.ts +++ b/src/lib/ae_events/ae_events__event.ts @@ -20,6 +20,7 @@ export async function load_ae_obj_id__event({ event_id, view = 'default', inc_device_li = false, + inc_file_li = false, inc_location_li = false, inc_session_li = false, inc_template_li = false, @@ -30,6 +31,7 @@ export async function load_ae_obj_id__event({ event_id: string; view?: string; inc_device_li?: boolean; + inc_file_li?: boolean; inc_location_li?: boolean; inc_session_li?: boolean; inc_template_li?: boolean; @@ -45,7 +47,7 @@ export async function load_ae_obj_id__event({ 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 await _handle_nested_loads(ae_promises.load__event_obj, { api_cfg, inc_device_li, inc_file_li, inc_location_li, inc_session_li, inc_template_li, log_lvl }); } return null; } @@ -97,13 +99,13 @@ export async function load_ae_obj_id__event({ return null; } - 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 await _handle_nested_loads(ae_promises.load__event_obj, { api_cfg, inc_device_li, inc_file_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) { +async function _handle_nested_loads(event_obj: any, { api_cfg, inc_device_li, inc_file_li, inc_location_li, inc_session_li, inc_template_li, log_lvl }: any) { const current_event_id = event_obj.event_id_random || event_obj.event_id || event_obj.id; if (inc_device_li) { @@ -114,6 +116,16 @@ async function _handle_nested_loads(event_obj: any, { api_cfg, inc_device_li, in log_lvl }); } + if (inc_file_li) { + event_obj.event_file_li = await load_ae_obj_li__event_file({ + api_cfg, + for_obj_type: 'event', + for_obj_id: current_event_id, + enabled: 'all', + limit: 100, + log_lvl + }); + } if (inc_location_li) { event_obj.event_location_obj_li = await load_ae_obj_li__event_location({ api_cfg, diff --git a/src/lib/ae_events/ae_events__event_location.ts b/src/lib/ae_events/ae_events__event_location.ts index cd548641..cd23e0e0 100644 --- a/src/lib/ae_events/ae_events__event_location.ts +++ b/src/lib/ae_events/ae_events__event_location.ts @@ -5,6 +5,7 @@ import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie'; import { db_events } from '$lib/ae_events/db_events'; import type { ae_EventLocation } from '$lib/types/ae_types'; import { load_ae_obj_li__event_file } from '$lib/ae_events/ae_events__event_file'; +import { load_ae_obj_li__event_session } from '$lib/ae_events/ae_events__event_session'; const ae_promises: key_val = {}; diff --git a/src/lib/app_components/e_app_codemirror_v5.svelte b/src/lib/app_components/e_app_codemirror_v5.svelte index d2b6067f..2bacfd03 100644 --- a/src/lib/app_components/e_app_codemirror_v5.svelte +++ b/src/lib/app_components/e_app_codemirror_v5.svelte @@ -43,7 +43,7 @@ class: classes = '' }: Props = $props(); - let editor_element: HTMLDivElement = $state(); + let editor_element: HTMLDivElement | undefined = $state(); // let editorView: any = $state(); // Removed redundant declaration let cm_modules: any = $state(); // To hold the dynamically loaded CodeMirror modules let editor_extensions: any[] = $state([]); diff --git a/src/lib/elements/element_codemirror_editor.svelte b/src/lib/elements/element_codemirror_editor.svelte index 58cc55a2..a673ccf6 100644 --- a/src/lib/elements/element_codemirror_editor.svelte +++ b/src/lib/elements/element_codemirror_editor.svelte @@ -10,7 +10,7 @@ let { content = $bindable(''), placeholder = 'Start typing...' }: Props = $props(); - let editor_container: HTMLDivElement = $state(); + let editor_container: HTMLDivElement | undefined = $state(); let editor_view: any; let cm: any; // Declare cm at the top level diff --git a/src/routes/events/[event_id]/(launcher)/launcher/+layout.svelte b/src/routes/events/[event_id]/(launcher)/launcher/+layout.svelte index 2028e1f8..1b299f1b 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher/+layout.svelte +++ b/src/routes/events/[event_id]/(launcher)/launcher/+layout.svelte @@ -47,7 +47,7 @@ events_trigger, events_trig } from '$lib/stores/ae_events_stores'; - // import { events_func } from '$lib/ae_events_functions'; + import { events_func } from '$lib/ae_events_functions'; import Launcher_cfg from '../launcher_cfg.svelte'; import Launcher_menu from '../launcher_menu.svelte'; @@ -1222,7 +1222,6 @@ @@ -1276,7 +1274,6 @@ @@ -1321,7 +1317,6 @@ open={$events_sess.launcher?.modal__open_event_file_id} autoclose={false} placement="top-center" - size="" class=" bg-gray-500/90 dark:bg-gray-800/90 text-gray-800 dark:text-gray-200 rounded-lg border-gray-200 dark:border-gray-700 diff --git a/src/routes/events/[event_id]/(launcher)/launcher/[event_location_id]/+page.svelte b/src/routes/events/[event_id]/(launcher)/launcher/[event_location_id]/+page.svelte index f230a13b..9eb5a324 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher/[event_location_id]/+page.svelte +++ b/src/routes/events/[event_id]/(launcher)/launcher/[event_location_id]/+page.svelte @@ -47,7 +47,7 @@ get_device_info } from '$lib/electron/electron_relay'; - import LauncherBackgroundSync from '../../launcher_background_sync.svelte'; + // import LauncherBackgroundSync from '../../launcher_background_sync.svelte'; // import Event_launcher_menu from '../../launcher_menu.svelte'; // import Event_launcher_session_view from '../../launcher_session_view.svelte'; diff --git a/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte b/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte index b5f40315..30977256 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte +++ b/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte @@ -6,6 +6,7 @@ import { onMount, onDestroy } from 'svelte'; import { ae_loc, ae_api } from '$lib/stores/ae_stores'; import { events_slct } from '$lib/stores/ae_events_stores'; + import { events_func } from '$lib/ae_events_functions'; import { db_events } from '$lib/ae_events/db_events'; import * as native from '$lib/electron/electron_relay'; @@ -14,6 +15,7 @@ let currently_syncing: string | null = $state(null); let sync_results: Record = $state({}); let sync_stats = $state({ total: 0, cached: 0, missing: 0 }); + let last_heartbeat: string | null = $state(null); // Loop Timings (Visible in UI) let loop_info = $state({ @@ -24,10 +26,10 @@ }); // Timer Handles - let timer__event: any = null; - let timer__device: any = null; - let timer__location: any = null; - let timer__session: any = null; + let timer__event: any = $state(null); + let timer__device: any = $state(null); + let timer__location: any = $state(null); + let timer__session: any = $state(null); let is_syncing = false; let show_monitor = $state(false); @@ -44,12 +46,14 @@ loop_info.session = dev.check_event_session_loop_period || 10000; timer__event = setInterval(() => run_sync_cycle(), loop_info.event); - timer__device = setInterval(() => { /* TODO: Heartbeat */ }, loop_info.device); - timer__location = setInterval(() => { /* TODO: Refresh Loc */ }, loop_info.location); + timer__device = setInterval(() => run_device_heartbeat(), loop_info.device); + timer__location = setInterval(() => refresh_location_config(), loop_info.location); timer__session = setInterval(() => run_sync_cycle(), loop_info.session); // Immediate first run run_sync_cycle(); + run_device_heartbeat(); + refresh_location_config(); }); onDestroy(() => { @@ -128,8 +132,15 @@ * Gathers OS info and updates the device record in the cloud. */ async function run_device_heartbeat() { - const device_id = $ae_loc.native_device?.event_device_id || $ae_loc.native_device?.id; - if (!device_id) return; + const dev = $ae_loc.native_device; + const device_id = dev?.event_device_id_random || dev?.id_random || dev?.event_device_id || dev?.id; + + if (!device_id) { + if (log_lvl) console.warn('Sync: Heartbeat skipped, no device_id found in $ae_loc.native_device.'); + return; + } + + if (log_lvl > 1) console.log(`Sync: Running heartbeat for device: ${device_id}`); try { const info = await native.get_device_info(); @@ -151,11 +162,12 @@ await events_func.update_ae_obj__event_device({ api_cfg: $ae_api, - event_device_id: device_id, + event_device_id: String(device_id), data_kv: update_payload, log_lvl: 0 }); + last_heartbeat = new Date().toLocaleTimeString(); if (log_lvl > 1) console.log('Sync: Device heartbeat SUCCESS.'); } catch (err) { console.error('Sync: Device heartbeat FAILED:', err); @@ -198,6 +210,11 @@ Prefix Len: {$ae_loc.native_device?.hash_prefix_length || 2} chars + + Heartbeat: + + {last_heartbeat || 'Pending...'} +
diff --git a/src/routes/events/[event_id]/+page.svelte b/src/routes/events/[event_id]/+page.svelte index f8b9a651..3ef8f408 100644 --- a/src/routes/events/[event_id]/+page.svelte +++ b/src/routes/events/[event_id]/+page.svelte @@ -422,7 +422,7 @@ max-w-max --> - + {#if !$lq__event_obj}