From 7a705d5f9abadc59f053757ea36fb54241bceab3 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Sat, 27 Nov 2021 01:18:58 +0100 Subject: [PATCH] [core] Ensure everything is initialized in the correct order. Make sure api is initialized before making any requests. --- quasar.conf.js | 2 +- src/boot/init.ts | 81 ++++++++++++++++++++++++++++++++ src/boot/plugins.ts | 110 +++++++++----------------------------------- src/boot/store.ts | 13 ++---- 4 files changed, 107 insertions(+), 99 deletions(-) create mode 100644 src/boot/init.ts diff --git a/quasar.conf.js b/quasar.conf.js index e981046..6a86699 100644 --- a/quasar.conf.js +++ b/quasar.conf.js @@ -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'], diff --git a/src/boot/init.ts b/src/boot/init.ts new file mode 100644 index 0000000..361b2bb --- /dev/null +++ b/src/boot/init.ts @@ -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('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('/'); + 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); + } + } +}); diff --git a/src/boot/plugins.ts b/src/boot/plugins.ts index d89cb20..b45bf7c 100644 --- a/src/boot/plugins.ts +++ b/src/boot/plugins.ts @@ -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 = >>[ /*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('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('/'); - 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 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 result.reason; + } else { + if ( + !( + validatePlugin(result.value.default) && + loadPlugin(loadedPlugins, result.value.default, backend) ) - throw result.value.default.id; - } - }); - } catch (reason) { - const id = 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) => (b.order || b.priority) - (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; +} diff --git a/src/boot/store.ts b/src/boot/store.ts index d54acfc..1c1f364 100644 --- a/src/boot/store.ts +++ b/src/boot/store.ts @@ -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); - }); - }); });