Files
NetAlertX/front/js/app-init.js
Jokob @NetAlertX 63cef590d6 Refactor network API calls to use centralized authentication context and improve cache handling
- Removed redundant getApiToken function and replaced its usage with getAuthContext in network-api.js, network-events.js, and network-init.js.
- Updated cache handling in network-events.js and network-init.js to use CACHE_KEYS constants for better maintainability.
- Introduced cache.js for centralized cache management functions and constants, including cache initialization and retrieval.
- Added app-init.js for application lifecycle management, including cache orchestration and initialization checks.
- Created app_config.php to securely fetch API token and GraphQL port from configuration.
- Improved error handling and logging throughout the codebase for better debugging and maintenance.
2026-02-26 04:21:29 +00:00

282 lines
9.8 KiB
JavaScript

/* -----------------------------------------------------------------------------
* NetAlertX
* Open Source Network Guard / WIFI & LAN intrusion detector
*
* app-init.js - Front module. Application lifecycle: initialization,
* cache orchestration, and startup sequencing.
* Loaded AFTER common.js — depends on showSpinner(), isEmpty(),
* mergeUniqueArrays(), getSetting(), getString(), getCache(),
* setCache(), and all cache* functions from cache.js.
*-------------------------------------------------------------------------------
# jokob@duck.com GNU GPLv3
----------------------------------------------------------------------------- */
// -----------------------------------------------------------------------------
// initialize
// -----------------------------------------------------------------------------
var completedCalls = []
var completedCalls_final = ['cacheApiConfig', 'cacheSettings', 'cacheStrings', 'cacheDevices'];
var lang_completedCalls = 0;
// -----------------------------------------------------------------------------
// Clearing all the caches
function clearCache() {
showSpinner();
sessionStorage.clear();
localStorage.clear();
// Wait for spinner to show and cache to clear, then reload
setTimeout(() => {
console.warn("clearCache called");
window.location.reload();
}, 100);
}
// ===================================================================
// DEPRECATED: checkSettingChanges() - Replaced by SSE-based manager
// Settings changes are now handled via SSE events
// Kept for backward compatibility, will be removed in future version
// ===================================================================
function checkSettingChanges() {
// SSE manager handles settings_changed events now
if (typeof netAlertXStateManager !== 'undefined' && netAlertXStateManager.initialized) {
return; // SSE handles this now
}
// Fallback for backward compatibility
$.get('php/server/query_json.php', { file: 'app_state.json', nocache: Date.now() }, function(appState) {
const importedMilliseconds = parseInt(appState["settingsImported"] * 1000);
const lastReloaded = parseInt(getCache(CACHE_KEYS.INIT_TIMESTAMP));
if (importedMilliseconds > lastReloaded) {
console.log("Cache needs to be refreshed because of setting changes");
setTimeout(() => {
clearCache();
}, 500);
}
});
}
// ===================================================================
// Display spinner and reload page if not yet initialized
async function handleFirstLoad(callback) {
if (!isAppInitialized()) {
await new Promise(resolve => setTimeout(resolve, 1000));
callback();
}
}
// ===================================================================
// Execute callback once the app is initialized and GraphQL server is running
async function callAfterAppInitialized(callback) {
if (!isAppInitialized() || !(await isGraphQLServerRunning())) {
setTimeout(() => {
callAfterAppInitialized(callback);
}, 500);
} else {
callback();
}
}
// ===================================================================
// Polling function to repeatedly check if the server is running
async function waitForGraphQLServer() {
const pollInterval = 2000; // 2 seconds between each check
let serverRunning = false;
while (!serverRunning) {
serverRunning = await isGraphQLServerRunning();
if (!serverRunning) {
console.log("GraphQL server not running, retrying in 2 seconds...");
await new Promise(resolve => setTimeout(resolve, pollInterval));
}
}
console.log("GraphQL server is now running.");
}
// -----------------------------------------------------------------------------
// Returns 1 if running, 0 otherwise
async function isGraphQLServerRunning() {
try {
const response = await $.get('php/server/query_json.php', { file: 'app_state.json', nocache: Date.now()});
console.log("graphQLServerStarted: " + response["graphQLServerStarted"]);
setCache(CACHE_KEYS.GRAPHQL_STARTED, response["graphQLServerStarted"]);
return response["graphQLServerStarted"];
} catch (error) {
console.error("Failed to check GraphQL server status:", error);
return false;
}
}
// Throttle isAppInitialized logging so the console isn't spammed on every poll.
let _isAppInit_lastLogTime = 0;
function _isAppInitLog(msg) {
const now = Date.now();
if (now - _isAppInit_lastLogTime > 5000) { // log at most once per 5s
console.log(msg);
_isAppInit_lastLogTime = now;
}
}
// -----------------------------------------------------------------------------
// Check if the code has been executed before by checking localStorage
function isAppInitialized() {
lang_shouldBeCompletedCalls = getLangCode() == 'en_us' ? 1 : 2;
// check if each ajax call completed succesfully
for (const call_name of completedCalls_final) {
if (getCache(CACHE_KEYS.initFlag(call_name)) != "true") {
_isAppInitLog(`[isAppInitialized] waiting on ${call_name} (value: ${getCache(CACHE_KEYS.initFlag(call_name))})`);
return false;
}
}
// check if all required languages chached
if(parseInt(getCache(CACHE_KEYS.STRINGS_COUNT)) != lang_shouldBeCompletedCalls)
{
_isAppInitLog(`[isAppInitialized] waiting on cacheStrings: ${getCache(CACHE_KEYS.STRINGS_COUNT)} of ${lang_shouldBeCompletedCalls}`);
return false;
}
return true;
}
// Retry a single async init step up to maxAttempts times with a delay.
async function retryStep(name, fn, maxAttempts = 3, delayMs = 1500) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
await fn();
return; // success
} catch (err) {
console.warn(`[executeOnce] ${name} failed (attempt ${attempt}/${maxAttempts}):`, err);
if (attempt < maxAttempts) {
await new Promise(r => setTimeout(r, delayMs));
} else {
console.error(`[executeOnce] ${name} permanently failed after ${maxAttempts} attempts.`);
}
}
}
}
// -----------------------------------------------------------------------------
// Main execution logic
let _executeOnceRunning = false;
async function executeOnce() {
if (_executeOnceRunning) {
console.log('[executeOnce] Already running — skipping duplicate call.');
return;
}
_executeOnceRunning = true;
showSpinner();
// Auto-bust stale cache if code version has changed since last init.
// Clears localStorage in-place so the subsequent init runs fresh without
// requiring a page reload.
if (getCache(CACHE_KEYS.CACHE_VERSION) !== NAX_CACHE_VERSION) {
console.log(`[executeOnce] Cache version mismatch (stored: "${getCache(CACHE_KEYS.CACHE_VERSION)}", expected: "${NAX_CACHE_VERSION}"). Clearing cache.`);
localStorage.clear();
sessionStorage.clear();
}
if (!isAppInitialized()) {
try {
await waitForGraphQLServer(); // Wait for the server to start
await retryStep('cacheApiConfig', cacheApiConfig); // Bootstrap: API_TOKEN + GRAPHQL_PORT from app.conf
await retryStep('cacheDevices', cacheDevices);
await retryStep('cacheSettings', cacheSettings);
await retryStep('cacheStrings', cacheStrings);
console.log("All AJAX callbacks have completed");
onAllCallsComplete();
} finally {
_executeOnceRunning = false;
}
} else {
_executeOnceRunning = false;
}
}
// -----------------------------------------------------------------------------
// Function to handle successful completion of an AJAX call
const handleSuccess = (callName) => {
console.log(`AJAX call successful: ${callName}`);
if(callName.includes("cacheStrings"))
{
completed_tmp = getCache(CACHE_KEYS.STRINGS_COUNT);
completed_tmp == "" ? completed_tmp = 0 : completed_tmp = completed_tmp;
completed_tmp++;
setCache(CACHE_KEYS.STRINGS_COUNT, completed_tmp);
}
setCache(CACHE_KEYS.initFlag(callName), true)
};
// -----------------------------------------------------------------------------
// Function to handle failure of an AJAX call
const handleFailure = (callName, callback) => {
msg = `AJAX call ${callName} failed`
console.error(msg);
if (typeof callback === 'function') {
callback(new Error(msg));
}
};
// -----------------------------------------------------------------------------
// Function to execute when all AJAX calls have completed
const onAllCallsComplete = () => {
completedCalls = mergeUniqueArrays(getCache(CACHE_KEYS.COMPLETED_CALLS).split(','), completedCalls);
setCache(CACHE_KEYS.COMPLETED_CALLS, completedCalls);
// Check if all necessary strings are initialized
if (areAllStringsInitialized()) {
const millisecondsNow = Date.now();
setCache(CACHE_KEYS.INIT_TIMESTAMP, millisecondsNow);
setCache(CACHE_KEYS.CACHE_VERSION, NAX_CACHE_VERSION);
console.log('✔ Cache initialized');
} else {
// If not all strings are initialized, retry initialization
console.log('❌ Not all strings are initialized. Retrying...');
executeOnce();
return;
}
// Call any other initialization functions here if needed
};
// Function to check if all necessary strings are initialized
const areAllStringsInitialized = () => {
// Implement logic to check if all necessary strings are initialized
// Return true if all strings are initialized, false otherwise
return getString('UI_LANG_name') != ""
};
// Call the function to execute the code
executeOnce();
// Set timer for regular UI refresh if enabled
setTimeout(() => {
// page refresh if configured
const refreshTime = getSetting("UI_REFRESH");
if (refreshTime && refreshTime !== "0" && refreshTime !== "") {
console.log("Refreshing page becasue UI_REFRESH setting enabled.");
newTimerRefreshData(clearCache, parseInt(refreshTime)*1000);
}
// Check if page needs to refresh due to setting changes
checkSettingChanges()
}, 10000);
console.log("init app-init.js");