/** @type {import('./$types').LayoutLoad} */ // console.log(`ae_root +layout.ts: start`); // import { error } from '@sveltejs/kit'; 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, PUBLIC_AE_API_BAK_SERVER, PUBLIC_AE_API_PORT, PUBLIC_AE_API_PATH, PUBLIC_AE_API_SECRET_KEY, PUBLIC_AE_API_CRUD_SUPER_KEY, PUBLIC_AE_BOOTSTRAP_KEY // PUBLIC_AE_NO_ACCOUNT_ID, // PUBLIC_AE_NO_ACCOUNT_ID_TOKEN } from '$env/static/public'; const api_base_url = `${PUBLIC_AE_API_PROTOCOL}://${PUBLIC_AE_API_SERVER}:${PUBLIC_AE_API_PORT}${PUBLIC_AE_API_PATH}`; const api_base_url_bak = `${PUBLIC_AE_API_PROTOCOL}://${PUBLIC_AE_API_BAK_SERVER}:${PUBLIC_AE_API_PORT}${PUBLIC_AE_API_PATH}`; const api_secret_key = PUBLIC_AE_API_SECRET_KEY; const api_crud_super_key = PUBLIC_AE_API_CRUD_SUPER_KEY; const ae_account_id: null | string = null; // const ae_no_account_id = PUBLIC_AE_NO_ACCOUNT_ID; // const ae_no_account_id_token = PUBLIC_AE_NO_ACCOUNT_ID_TOKEN; const ae_api_init: key_val = { ver: '2026-05-13_04', base_url: api_base_url, base_url_bak: api_base_url_bak, api_secret_key: api_secret_key, api_secret_key_bak: api_secret_key, api_crud_super_key: api_crud_super_key, headers: {}, account_id: ae_account_id }; const ae_api_headers: key_val = { 'Content-Type': 'application/json', 'x-aether-api-key': api_secret_key, 'x-ae-ignore-extra-fields': 'true' }; ae_api_init['headers'] = ae_api_headers; export async function load({ fetch, params, parent, route, url }) { const log_lvl: number = 1; let account_id: any; const ae_acct: key_val = { api: { ...ae_api_init, headers: { ...ae_api_headers } // Local clone }, ds: {}, loc: { account_id: '', site_id: '', site_domain_id: '', iframe: false }, sess: {}, slct: {} }; // Initialize API fetch with SvelteKit fetch ae_api_init['fetch'] = fetch; // IMMEDIATE AUTH SYNC: Read JWT from localStorage to avoid race conditions if (typeof localStorage !== 'undefined') { try { const ae_loc_raw = localStorage.getItem('ae_loc'); if (ae_loc_raw) { const ae_loc_json = JSON.parse(ae_loc_raw); if (ae_loc_json.jwt) { ae_api_init['jwt'] = ae_loc_json.jwt; } } } catch (e) { // Silently fail on storage read } } const ae_loc_init: key_val = {}; const ds_code_li: null | key_val = {}; const data_struct: key_val = { account_id: null, ae_acct: {}, ae_loc: {}, ae_api: ae_api_init, ae_ds: {}, ae_hub: {}, ae_m_sponsorships: {}, ae_m_events: {}, ae_m_events_speakers: {}, ae_m_idaa: {}, ae_slct: {}, iframe: false, ae_root_layout_ts: true, params: params, route: route, url: url, sections: [ { slug: 'new', title: 'New Test' }, { slug: 'manage', title: 'Manage Test' }, { slug: 'test', title: 'Test Test' } ], submenu: {} }; const fqdn = url.host; // Optional access key provided via URL `?key=...` to support restricted site domains const access_key = url.searchParams.get('key') || undefined; let result: any = null; let api_error = false; let is_native = false; // 1. FAST PATH: Check Dexie for cached site domain first // This allows the load function to return nearly instantly if the user has visited before. if (typeof window !== 'undefined' && (window as any).indexedDB) { try { // Use the db_core instance directly for a quick lookup const { db_core } = await import('$lib/ae_core/db_core'); const cached = await db_core.site_domain .where('fqdn') .equals(fqdn) .first(); if (cached) { if (log_lvl) console.log( 'ROOT LOAD: Found cached site domain. Evaluating cache vs URL key.' ); // If the URL contains an access_key, we must ensure the cached // entry is valid for that key. If the cache has no keys or the // keys don't match, force a live lookup to revalidate. if (access_key) { const cachedSiteKey = (cached as any).access_key || (cached as any).site_access_key || (cached as any).site_domain_access_key || ''; const cachedDomainKey = (cached as any).site_domain_access_key || (cached as any).site_access_key || (cached as any).access_key || ''; if (!cachedSiteKey && !cachedDomainKey) { if (log_lvl) console.log( 'BOOTSTRAP: Cache has no access keys; forcing live lookup because URL contains a key.' ); // leave `result` null so slow-path lookup runs } else if ( access_key === cachedSiteKey || access_key === cachedDomainKey ) { if (log_lvl) console.log('BOOTSTRAP: URL key matches cached access key; using cached object.'); result = cached as any; } else { if (log_lvl) console.log('BOOTSTRAP: URL key does not match cached keys; forcing live lookup to revalidate.'); // leave `result` null so slow-path lookup runs } } else { // No access_key in URL — safe to use cached value result = cached as any; } } } catch (e) { if (log_lvl) console.warn('ROOT LOAD: Failed to read from Dexie cache.', e); } } // Detect Aether Native Bridge (Electron) if (typeof window !== 'undefined' && (window as any).aetherNative) { is_native = true; if (log_lvl) console.log( 'ROOT LOAD: Detected Aether Native Bridge. Requesting device config...' ); try { const native_device_config = await ( window as any ).aetherNative.get_device_config(); if (native_device_config) { if (log_lvl) console.log( 'ROOT LOAD: Native device config received:', native_device_config ); // Map native device config to the expected result structure // Use native as source of truth if available result = { ...native_device_config, // Ensure naming consistency account_id: native_device_config.account_id || native_device_config.account_id_random, site_id: native_device_config.site_id || native_device_config.site_id_random, site_domain_id: native_device_config.site_domain_id || native_device_config.site_domain_id_random }; // Inject native device metadata into the location state with SAFE MERGE if (native_device_config.native_device) { const incoming_dev = native_device_config.native_device; // String-Only ID Vision: Ensure semantic fields use the random string ID if (incoming_dev.event_device_id_random) incoming_dev.event_device_id = incoming_dev.event_device_id_random; if (incoming_dev.event_id_random) incoming_dev.event_id = incoming_dev.event_id_random; if (incoming_dev.id_random) incoming_dev.id = incoming_dev.id_random; // 1. Recover existing user overrides from localStorage let existing_dev = {}; try { const raw = localStorage.getItem('ae_loc'); if (raw) existing_dev = JSON.parse(raw).native_device || {}; } catch (e) {} // 2. Merge: Priority to EXISTING overrides for specific timers ae_loc_init['native_device'] = { ...incoming_dev, // Persist these specific user-controlled fields check_event_loop_period: (existing_dev as any).check_event_loop_period || incoming_dev.check_event_loop_period, check_event_device_loop_period: (existing_dev as any) .check_event_device_loop_period || incoming_dev.check_event_device_loop_period, check_event_location_loop_period: (existing_dev as any) .check_event_location_loop_period || incoming_dev.check_event_location_loop_period, check_event_session_loop_period: (existing_dev as any) .check_event_session_loop_period || incoming_dev.check_event_session_loop_period, // Use API value if present; default to 2 (never preserve from localStorage — stale values cause orphaned cache dirs) hash_prefix_length: incoming_dev.hash_prefix_length || 2 }; // Map specific operational paths ae_loc_init['local_file_cache_path'] = incoming_dev.local_file_cache_path; ae_loc_init['host_file_temp_path'] = incoming_dev.host_file_temp_path; ae_loc_init['recording_path'] = incoming_dev.recording_path; } // IMPORTANT: Update API settings with the native-authorized key if present if (native_device_config.aether_api_key) { ae_api_init['api_secret_key'] = native_device_config.aether_api_key; ae_api_headers['x-aether-api-key'] = native_device_config.aether_api_key; } } } catch (err) { console.error( 'ROOT LOAD: Failed to fetch native device config.', err ); } } // 2. SLOW PATH: Wait for API site lookup only if we have no result yet if (!result) { try { if (log_lvl) console.log( `ROOT LOAD: No cache. Starting site lookup V3 for ${fqdn}...` ); // Use dedicated Bootstrap key — limited permissions, no account_id required. // Key is injected at build time from PUBLIC_AE_BOOTSTRAP_KEY in .env. const bootstrap_api_cfg = { ...ae_api_init, api_secret_key: PUBLIC_AE_BOOTSTRAP_KEY, headers: { ...ae_api_init.headers, 'x-aether-api-key': PUBLIC_AE_BOOTSTRAP_KEY, 'x-no-account-id': 'bypass' // Force explicit bypass for bootstrap } }; result = await lookup_site_domain({ api_cfg: bootstrap_api_cfg, fqdn, view: 'base', log_lvl, access_key }); if (log_lvl) console.log( `ROOT LOAD: Site lookup result for ${fqdn}:`, result ); } catch (err) { console.error( `ROOT LOAD: Site lookup critical failure for ${fqdn}.`, err ); api_error = true; } } else { // We have a result (cache or native), fire off the refresh in the background to update Dexie if (log_lvl) console.log( 'ROOT LOAD: Result already obtained. Background refresh triggered.' ); lookup_site_domain({ api_cfg: ae_api_init, fqdn, view: 'base', log_lvl: 0, access_key }); } // Defensive check: if result is false (common from API helper) or null, use emergency ghost 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: api_error ? 'API Connection Failed' : 'Domain Not Found or Missing Access Key', site_id_random: 'ghost', site_domain_id_random: 'ghost', enable: '1', cfg_json: {}, style_href: '', header_image_path: '' }; } const json_data = result; // ... rest of the mapping logic ... account_id = json_data.account_id || 'ghost'; data_struct.account_id = account_id; ae_acct.account_id = account_id; if (log_lvl) console.log(`ROOT LOAD: Using account_id: ${account_id}`); // Update the local clones ae_acct.api.account_id = account_id; ae_acct.api.headers['x-account-id'] = account_id; ae_loc_init['is_native'] = is_native; ae_loc_init['account_id'] = account_id; 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 || 'ghost'; ae_loc_init['site_domain_id'] = json_data.site_domain_id || json_data.site_domain_id || '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 || ''; ae_loc_init['site_google_tracking_id'] = json_data.google_tracking_id || ''; ae_loc_init['site_access_code_kv'] = json_data.access_code_kv_json || {}; ae_loc_init['site_cfg_json'] = json_data.cfg_json || {}; // Apply site-level theme defaults only when there is no persisted ae_loc // in localStorage (first-run). URL params and manual selection still override. try { // site_cfg_json may be an object or a JSON string depending on the API/backend. let scfg_raw = ae_loc_init['site_cfg_json']; let scfg: any = scfg_raw; if (typeof scfg_raw === 'string') { try { scfg = JSON.parse(scfg_raw); } catch (e) { scfg = {}; } } // Inspect persisted `ae_loc` in localStorage to determine whether the // user has already selected a theme. Some persisted-store implementations // write a default `ae_loc` object immediately, so the mere presence of // the key is not a reliable indicator of a user choice. let persistedRaw: string | null = null; try { if (typeof localStorage !== 'undefined') persistedRaw = localStorage.getItem('ae_loc'); } catch (e) { persistedRaw = null; } let persistedObj: any = null; let persistedHasUserTheme = false; if (persistedRaw) { try { persistedObj = JSON.parse(persistedRaw); } catch (e) { persistedObj = null; } if (persistedObj) { // Explicit flag wins. if (persistedObj.user_theme_selected === true) { persistedHasUserTheme = true; } else if (persistedObj.theme_name && persistedObj.theme_name !== 'AE_Firefly') { // If the persisted theme differs from the global default, assume // it was a prior user choice (backcompat for older installs). persistedHasUserTheme = true; } } } if (scfg && typeof scfg === 'object' && !persistedHasUserTheme) { // Accept common keys for theme name so site authors have flexibility. const themeName = scfg.theme_name ?? scfg.theme ?? scfg.themeName ?? scfg.site_theme; if (themeName) ae_loc_init['theme_name'] = themeName; if (scfg.theme_mode && (scfg.theme_mode === 'light' || scfg.theme_mode === 'dark')) ae_loc_init['theme_mode'] = scfg.theme_mode; } } catch (e) { // Defensive: if parsing fails, don't block boot. } ae_loc_init['site_access_key'] = json_data.access_key || ''; ae_loc_init['site_domain_access_key'] = json_data.site_domain_access_key || ''; ae_loc_init['base_url'] = url.origin; ae_loc_init['hostname'] = url.hostname; // Access key gate — re-enabled 2026-04-28. // Only write allow_access when a key is actively present in the URL. // If no key on refresh/navigation, do NOT set allow_access — the persisted // value from the original keyed visit survives the ae_loc spread in +layout.svelte. // Setting it unconditionally (even to `true`) overwrites the persisted key string // on every refresh, which was the root cause of the 2026-04-01 lockout bug. ae_loc_init['key_checked'] = true; if (access_key) { ae_loc_init['allow_access'] = access_key; } // if (!account_id) { // error(500, { // message: 'The account ID was not found! Check the API.' // }); // } // account_name is already set above (line ~385). This was a duplicate that // overwrote it with a different fallback string — removed. // ae_acct['api'] = ae_api_init; // DO NOT USE: This overwrites our isolated clone from line 65 ae_acct['loc'] = ae_loc_init; ae_acct['ds'] = ds_code_li; ae_acct['slct'] = { account_id: account_id, site_domain_id: ae_loc_init.site_domain_id, site_id: ae_loc_init.site_id // event_id: ae_loc_init.site_cfg_json?.slct__event_id, // event_badge_template_id: ae_loc_init.site_cfg_json?.slct__event_badge_template_id, // sponsorship_cfg_id: ae_loc_init.site_cfg_json?.slct__sponsorship_cfg_id }; data_struct[account_id] = ae_acct; if (log_lvl) console.log( 'ROOT LOAD: Final data_struct structure ready.', Object.keys(data_struct) ); return data_struct; }