[core] Ensure everything is initialized in the correct order.

Make sure api is initialized before making any requests.
This commit is contained in:
Ferdinand Thiessen 2021-11-27 01:18:58 +01:00
parent 368ca23c56
commit 7a705d5f9a
4 changed files with 107 additions and 99 deletions

View File

@ -31,7 +31,7 @@ module.exports = configure(function (/* ctx */) {
// app boot file (/src/boot) // app boot file (/src/boot)
// --> boot files are part of "main.js" // --> boot files are part of "main.js"
// https://quasar.dev/quasar-cli/boot-files // https://quasar.dev/quasar-cli/boot-files
boot: ['axios', 'store', 'plugins', 'login'], boot: ['axios', 'store', 'plugins', 'login', 'init'],
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css // https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css
css: ['app.scss'], css: ['app.scss'],

81
src/boot/init.ts Normal file
View File

@ -0,0 +1,81 @@
/**
* This boot file initalizes the store from persistent storage and load all plugins
*/
import { PersistentStorage, api, saveToken, useMainStore, isAxiosError } from '@flaschengeist/api';
import { Notify, Platform } from 'quasar';
import { loadPlugins } from './plugins';
import { boot } from 'quasar/wrappers';
import routes from 'src/router/routes';
async function loadBaseUrl() {
try {
const url = await PersistentStorage.get<string>('baseURL');
if (url !== null) api.defaults.baseURL = url;
} catch (e) {
console.warn('Could not load BaseURL', e);
}
}
class BackendError extends Error {}
/**
* Loading backend information
* @returns Backend object or null
*/
async function getBackend() {
const { data } = await api.get<FG.Backend>('/');
if (!data || typeof data !== 'object' || !('plugins' in data))
throw new BackendError('Invalid backend response received');
return data;
}
/**
* Boot file for loading baseURL + Session from PersistentStorage + loading and initializing all plugins
*/
export default boot(async ({ app, router }) => {
const store = useMainStore();
// FIRST(!) get the base URL
await loadBaseUrl();
// Init the store, load current session and user, if available
try {
await store.init();
} finally {
// Any changes on the session is written back to the persistent store
store.$subscribe((mutation, state) => {
saveToken(state.session?.token);
});
}
// Load all plugins
try {
// Fetch backend data
const backend = await getBackend();
// Load enabled plugins
const flaschengeist = await loadPlugins(backend, routes);
// Add loaded routes to router
flaschengeist.routes.forEach((route) => router.addRoute(route));
// save plugins in VM-variable
app.provide('flaschengeist', flaschengeist);
} catch (error) {
// Handle errors from loading the backend information
if (error instanceof BackendError || isAxiosError(error)) {
router.isReady().finally(() => {
if (Platform.is.capacitor) void router.push({ name: 'setup_backend' });
else void router.push({ name: 'offline', params: { refresh: 1 } });
});
} else if (typeof error === 'string') {
// Handle plugin not found errors
void router.push({ name: 'error' });
Notify.create({
type: 'negative',
message: `Fehler beim Laden: Bitte wende dich an den Admin (${error})!`,
timeout: 10000,
progress: true,
});
} else {
console.error('Unknown error in init.ts:', error);
}
}
});

View File

@ -1,9 +1,5 @@
import { FG_Plugin } from '@flaschengeist/types'; import { FG_Plugin } from '@flaschengeist/types';
import { api, PersistentStorage } from '@flaschengeist/api';
import { RouteRecordRaw } from 'vue-router'; import { RouteRecordRaw } from 'vue-router';
import { Notify, Platform } from 'quasar';
import { boot } from 'quasar/wrappers';
import routes from 'src/router/routes';
/**************************************************** /****************************************************
******** Internal area for some magic ************** ******** Internal area for some magic **************
@ -25,21 +21,6 @@ const PLUGINS = <Array<Promise<ImportPlgn>>>[
/*INSERT_PLUGIN_LIST*/ /*INSERT_PLUGIN_LIST*/
]; ];
interface BackendPlugin {
permissions: string[];
version: string;
}
interface BackendPlugins {
[key: string]: BackendPlugin;
}
interface Backend {
plugins: BackendPlugins;
version: string;
}
export { Backend };
// Handle Notifications // Handle Notifications
export const translateNotification = (note: FG.Notification): FG_Plugin.Notification => note; export const translateNotification = (note: FG.Notification): FG_Plugin.Notification => note;
@ -211,7 +192,7 @@ function combineShortcuts(target: FG_Plugin.Shortcut[], source: FG_Plugin.MenuRo
function loadPlugin( function loadPlugin(
loadedPlugins: FG_Plugin.Flaschengeist, loadedPlugins: FG_Plugin.Flaschengeist,
plugin: FG_Plugin.Plugin, plugin: FG_Plugin.Plugin,
backend: Backend backend: FG.Backend
) { ) {
// Check if already loaded // Check if already loaded
if (loadedPlugins.plugins.findIndex((p) => p.id === plugin.id) !== -1) return true; if (loadedPlugins.plugins.findIndex((p) => p.id === plugin.id) !== -1) return true;
@ -263,43 +244,9 @@ function loadPlugin(
return true; return true;
} }
async function loadBaseUrl() { export async function loadPlugins(backend: FG.Backend, baseRoutes: RouteRecordRaw[]) {
const url = await PersistentStorage.get<string>('baseURL');
if (url !== null) api.defaults.baseURL = url;
return url;
}
/**
* Loading backend information
* @returns Backend object or null
*/
async function getBackend() {
try {
const { data } = await api.get<Backend>('/');
if (!data || typeof data !== 'object' || !('plugins' in data))
throw Error('Invalid backend response received');
return data;
} catch (e) {
console.error('Loading backend', e);
return null;
}
}
/**
* Boot file, load all required plugins, check for dependencies
*/
export default boot(async ({ router, app }) => {
await loadBaseUrl();
const backend = await getBackend();
if (backend === null) {
router.isReady().finally(() => {
if (Platform.is.capacitor) void router.push({ name: 'setup_backend' });
else void router.push({ name: 'offline', params: { refresh: 1 } });
});
return;
}
const loadedPlugins: FG_Plugin.Flaschengeist = { const loadedPlugins: FG_Plugin.Flaschengeist = {
routes, routes: baseRoutes,
plugins: [], plugins: [],
menuLinks: [], menuLinks: [],
shortcuts: [], shortcuts: [],
@ -307,50 +254,35 @@ export default boot(async ({ router, app }) => {
widgets: [], widgets: [],
}; };
try { // Wait for all plugins to be loaded
// Wait for all plugins to be loaded const results = await Promise.allSettled(PLUGINS);
const results = await Promise.allSettled(PLUGINS);
// Check if loaded successfully // Check if loaded successfully
results.forEach((result) => { results.forEach((result) => {
if (result.status === 'rejected') { if (result.status === 'rejected') {
throw <string>result.reason; throw <string>result.reason;
} else { } else {
if ( if (
!( !(
validatePlugin(result.value.default) && validatePlugin(result.value.default) &&
loadPlugin(loadedPlugins, result.value.default, backend) loadPlugin(loadedPlugins, result.value.default, backend)
)
) )
throw result.value.default.id; )
} throw result.value.default.id;
}); }
} catch (reason) { });
const id = <string>reason;
void router.push({ name: 'error' });
Notify.create({
type: 'negative',
message: `Fehler beim Laden: Bitte wende dich an den Admin (error: PNF-${id}!`,
timeout: 10000,
progress: true,
});
}
// Sort widgets by priority // Sort widgets by priority
/** @todo Remove priority with first beta */ /** @todo Remove priority with first beta */
loadedPlugins.widgets.sort( loadedPlugins.widgets.sort(
(a, b) => <number>(b.order || b.priority) - <number>(a.order || a.priority) (a, b) => <number>(b.order || b.priority) - <number>(a.order || a.priority)
); );
/** @todo Can be cleaned up with first beta */ /** @todo Can be cleaned up with first beta */
loadedPlugins.menuLinks.sort((a, b) => { loadedPlugins.menuLinks.sort((a, b) => {
const diff = a.order && b.order ? b.order - a.order : 0; const diff = a.order && b.order ? b.order - a.order : 0;
return diff ? diff : a.title.toString().localeCompare(b.title.toString()); return diff ? diff : a.title.toString().localeCompare(b.title.toString());
}); });
// Add loaded routes to router return loadedPlugins;
loadedPlugins.routes.forEach((route) => router.addRoute(route)); }
// save plugins in VM-variable
app.provide('flaschengeist', loadedPlugins);
});

View File

@ -1,14 +1,9 @@
import { useMainStore, pinia } from '@flaschengeist/api'; /**
import { saveToken } from 'app/api'; * This boot file installs the global pinia instance
*/
import { pinia } from '@flaschengeist/api';
import { boot } from 'quasar/wrappers'; import { boot } from 'quasar/wrappers';
export default boot(({ app }) => { export default boot(({ app }) => {
app.use(pinia); app.use(pinia);
const store = useMainStore();
store.init().finally(() => {
store.$subscribe((mutation, state) => {
saveToken(state.session?.token);
});
});
}); });