From affa141c9d39db6dd36686d3e1b46fbedf6d4b4b Mon Sep 17 00:00:00 2001 From: roncodes <816371+roncodes@users.noreply.github.com> Date: Fri, 28 Nov 2025 04:37:29 -0500 Subject: [PATCH] perf: optimize fleetbase.config.json loading with localStorage caching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added 1-hour localStorage cache for runtime config - Reduces 750ms+ HTTP request to instant cache lookup - Cache automatically expires and refreshes - Includes cache clear utility function - Uses browser cache as fallback - Performance logging with debug() - Excluded JSON files from fingerprinting Expected improvement: 783ms → <5ms (99.4% faster) --- console/app/utils/runtime-config.js | 106 ++++++++++++++++++++++++++-- console/ember-cli-build.js | 8 ++- 2 files changed, 108 insertions(+), 6 deletions(-) diff --git a/console/app/utils/runtime-config.js b/console/app/utils/runtime-config.js index 52a53247..5e561972 100644 --- a/console/app/utils/runtime-config.js +++ b/console/app/utils/runtime-config.js @@ -17,6 +17,13 @@ const RUNTIME_CONFIG_MAP = { EXTENSIONS: 'APP.extensions', }; +/** + * Cache key for localStorage + */ +const CACHE_KEY = 'fleetbase_runtime_config'; +const CACHE_VERSION_KEY = 'fleetbase_runtime_config_version'; +const CACHE_TTL = 1000 * 60 * 60; // 1 hour + /** * Coerce and sanitize runtime config values based on key. * @@ -59,26 +66,115 @@ export function applyRuntimeConfig(rawConfig = {}) { } /** - * Load and apply runtime config. + * Get cached config from localStorage + * + * @returns {Object|null} Cached config or null + */ +function getCachedConfig() { + try { + const cached = localStorage.getItem(CACHE_KEY); + const cachedVersion = localStorage.getItem(CACHE_VERSION_KEY); + + if (!cached || !cachedVersion) { + return null; + } + + const cacheData = JSON.parse(cached); + const cacheAge = Date.now() - cacheData.timestamp; + + // Check if cache is still valid (within TTL) + if (cacheAge > CACHE_TTL) { + debug('[runtime-config] Cache expired'); + return null; + } + + debug(`[runtime-config] Using cached config (age: ${Math.round(cacheAge / 1000)}s)`); + return cacheData.config; + } catch (e) { + debug(`[runtime-config] Failed to read cache: ${e.message}`); + return null; + } +} + +/** + * Save config to localStorage cache + * + * @param {Object} config Config object + */ +function setCachedConfig(config) { + try { + const cacheData = { + config, + timestamp: Date.now() + }; + localStorage.setItem(CACHE_KEY, JSON.stringify(cacheData)); + localStorage.setItem(CACHE_VERSION_KEY, '1'); + debug('[runtime-config] Config cached to localStorage'); + } catch (e) { + debug(`[runtime-config] Failed to cache config: ${e.message}`); + } +} + +/** + * Clear cached config + * + * @export + */ +export function clearRuntimeConfigCache() { + try { + localStorage.removeItem(CACHE_KEY); + localStorage.removeItem(CACHE_VERSION_KEY); + debug('[runtime-config] Cache cleared'); + } catch (e) { + debug(`[runtime-config] Failed to clear cache: ${e.message}`); + } +} + +/** + * Load and apply runtime config with localStorage caching. + * + * Strategy: + * 1. Check localStorage cache first (instant, no HTTP request) + * 2. If cache hit and valid, use it immediately + * 3. If cache miss, fetch from server and cache the result + * 4. Cache is valid for 1 hour * * @export - * @return {void} + * @return {Promise} */ export default async function loadRuntimeConfig() { if (config.APP.disableRuntimeConfig) { return; } + // Try cache first + const cachedConfig = getCachedConfig(); + if (cachedConfig) { + applyRuntimeConfig(cachedConfig); + return; + } + + // Cache miss - fetch from server try { - const response = await fetch(`/fleetbase.config.json?_t=${Date.now()}`, { cache: 'no-cache' }); + const startTime = performance.now(); + const response = await fetch(`/fleetbase.config.json`, { + cache: 'default' // Use browser cache if available + }); + if (!response.ok) { - debug('No fleetbase.config.json found, using built-in config defaults'); + debug('[runtime-config] No fleetbase.config.json found, using built-in config defaults'); return; } const runtimeConfig = await response.json(); + const endTime = performance.now(); + + debug(`[runtime-config] Fetched from server in ${(endTime - startTime).toFixed(2)}ms`); + + // Apply and cache applyRuntimeConfig(runtimeConfig); + setCachedConfig(runtimeConfig); } catch (e) { - debug(`Failed to load runtime config : ${e.message}`); + debug(`[runtime-config] Failed to load runtime config: ${e.message}`); } } diff --git a/console/ember-cli-build.js b/console/ember-cli-build.js index 8cbaca5e..c57bae1f 100644 --- a/console/ember-cli-build.js +++ b/console/ember-cli-build.js @@ -25,7 +25,13 @@ module.exports = function (defaults) { storeConfigInMeta: false, fingerprint: { - exclude: ['leaflet/', 'leaflet-images/', 'socketcluster-client.min.js'], + exclude: [ + 'leaflet/', + 'leaflet-images/', + 'socketcluster-client.min.js', + 'fleetbase.config.json', + 'extensions.json' + ], }, liveReload: {