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);
+ },
},
});