diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsAuthProvider.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsAuthProvider.tsx new file mode 100644 index 0000000000..dcd8324169 --- /dev/null +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsAuthProvider.tsx @@ -0,0 +1,101 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { ReactNode, useEffect, useState, useCallback } from 'react'; +import { ErrorPanel } from '@backstage/core-components'; +import { techdocsApiRef } from '@backstage/plugin-techdocs-react'; +import { useApi, useApp } from '@backstage/core-plugin-api'; +import useAsyncRetry from 'react-use/lib/useAsyncRetry'; +import { Button } from '@material-ui/core'; + +type TechDocsRefreshCookieMessage = MessageEvent<{ + action: string; + payload: { + expiresAt: string; + }; +}>; + +function useTechDocsCookie() { + const techdocsApi = useApi(techdocsApiRef); + + const { retry, ...state } = useAsyncRetry(async () => { + return await techdocsApi.getCookie(); + }, [techdocsApi]); + + const refresh = useCallback( + (expiresAt: string) => { + // Randomize the refreshing margin to avoid all tabs refreshing at the same time + const refreshingMargin = (1 + 3 * Math.random()) * 60000; + const delay = Date.parse(expiresAt) - Date.now() - refreshingMargin; + const timeout = setTimeout(retry, delay); + return () => clearTimeout(timeout); + }, + [retry], + ); + + return { ...state, retry, refresh }; +} + +export function TechDocsAuthProvider({ children }: { children: ReactNode }) { + const app = useApp(); + const { Progress } = app.getComponents(); + + const [channel] = useState(new BroadcastChannel('techdocs-cookie-refresh')); + + const { loading, error, value, retry, refresh } = useTechDocsCookie(); + + useEffect(() => { + if (!value) return () => {}; + + channel.postMessage({ + action: 'TECHDOCS_COOKIE_REFRESHED', + payload: value, + }); + + let cancel = refresh(value.expiresAt); + + const handleMessage = (event: TechDocsRefreshCookieMessage): void => { + const { action, payload } = event.data; + if (action === 'TECHDOCS_COOKIE_REFRESHED') { + cancel(); + cancel = refresh(payload.expiresAt); + } + }; + + channel.addEventListener('message', handleMessage); + + return () => { + cancel(); + channel.removeEventListener('message', handleMessage); + }; + }, [value, refresh, channel]); + + if (error) { + return ( + + + + ); + } + + if (loading) { + return ; + } + + return children; +} diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx index 23b376b626..d7f3843ebb 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx @@ -14,23 +14,15 @@ * limitations under the License. */ -import React, { - ReactNode, - Children, - ReactElement, - useEffect, - useState, - useCallback, -} from 'react'; +import React, { ReactNode, Children, ReactElement } from 'react'; import { useOutlet } from 'react-router-dom'; -import { ErrorPanel, Page } from '@backstage/core-components'; +import { Page } from '@backstage/core-components'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { TECHDOCS_ADDONS_WRAPPER_KEY, TECHDOCS_ADDONS_KEY, TechDocsReaderPageProvider, - techdocsApiRef, } from '@backstage/plugin-techdocs-react'; import { TechDocsReaderPageRenderFunction } from '../../../types'; @@ -41,12 +33,9 @@ import { TechDocsReaderPageSubheader } from '../TechDocsReaderPageSubheader'; import { rootDocsRouteRef } from '../../../routes'; import { getComponentData, - useApi, - useApp, useRouteRefParams, } from '@backstage/core-plugin-api'; -import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { Button } from '@material-ui/core'; +import { TechDocsAuthProvider } from './TechDocsAuthProvider'; /* An explanation for the multiple ways of customizing the TechDocs reader page @@ -155,66 +144,6 @@ export const TechDocsReaderLayout = (props: TechDocsReaderLayoutProps) => { ); }; -function TechDocsAuthProvider({ children }: { children: ReactNode }) { - const app = useApp(); - const { Progress } = app.getComponents(); - - const [channel] = useState(new BroadcastChannel('techdocs-cookie-refresh')); - - const techdocsApi = useApi(techdocsApiRef); - - const { loading, error, value, retry } = useAsyncRetry(async () => { - return await techdocsApi.getCookie(); - }, [techdocsApi]); - - const startCookieRefresh = useCallback( - (expiresAt: string) => { - // Randomize the refreshing margin to avoid all tabs refreshing at the same time - const refreshingMargin = (1 + 3 * Math.random()) * 60_000; - const delay = Date.parse(expiresAt) - Date.now() - refreshingMargin; - const timeout = setTimeout(retry, delay); - return () => clearTimeout(timeout); - }, - [retry], - ); - - useEffect(() => { - if (!value) return () => {}; - channel.postMessage({ action: 'COOKIE_REFRESHED', payload: value }); - let stopCookieRefresh = startCookieRefresh(value.expiresAt); - const handleMessage = ( - event: MessageEvent<{ action: string; payload: { expiresAt: string } }>, - ): void => { - const { action, payload } = event.data; - if (action === 'COOKIE_REFRESHED') { - stopCookieRefresh(); - stopCookieRefresh = startCookieRefresh(payload.expiresAt); - } - }; - channel.addEventListener('message', handleMessage); - return () => { - stopCookieRefresh(); - channel.removeEventListener('message', handleMessage); - }; - }, [value, channel, startCookieRefresh]); - - if (error) { - return ( - - - - ); - } - - if (loading) { - return ; - } - - return children; -} - /** * @public */