diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index e8936955d..db94b0829 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -3,6 +3,7 @@ namespace App\Http; use App\Http\Middleware\EnsureModelNotStale; +use App\Http\Middleware\FrontendVersion; use App\Http\Middleware\LogContext; use App\Http\Middleware\RequestMetricsMiddleware; use App\Http\Middleware\RoomAuthenticate; @@ -59,6 +60,7 @@ class Kernel extends HttpKernel \Illuminate\Routing\Middleware\SubstituteBindings::class, \App\Http\Middleware\SetApplicationLocale::class, LogContext::class, + FrontendVersion::class, ], ]; diff --git a/app/Http/Middleware/FrontendVersion.php b/app/Http/Middleware/FrontendVersion.php new file mode 100644 index 000000000..4cf7ec093 --- /dev/null +++ b/app/Http/Middleware/FrontendVersion.php @@ -0,0 +1,32 @@ +headers->set('X-Frontend-Version', $manifestHash); + } + + return $response; + } +} diff --git a/lang/en/app.php b/lang/en/app.php index f50528ac5..1e6b23e1b 100644 --- a/lang/en/app.php +++ b/lang/en/app.php @@ -34,6 +34,7 @@ 'error' => 'An error occurred', 'errors' => [ 'attendance_agreement_missing' => 'Consent to record attendance is required.', + 'frontend_outdated' => 'Your version of the application is outdated. Please reload to continue.', 'join_failed' => 'Joining the room has failed because a connection error has occurred.', 'meeting_attendance_disabled' => 'Attendance logging was not active at this meeting.', 'meeting_attendance_not_ended' => 'The attendance logs are not yet available for this meeting as it has not yet ended.', diff --git a/resources/js/components/App.vue b/resources/js/components/App.vue index 273ba86d9..90ff12b01 100644 --- a/resources/js/components/App.vue +++ b/resources/js/components/App.vue @@ -26,6 +26,7 @@
+
@@ -37,7 +38,7 @@ import { useLoadingStore } from "../stores/loading"; import { useSettingsStore } from "../stores/settings"; import Toast from "primevue/toast"; import { useRoute } from "vue-router"; -import { computed } from "vue"; +import { computed, watch } from "vue"; const loadingStore = useLoadingStore(); const settingsStore = useSettingsStore(); diff --git a/resources/js/components/FrontendOutdatedDialog.vue b/resources/js/components/FrontendOutdatedDialog.vue new file mode 100644 index 000000000..4bccc6acf --- /dev/null +++ b/resources/js/components/FrontendOutdatedDialog.vue @@ -0,0 +1,41 @@ + + + diff --git a/resources/js/services/Api.js b/resources/js/services/Api.js index d70623a9e..78547c135 100644 --- a/resources/js/services/Api.js +++ b/resources/js/services/Api.js @@ -18,6 +18,17 @@ export class Api { this.t = i18n.global.t; } + setupAxiosInterceptors(settingsStore) { + // Add a response interceptor + axios.interceptors.response.use(function onFulfilled(response) { + const frontendHash = response.headers["x-frontend-version"]; + if (frontendHash !== undefined) + settingsStore.setFrontendVersion(frontendHash); + + return response; + }); + } + /** * Makes a request with the passed params. * diff --git a/resources/js/stores/loading.js b/resources/js/stores/loading.js index dedfbaf3a..29ed5134c 100644 --- a/resources/js/stores/loading.js +++ b/resources/js/stores/loading.js @@ -1,6 +1,7 @@ import { defineStore } from "pinia"; import { useAuthStore } from "./auth"; import { useSettingsStore } from "./settings"; +import { useApi } from "../composables/useApi.js"; export const useLoadingStore = defineStore("loading", { state: () => { @@ -23,12 +24,15 @@ export const useLoadingStore = defineStore("loading", { }, actions: { async initialize() { + const api = useApi(); const auth = useAuthStore(); const settings = useSettingsStore(); this.setLoading(); await settings.getSettings(); + settings.setupAxiosInterceptors(); + await auth.getCurrentUser(); this.initialized = true; diff --git a/resources/js/stores/settings.js b/resources/js/stores/settings.js index 4507eef5a..0bc797f62 100644 --- a/resources/js/stores/settings.js +++ b/resources/js/stores/settings.js @@ -8,6 +8,7 @@ export const useSettingsStore = defineStore("settings", { state: () => { return { settings: null, + frontendVersion: null, }; }, getters: { @@ -30,5 +31,14 @@ export const useSettingsStore = defineStore("settings", { this.settings.theme.rounded, ); }, + + setFrontendVersion(version) { + this.frontendVersion = version; + }, + + setupAxiosInterceptors() { + const api = useApi(); + api.setupAxiosInterceptors(this); + }, }, });