[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)
// --> boot files are part of "main.js"
// 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
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 { api, PersistentStorage } from '@flaschengeist/api';
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 **************
@ -25,21 +21,6 @@ const PLUGINS = <Array<Promise<ImportPlgn>>>[
/*INSERT_PLUGIN_LIST*/
];
interface BackendPlugin {
permissions: string[];
version: string;
}
interface BackendPlugins {
[key: string]: BackendPlugin;
}
interface Backend {
plugins: BackendPlugins;
version: string;
}
export { Backend };
// Handle Notifications
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(
loadedPlugins: FG_Plugin.Flaschengeist,
plugin: FG_Plugin.Plugin,
backend: Backend
backend: FG.Backend
) {
// Check if already loaded
if (loadedPlugins.plugins.findIndex((p) => p.id === plugin.id) !== -1) return true;
@ -263,43 +244,9 @@ function loadPlugin(
return true;
}
async function loadBaseUrl() {
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;
}
export async function loadPlugins(backend: FG.Backend, baseRoutes: RouteRecordRaw[]) {
const loadedPlugins: FG_Plugin.Flaschengeist = {
routes,
routes: baseRoutes,
plugins: [],
menuLinks: [],
shortcuts: [],
@ -307,50 +254,35 @@ export default boot(async ({ router, app }) => {
widgets: [],
};
try {
// Wait for all plugins to be loaded
const results = await Promise.allSettled(PLUGINS);
// Wait for all plugins to be loaded
const results = await Promise.allSettled(PLUGINS);
// Check if loaded successfully
results.forEach((result) => {
if (result.status === 'rejected') {
throw <string>result.reason;
} else {
if (
!(
validatePlugin(result.value.default) &&
loadPlugin(loadedPlugins, result.value.default, backend)
)
// Check if loaded successfully
results.forEach((result) => {
if (result.status === 'rejected') {
throw <string>result.reason;
} else {
if (
!(
validatePlugin(result.value.default) &&
loadPlugin(loadedPlugins, result.value.default, backend)
)
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,
});
}
)
throw result.value.default.id;
}
});
// Sort widgets by priority
/** @todo Remove priority with first beta */
loadedPlugins.widgets.sort(
(a, b) => <number>(b.order || b.priority) - <number>(a.order || a.priority)
);
/** @todo Can be cleaned up with first beta */
loadedPlugins.menuLinks.sort((a, b) => {
const diff = a.order && b.order ? b.order - a.order : 0;
return diff ? diff : a.title.toString().localeCompare(b.title.toString());
});
// Add loaded routes to router
loadedPlugins.routes.forEach((route) => router.addRoute(route));
// save plugins in VM-variable
app.provide('flaschengeist', loadedPlugins);
});
return 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';
export default boot(({ app }) => {
app.use(pinia);
const store = useMainStore();
store.init().finally(() => {
store.$subscribe((mutation, state) => {
saveToken(state.session?.token);
});
});
});