diff --git a/CHANGELOG.md b/CHANGELOG.md index e4d0e0768..886666dea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to ### Fixed +- 🩺(project) reload app if front and back unsync #2276 - 🐛(frontend) fix patch and comments #2273 - 🐛(frontend) interlinking are exported correctly in print mode #2269 - 💬(frontend) add missing link in onboarding description #2233 diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index 8237a5291..62b4f92ae 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -2841,6 +2841,7 @@ class ConfigView(drf.views.APIView): dict_settings[setting] = getattr(settings, setting) dict_settings["theme_customization"] = self._load_theme_customization() + dict_settings["RELEASE_VERSION"] = settings.RELEASE return drf.response.Response(dict_settings) diff --git a/src/backend/core/tests/test_api_config.py b/src/backend/core/tests/test_api_config.py index 84151df7f..b3ecee83e 100644 --- a/src/backend/core/tests/test_api_config.py +++ b/src/backend/core/tests/test_api_config.py @@ -34,6 +34,7 @@ pytestmark = pytest.mark.django_db FRONTEND_THEME="test-theme", MEDIA_BASE_URL="http://testserver/", POSTHOG_KEY={"id": "132456", "host": "https://eu.i.posthog-test.com"}, + RELEASE="1.0.0", SENTRY_DSN="https://sentry.test/123", THEME_CUSTOMIZATION_FILE_PATH="", ) @@ -77,6 +78,7 @@ def test_api_config(is_authenticated): "LANGUAGE_CODE": "en-us", "MEDIA_BASE_URL": "http://testserver/", "POSTHOG_KEY": {"id": "132456", "host": "https://eu.i.posthog-test.com"}, + "RELEASE_VERSION": "1.0.0", "SENTRY_DSN": "https://sentry.test/123", "TRASHBIN_CUTOFF_DAYS": 30, "theme_customization": {}, diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts index ba80e0a86..7590964b6 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts @@ -1,6 +1,11 @@ import { expect, test } from '@playwright/test'; -import { createDoc, getCurrentConfig, verifyDocName } from './utils-common'; +import { + createDoc, + getCurrentConfig, + overrideConfig, + verifyDocName, +} from './utils-common'; import { writeInEditor } from './utils-editor'; import { SignIn, expectLoginPage } from './utils-signin'; import { createRootSubPage } from './utils-sub-pages'; @@ -145,6 +150,29 @@ test.describe('Doc Routing', () => { ); await expect(page).toHaveTitle(/401 Unauthorized - Docs/); }); + + test('checks redirect if unsync version', async ({ page }) => { + await overrideConfig(page, { + RELEASE_VERSION: '0.0.0', + }); + + let counterReload = 0; + await page.route(/.*\/users\/me\/$/, async (route) => { + counterReload += 1; + await route.continue(); + }); + + await page.waitForTimeout(1000); + + // The sessionStorage guard should be set to the mismatched backend version. + const reloadVersion = await page.evaluate(() => + sessionStorage.getItem('reload-version'), + ); + expect(reloadVersion).toBe('0.0.0'); + + // The page should have reloaded once + expect(counterReload).toBe(2); + }); }); test.describe('Doc Routing: Not logged', () => { diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts index 0a7dc3acf..032ac32df 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts @@ -4,6 +4,7 @@ import path from 'path'; import { Locator, Page, TestInfo, expect } from '@playwright/test'; import theme_customization from '../../../../../backend/impress/configuration/theme/default.json'; +import { version as packageJsonVersion } from '../../package.json'; export type BrowserName = 'chromium' | 'firefox' | 'webkit'; export const BROWSERS: BrowserName[] = ['chromium', 'webkit', 'firefox']; @@ -40,6 +41,7 @@ export const CONFIG = { ], LANGUAGE_CODE: 'en-us', POSTHOG_KEY: {}, + RELEASE_VERSION: packageJsonVersion, SENTRY_DSN: null, TRASHBIN_CUTOFF_DAYS: 30, theme_customization, diff --git a/src/frontend/apps/impress/next.config.js b/src/frontend/apps/impress/next.config.js index f99905954..c641b271e 100644 --- a/src/frontend/apps/impress/next.config.js +++ b/src/frontend/apps/impress/next.config.js @@ -2,6 +2,8 @@ const crypto = require('crypto'); const { InjectManifest } = require('workbox-webpack-plugin'); +const { version } = require('./package.json'); + const buildId = crypto.randomBytes(256).toString('hex').slice(0, 8); /** @type {import('next').NextConfig} */ @@ -25,6 +27,7 @@ const nextConfig = { generateBuildId: () => buildId, env: { NEXT_PUBLIC_BUILD_ID: buildId, + NEXT_PUBLIC_APP_VERSION: version, }, /** * In dev mode, Next.js doesn't use Webpack, but Turbopack. diff --git a/src/frontend/apps/impress/src/core/config/ConfigProvider.tsx b/src/frontend/apps/impress/src/core/config/ConfigProvider.tsx index da4e35cd7..4c52cf0c8 100644 --- a/src/frontend/apps/impress/src/core/config/ConfigProvider.tsx +++ b/src/frontend/apps/impress/src/core/config/ConfigProvider.tsx @@ -85,6 +85,32 @@ export const ConfigProvider = ({ children }: PropsWithChildren) => { }); }, [conf?.CRISP_WEBSITE_ID]); + useEffect(() => { + const frontendVersion = process.env.NEXT_PUBLIC_APP_VERSION; + + if ( + !conf?.RELEASE_VERSION || + !frontendVersion || + conf.RELEASE_VERSION === frontendVersion + ) { + return; + } + + // Avoid infinite reload loops: only reload once per backend version + const RELOAD_VERSION_KEY = 'reload-version'; + try { + const reloadedForVersion = sessionStorage.getItem(RELOAD_VERSION_KEY); + if (reloadedForVersion === conf.RELEASE_VERSION) { + return; + } + + sessionStorage.setItem(RELOAD_VERSION_KEY, conf.RELEASE_VERSION); + window.location.reload(); + } catch { + console.warn('Failed to access sessionStorage for version reload logic'); + } + }, [conf?.RELEASE_VERSION]); + if (!conf) { return ( diff --git a/src/frontend/apps/impress/src/core/config/api/useConfig.tsx b/src/frontend/apps/impress/src/core/config/api/useConfig.tsx index d41fb0d0b..e6843f2ee 100644 --- a/src/frontend/apps/impress/src/core/config/api/useConfig.tsx +++ b/src/frontend/apps/impress/src/core/config/api/useConfig.tsx @@ -57,6 +57,7 @@ export interface ConfigResponse { LANGUAGE_CODE: string; MEDIA_BASE_URL?: string; POSTHOG_KEY?: PostHogConf; + RELEASE_VERSION: string; SENTRY_DSN?: string; TRASHBIN_CUTOFF_DAYS?: number; theme_customization?: ThemeCustomization; @@ -94,13 +95,13 @@ export const KEY_CONFIG = 'config'; export function useConfig() { const cachedData = getCachedTranslation(); - const oneHour = 1000 * 60 * 60; + const staleTime = 1000 * 60 * 5; return useQuery({ queryKey: [KEY_CONFIG], queryFn: () => getConfig(), initialData: cachedData, - staleTime: oneHour, - initialDataUpdatedAt: Date.now() - oneHour, // Force initial data to be considered stale + staleTime, + initialDataUpdatedAt: Date.now() - staleTime, // Force initial data to be considered stale }); }