diff --git a/changes/45862-android-enterprise-page-refresh b/changes/45862-android-enterprise-page-refresh new file mode 100644 index 0000000000..95a619994b --- /dev/null +++ b/changes/45862-android-enterprise-page-refresh @@ -0,0 +1 @@ +- Fixed Android Enterprise page not refreshing after connecting or disconnecting Android MDM, so the Enterprise ID and card state are visible without a manual page reload. diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AndroidMdmPage/AndroidMdmPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AndroidMdmPage/AndroidMdmPage.tsx index c4e5661625..ae1e3221af 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AndroidMdmPage/AndroidMdmPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AndroidMdmPage/AndroidMdmPage.tsx @@ -6,11 +6,12 @@ import React, { useState, } from "react"; import { InjectedRouter } from "react-router"; -import { useQuery } from "react-query"; +import { useQuery, useQueryClient } from "react-query"; import PATHS from "router/paths"; import { AppContext } from "context/app"; import { NotificationContext } from "context/notification"; +import { IConfig } from "interfaces/config"; import { getErrorReason } from "interfaces/errors"; import mdmAndroidAPI from "services/entities/mdm_android"; import { DEFAULT_USE_QUERY_OPTIONS, SUPPORT_LINK } from "utilities/constants"; @@ -37,6 +38,8 @@ interface ITurnOnAndroidMdmProps { const TurnOnAndroidMdm = ({ router }: ITurnOnAndroidMdmProps) => { const { renderFlash } = useContext(NotificationContext); + const { setConfig } = useContext(AppContext); + const queryClient = useQueryClient(); // TODO: figure out issue with aborting the SSE fetch when the window is closed const newWindow = useRef(null); @@ -48,18 +51,33 @@ const TurnOnAndroidMdm = ({ router }: ITurnOnAndroidMdmProps) => { async (abortController: AbortController) => { try { await mdmAndroidAPI.startSSE(abortController.signal); - abortController.abort(); - renderFlash("success", "Android MDM turned on successfully.", { - persistOnPageChange: true, - }); - setSetupSse(false); - router.push(PATHS.ADMIN_INTEGRATIONS_MDM); } catch { renderFlash("error", "Couldn't turn on Android MDM. Please try again."); setSetupSse(false); + return; } + abortController.abort(); + // SSE success means the backend has already set + // android_enabled_and_configured=true. Patch the in-memory config so + // AppContext.isAndroidMdmEnabledAndConfigured flips immediately and + // AndroidMdmCard renders correctly on redirect, without a synchronous + // round-trip. + const prevConfig = queryClient.getQueryData(["config"]); + if (prevConfig) { + const patched: IConfig = { + ...prevConfig, + mdm: { ...prevConfig.mdm, android_enabled_and_configured: true }, + }; + setConfig(patched); + queryClient.setQueryData(["config"], patched); + } + renderFlash("success", "Android MDM turned on successfully.", { + persistOnPageChange: true, + }); + setSetupSse(false); + router.push(PATHS.ADMIN_INTEGRATIONS_MDM); }, - [renderFlash, router] + [queryClient, renderFlash, router, setConfig] ); useEffect(() => { diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AndroidMdmPage/components/TurnOffAndroidMdmModal/TurnOffAndroidMdmModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AndroidMdmPage/components/TurnOffAndroidMdmModal/TurnOffAndroidMdmModal.tsx index 22a0bafacf..c48dba51ed 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AndroidMdmPage/components/TurnOffAndroidMdmModal/TurnOffAndroidMdmModal.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AndroidMdmPage/components/TurnOffAndroidMdmModal/TurnOffAndroidMdmModal.tsx @@ -1,9 +1,12 @@ import React, { useCallback, useContext, useState } from "react"; import { InjectedRouter } from "react-router"; +import { useQueryClient } from "react-query"; import PATHS from "router/paths"; import mdmAndroidAPI from "services/entities/mdm_android"; +import { AppContext } from "context/app"; import { NotificationContext } from "context/notification"; +import { IConfig } from "interfaces/config"; import Modal from "components/Modal"; import Button from "components/buttons/Button"; @@ -20,6 +23,8 @@ const TurnOffAndroidMdmModal = ({ router, }: ITurnOffAndroidMdmModalProps) => { const { renderFlash } = useContext(NotificationContext); + const { setConfig } = useContext(AppContext); + const queryClient = useQueryClient(); const [isDeleting, setIsDeleting] = useState(false); @@ -27,15 +32,28 @@ const TurnOffAndroidMdmModal = ({ setIsDeleting(true); try { await mdmAndroidAPI.turnOffAndroidMdm(); - renderFlash("success", "Android MDM turned off successfully.", { - persistOnPageChange: true, - }); - router.push(PATHS.ADMIN_INTEGRATIONS_MDM); } catch (e) { onExit(); renderFlash("error", "Couldn't turn off Android MDM. Please try again."); + return; } - }, [onExit, renderFlash, router]); + // DELETE success means the backend has already cleared + // android_enabled_and_configured. Patch the in-memory config so the + // parent MDM page's card flips immediately on redirect. + const prevConfig = queryClient.getQueryData(["config"]); + if (prevConfig) { + const patched: IConfig = { + ...prevConfig, + mdm: { ...prevConfig.mdm, android_enabled_and_configured: false }, + }; + setConfig(patched); + queryClient.setQueryData(["config"], patched); + } + renderFlash("success", "Android MDM turned off successfully.", { + persistOnPageChange: true, + }); + router.push(PATHS.ADMIN_INTEGRATIONS_MDM); + }, [onExit, queryClient, renderFlash, router, setConfig]); return ( diff --git a/frontend/services/entities/mdm_android.ts b/frontend/services/entities/mdm_android.ts index 49d1506244..eddf646714 100644 --- a/frontend/services/entities/mdm_android.ts +++ b/frontend/services/entities/mdm_android.ts @@ -44,18 +44,32 @@ export default { }); const reader = response?.body?.getReader(); + if (!reader) { + reject(new Error("Android MDM SSE stream unavailable")); + return; + } const decoder = new TextDecoder(); + const successSignal = "Android Enterprise successfully connected"; + // Buffer accumulates decoded text so a success message split across + // multiple chunks (valid with chunked transfer encoding) is still + // detected. + let buffer = ""; while (true) { - // @ts-ignore // eslint-disable-next-line no-await-in-loop - const { done, value } = await reader?.read(); - if (done) break; - const text = decoder.decode(value); - if (text === "Android Enterprise successfully connected") { - resolve(); - break; + const { done, value } = await reader.read(); + if (done) { + // Server closed the stream without ever sending success. + // Reject so callers don't await forever on unmount or backend hiccup. + reject(new Error("Android MDM SSE ended before success signal")); + return; } + buffer += decoder.decode(value, { stream: true }); + if (buffer.includes(successSignal)) { + resolve(); + return; + } + buffer = buffer.slice(-successSignal.length); } } catch (error) { if ((error as Error).name === "AbortError") {