Two Svelte-side bugs causing account_name to always show 'Account Name Not Set':
1. ae_core__site.ts: background site_domain refresh only pushed cfg_json back
into $ae_loc after a stale cache hit. Now also pushes account_name and
account_code when the store holds a placeholder value.
2. +layout.ts: duplicate ae_loc_init['account_name'] assignment at line ~475
was overwriting the correct one at line ~385 with a different fallback string
('Account Name Not Set' vs 'Ghost Account'). Removed the duplicate.
Also includes user-intentional changes during testing:
- events/+page.svelte: typo fix ('You access' -> 'Your access'); Pres Mgmt /
Launcher / Badges / Leads buttons now gated on trusted_access && edit_mode
- events/+page.ts: event list limit 25 -> 7
- events/[event_id]/+page.svelte: user edit
500 lines
19 KiB
TypeScript
500 lines
19 KiB
TypeScript
/** @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;
|
|
}
|