From 8e4b2e2fc685380c2366308295a5820439e5f2a9 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Fri, 3 Jul 2026 09:55:07 +0100 Subject: [PATCH] Disable update check and notification in SaaS mode (#6863) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description of Changes In SaaS mode the self-hosted "Update Available" notification could still appear and the update-check code (external call to `supabase.stirling.com/functions/v1/updates`) still ran, even though the cloud owns app versioning. The web `UpdateStartupPopup` was already SaaS-gated via a null override, but two other paths were not: - **Desktop app in SaaS connection mode** - `useDesktopUpdatePopup()` ran its startup check and rendered the `UpdateModal` regardless of connection mode, so a self-hosted update popup appeared while connected to SaaS. - **Settings → General** - the core `GeneralSection` fired `checkForUpdate()` on mount unconditionally, even when the update section was hidden (as SaaS does), so the external call still ran. **What changed** - `useDesktopUpdatePopup.ts` - the startup timer now bails out immediately when `connectionModeService.getCurrentMode() === "saas"`. No mode lookup, no external fetch, no modal. - `core/GeneralSection.tsx` - the mount `checkForUpdate()` now returns early when `hideUpdateSection` is set, so hiding the section (web SaaS, managed-disabled desktop) also stops the external call. - `desktop/GeneralSection.tsx` - passes `hideUpdateSection` when `useSaaSMode()` is true, which (via the above) suppresses the settings check in desktop-SaaS too. **Why** - in SaaS the update check should never be called and no update notification should be shown; the cloud handles versioning. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [x] I have run `task check` to verify linters, typechecks, and tests pass - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details. --- .../config/configSections/GeneralSection.tsx | 6 +++-- .../config/configSections/GeneralSection.tsx | 7 ++++- .../hooks/useDesktopUpdatePopup.test.ts | 26 ++++++++++++++++++- .../desktop/hooks/useDesktopUpdatePopup.ts | 5 ++++ 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/frontend/editor/src/core/components/shared/config/configSections/GeneralSection.tsx b/frontend/editor/src/core/components/shared/config/configSections/GeneralSection.tsx index e5fdacea79..598b705347 100644 --- a/frontend/editor/src/core/components/shared/config/configSections/GeneralSection.tsx +++ b/frontend/editor/src/core/components/shared/config/configSections/GeneralSection.tsx @@ -112,12 +112,14 @@ const GeneralSection: React.FC = ({ // falling back to the backend version const currentVersion = appVersion ?? config?.appVersion ?? null; - // Check for updates on mount + // Check for updates on mount — skipped when the update UI is hidden (SaaS + // build, managed-disabled desktop) so no external update call ever fires. useEffect(() => { + if (hideUpdateSection) return; if (currentVersion) { checkForUpdate(); } - }, [currentVersion, config?.machineType]); + }, [currentVersion, config?.machineType, hideUpdateSection]); const checkForUpdate = async () => { if (!currentVersion) return; diff --git a/frontend/editor/src/desktop/components/shared/config/configSections/GeneralSection.tsx b/frontend/editor/src/desktop/components/shared/config/configSections/GeneralSection.tsx index abe37d10b8..98c4455ce0 100644 --- a/frontend/editor/src/desktop/components/shared/config/configSections/GeneralSection.tsx +++ b/frontend/editor/src/desktop/components/shared/config/configSections/GeneralSection.tsx @@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next"; import CoreGeneralSection from "@core/components/shared/config/configSections/GeneralSection"; import { DefaultAppSettings } from "@app/components/shared/config/configSections/DefaultAppSettings"; import { useDesktopInstall } from "@app/hooks/useDesktopInstall"; +import { useSaaSMode } from "@app/hooks/useSaaSMode"; import { desktopUpdateService, type UpdateMode, @@ -22,6 +23,9 @@ import { const GeneralSection: React.FC = () => { const { t } = useTranslation(); const install = useDesktopInstall(); + // In SaaS connection mode the cloud owns app versioning — hide the update + // section (which also stops the core auto-check from firing). + const isSaaSMode = useSaaSMode(); const [updateModeInfo, setUpdateModeInfo] = useState({ mode: "prompt", locked: false, @@ -93,7 +97,8 @@ const GeneralSection: React.FC = () => { )} ({ invoke: (cmd: string, args?: unknown) => invokeMock(cmd, args), @@ -36,6 +37,12 @@ vi.mock("@app/services/desktopUpdateService", () => ({ }, })); +vi.mock("@app/services/connectionModeService", () => ({ + connectionModeService: { + getCurrentMode: () => getCurrentModeMock(), + }, +})); + import { useDesktopUpdatePopup } from "@app/hooks/useDesktopUpdatePopup"; /** Flush pending microtasks so awaited promises settle. */ @@ -74,8 +81,11 @@ describe("useDesktopUpdatePopup — auto mode", () => { getUpdateModeMock.mockReset(); canInstallUpdatesMock.mockReset(); getUpdateSummaryMock.mockReset(); + getCurrentModeMock.mockReset(); - // Defaults: auto mode, update available, install permitted. + // Defaults: local (non-SaaS) connection, auto mode, update available, + // install permitted. + getCurrentModeMock.mockResolvedValue("local"); getUpdateModeMock.mockResolvedValue("auto"); getVersionMock.mockResolvedValue("1.0.0"); getUpdateSummaryMock.mockResolvedValue({ latest_version: "2.0.0" }); @@ -189,4 +199,18 @@ describe("useDesktopUpdatePopup — auto mode", () => { expect(invocations).toContain("download_and_install_update"); expect(invocations).toContain("restart_app"); }); + + it("skips the update check entirely in SaaS connection mode", async () => { + // In SaaS mode the cloud owns versioning — the self-hosted update check + // must never run: no mode lookup, no external summary fetch, no install. + getCurrentModeMock.mockResolvedValue("saas"); + + await runStartup(); + + expect(getUpdateModeMock).not.toHaveBeenCalled(); + expect(getUpdateSummaryMock).not.toHaveBeenCalled(); + const invocations = invokeMock.mock.calls.map((c) => c[0]); + expect(invocations).not.toContain("download_and_install_update"); + expect(invocations).not.toContain("restart_app"); + }); }); diff --git a/frontend/editor/src/desktop/hooks/useDesktopUpdatePopup.ts b/frontend/editor/src/desktop/hooks/useDesktopUpdatePopup.ts index 6a2df77914..0cd0fcd003 100644 --- a/frontend/editor/src/desktop/hooks/useDesktopUpdatePopup.ts +++ b/frontend/editor/src/desktop/hooks/useDesktopUpdatePopup.ts @@ -6,6 +6,7 @@ import { desktopUpdateService, type CanInstallResult, } from "@app/services/desktopUpdateService"; +import { connectionModeService } from "@app/services/connectionModeService"; const SNOOZE_KEY = "stirling-pdf-updater:snoozedUntil"; const STARTUP_DELAY_MS = 15_000; @@ -72,6 +73,10 @@ export function useDesktopUpdatePopup() { hasChecked.current = true; const timer = setTimeout(async () => { + // In SaaS connection mode the cloud owns app versioning — the self-hosted + // update check + popup must never run (no external call, no modal). + if ((await connectionModeService.getCurrentMode()) === "saas") return; + let mode: Awaited> = "prompt"; try {