Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/Http/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -59,6 +60,7 @@ class Kernel extends HttpKernel
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\SetApplicationLocale::class,
LogContext::class,
FrontendVersion::class,
],
];

Expand Down
32 changes: 32 additions & 0 deletions app/Http/Middleware/FrontendVersion.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace App\Http\Middleware;

use Cache;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Vite;
use Symfony\Component\HttpFoundation\Response;

class FrontendVersion
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);

$manifestHash = Cache::flexible('vite-manifest-hash', [30, 60], function () {
return Vite::manifestHash();
});

if ($manifestHash) {
$response->headers->set('X-Frontend-Version', $manifestHash);
}

return $response;
}
}
1 change: 1 addition & 0 deletions lang/en/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand Down
3 changes: 2 additions & 1 deletion resources/js/components/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
</header>
<main :class="routeClass">
<router-view />
<FrontendOutdatedDialog />
</main>
<AppFooter />
</div>
Expand All @@ -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();
Expand Down
41 changes: 41 additions & 0 deletions resources/js/components/FrontendOutdatedDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<script setup>
import { ref, watch } from "vue";
import { useSettingsStore } from "../stores/settings.js";

const settingsStore = useSettingsStore();

const visible = ref(false);

watch(
() => settingsStore.frontendVersion,
(newVersion, oldVersion) => {
if (oldVersion && newVersion !== oldVersion) {
visible.value = true;
}
},
);

const reload = () => {
window.location.reload();
};
</script>

<template>
<Dialog
v-model:visible="visible"
modal
:style="{ width: '500px' }"
:breakpoints="{ '575px': '90vw' }"
:draggable="false"
:close-on-escape="false"
:dismissable-mask="false"
:closable="false"
>
<span>{{ $t("app.errors.frontend_outdated") }}</span>
<template #footer>
<div class="flex justify-end gap-2">
<Button :label="$t('app.reload')" @click="reload" />
</div>
</template>
</Dialog>
</template>
11 changes: 11 additions & 0 deletions resources/js/services/Api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
4 changes: 4 additions & 0 deletions resources/js/stores/loading.js
Original file line number Diff line number Diff line change
@@ -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: () => {
Expand All @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions resources/js/stores/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const useSettingsStore = defineStore("settings", {
state: () => {
return {
settings: null,
frontendVersion: null,
};
},
getters: {
Expand All @@ -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);
},
},
});
Loading