diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AndroidMdmPage/AndroidMdmPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AndroidMdmPage/AndroidMdmPage.tsx index 3fbeb9c73e..627902ba23 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AndroidMdmPage/AndroidMdmPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AndroidMdmPage/AndroidMdmPage.tsx @@ -1,4 +1,10 @@ -import React, { useContext, useState } from "react"; +import React, { + useCallback, + useContext, + useEffect, + useRef, + useState, +} from "react"; import { InjectedRouter } from "react-router"; import { useQuery } from "react-query"; @@ -21,17 +27,68 @@ import TurnOffAndroidMdmModal from "./components/TurnOffAndroidMdmModal"; const baseClass = "android-mdm-page"; -const TurnOnAndroidMdm = () => { +const POPUP_WIDTH = 885; +const POPUP_HEIGHT = 600; + +interface ITurnOnAndroidMdmProps { + router: InjectedRouter; +} + +const TurnOnAndroidMdm = ({ router }: ITurnOnAndroidMdmProps) => { const { renderFlash } = useContext(NotificationContext); + + // TODO: figure out issue with aborting the SSE fetch when the window is closed + const newWindow = useRef(null); + const [fetchingSignupUrl, setFetchingSignupUrl] = useState(false); + const [setupSse, setSetupSse] = useState(false); + + const handleSSE = useCallback( + 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); + } + }, + [renderFlash, router] + ); + + useEffect(() => { + const abortController = new AbortController(); + + if (setupSse) { + handleSSE(abortController); + + return () => { + abortController.abort(); + }; + } + }, [setupSse, router, renderFlash, handleSSE]); const onConnectMdm = async () => { setFetchingSignupUrl(true); try { const res = await mdmAndroidAPI.getSignupUrl(); - // TODO: set up SSE for successful android mdm turned on here. - window.open(res.android_enterprise_signup_url, "_blank"); + // Calculate the center position + const left = window.screenX + (window.innerWidth - POPUP_WIDTH) / 2; + const top = window.screenY + (window.innerHeight - POPUP_HEIGHT) / 2; + + // TODO: figure out issue with aborting the SSE fetch when the window is closed + newWindow.current = window.open( + res.android_enterprise_signup_url, + "_blank", + `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},top=${top},left=${left}` + ); + setSetupSse(true); } catch (e) { renderFlash("error", "Couldn't connect. Please try again"); } @@ -112,9 +169,6 @@ interface IAndroidMdmPageProps { const AndroidMdmPage = ({ router }: IAndroidMdmPageProps) => { const { isAndroidMdmEnabledAndConfigured } = useContext(AppContext); - - const { renderFlash } = useContext(NotificationContext); - const [showTurnOffMdmModal, setShowTurnOffMdmModal] = useState(false); return ( @@ -128,7 +182,7 @@ const AndroidMdmPage = ({ router }: IAndroidMdmPageProps) => {
{!isAndroidMdmEnabledAndConfigured ? ( - + ) : ( setShowTurnOffMdmModal(true)} diff --git a/frontend/services/entities/mdm_android.ts b/frontend/services/entities/mdm_android.ts index 85179dc9bb..4e66fa2074 100644 --- a/frontend/services/entities/mdm_android.ts +++ b/frontend/services/entities/mdm_android.ts @@ -1,5 +1,6 @@ import sendRequest from "services"; import endpoints from "utilities/endpoints"; +import { authToken } from "utilities/local"; interface IGetAndroidSignupUrlResponse { android_enterprise_signup_url: string; @@ -24,4 +25,46 @@ export default { const { MDM_ANDROID_ENTERPRISE } = endpoints; return sendRequest("DELETE", MDM_ANDROID_ENTERPRISE); }, + + /** + * This function starts a Server-Sent Events connection with the fleet server + * to get messages about a successful Android mdm connection. We have to use + * fetch here because the EventSource API does not support setting headers, + * which we need to authenticate the request. + */ + startSSE: (abortSignal: AbortSignal): Promise => { + return new Promise(async (resolve, reject) => { + try { + const response = await fetch(endpoints.MDM_ANDROID_SSE_URL, { + method: "GET", + headers: { + Authorization: `Bearer ${authToken()}`, + }, + signal: abortSignal, + }); + + const reader = response?.body?.getReader(); + const decoder = new TextDecoder(); + + 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; + } + } + } catch (error) { + if ((error as Error).name === "AbortError") { + // we want to ignore abort errors + console.error("SSE Fetch aborted"); + } else { + reject(error); + } + } + }); + }, }; diff --git a/frontend/utilities/endpoints.ts b/frontend/utilities/endpoints.ts index 2050b21b0e..de7b0a289d 100644 --- a/frontend/utilities/endpoints.ts +++ b/frontend/utilities/endpoints.ts @@ -94,6 +94,7 @@ export default { MDM_ANDROID_ENTERPRISE: `/${API_VERSION}/fleet/android_enterprise`, MDM_ANDROID_SIGNUP_URL: `/${API_VERSION}/fleet/android_enterprise/signup_url`, + MDM_ANDROID_SSE_URL: `/api/${API_VERSION}/fleet/android_enterprise/signup_sse`, // apple mdm endpoints MDM_APPLE: `/${API_VERSION}/fleet/mdm/apple`,