diff --git a/packages/app/package.json b/packages/app/package.json index 79101dff70..9401ca89be 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -142,8 +142,7 @@ "@types/react": "*", "@types/react-dom": "*", "@types/zen-observable": "^0.8.0", - "cross-env": "^7.0.0", - "msw": "^1.0.0" + "cross-env": "^7.0.0" }, "bundled": true } diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 06851d6ac7..2639f2add8 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -108,7 +108,7 @@ import { DevToolsPage } from '@backstage/plugin-devtools'; import { customDevToolsPage } from './components/devtools/CustomDevToolsPage'; import { CatalogUnprocessedEntitiesPage } from '@backstage/plugin-catalog-unprocessed-entities'; import { NotificationsPage } from '@backstage/plugin-notifications'; -import { AppMode } from './AppMode'; +import { ExperimentalAppProtection } from '@backstage/plugin-auth-react'; const app = createApp({ apis, @@ -283,10 +283,10 @@ export default app.createRoot( - + {routes} - + , ); diff --git a/plugins/auth-react/api-report.md b/plugins/auth-react/api-report.md index 7f1489543b..d6b7d2e17f 100644 --- a/plugins/auth-react/api-report.md +++ b/plugins/auth-react/api-report.md @@ -18,6 +18,11 @@ export type CookieAuthRefreshProviderProps = { children: ReactNode; }; +// @public +export function ExperimentalAppProtection(props: { + children: ReactNode; +}): JSX.Element; + // @public export function RedirectToRoot(): React_2.JSX.Element | null; diff --git a/plugins/auth-react/package.json b/plugins/auth-react/package.json index 4e4989ea22..9de3aade21 100644 --- a/plugins/auth-react/package.json +++ b/plugins/auth-react/package.json @@ -44,7 +44,8 @@ "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0" + "@testing-library/user-event": "^14.0.0", + "msw": "^1.0.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0" diff --git a/packages/app/src/AppMode.test.tsx b/plugins/auth-react/src/components/ExperimentalAppProtection/ExperimentalAppProtection.test.tsx similarity index 88% rename from packages/app/src/AppMode.test.tsx rename to plugins/auth-react/src/components/ExperimentalAppProtection/ExperimentalAppProtection.test.tsx index b110944e1e..2884400ea9 100644 --- a/packages/app/src/AppMode.test.tsx +++ b/plugins/auth-react/src/components/ExperimentalAppProtection/ExperimentalAppProtection.test.tsx @@ -24,14 +24,14 @@ import { renderInTestApp, setupRequestMockHandlers, } from '@backstage/test-utils'; -import { AppMode } from './AppMode'; +import { ExperimentalAppProtection } from './ExperimentalAppProtection'; import { configApiRef, discoveryApiRef, fetchApiRef, } from '@backstage/core-plugin-api'; -describe('AppMode', () => { +describe('ExperimentalAppProtection', () => { const worker = setupServer(); setupRequestMockHandlers(worker); @@ -62,7 +62,7 @@ describe('AppMode', () => { [fetchApiRef, fetchApiMock], ]} > - Test content + Test content , ); expect(screen.getByTestId('progress')).toBeVisible(); @@ -79,7 +79,7 @@ describe('AppMode', () => { [fetchApiRef, fetchApiMock], ]} > - Test content + Test content , ); await waitFor(() => @@ -98,7 +98,7 @@ describe('AppMode', () => { [fetchApiRef, fetchApiMock], ]} > - Test content + Test content , ); await waitFor(() => @@ -109,7 +109,9 @@ describe('AppMode', () => { it('should render the children also when the public index is available', async () => { fetchApiMock.fetch.mockResolvedValueOnce({ ok: true }); - await renderInTestApp(Test content); + await renderInTestApp( + Test content, + ); await waitFor(() => expect(screen.getByText('Test content')).toBeInTheDocument(), ); @@ -138,7 +140,7 @@ describe('AppMode', () => { [discoveryApiRef, discoveryApiMock], ]} > - Test content + Test content , ); await waitFor(() => diff --git a/packages/app/src/AppMode.tsx b/plugins/auth-react/src/components/ExperimentalAppProtection/ExperimentalAppProtection.tsx similarity index 76% rename from packages/app/src/AppMode.tsx rename to plugins/auth-react/src/components/ExperimentalAppProtection/ExperimentalAppProtection.tsx index 2b11064c65..f76b287d97 100644 --- a/packages/app/src/AppMode.tsx +++ b/plugins/auth-react/src/components/ExperimentalAppProtection/ExperimentalAppProtection.tsx @@ -15,7 +15,6 @@ */ import React, { ReactNode } from 'react'; -import useAsync from 'react-use/lib/useAsync'; import { configApiRef, fetchApiRef, @@ -23,25 +22,34 @@ import { useApp, } from '@backstage/core-plugin-api'; import { CookieAuthRefreshProvider } from '@backstage/plugin-auth-react'; +import { useAsync, useMountEffect } from '@react-hookz/web'; -export function AppMode(props: { children: ReactNode }): JSX.Element { +/** + * @public + * A provider that will protect the app when running in public experimental mode. + */ +export function ExperimentalAppProtection(props: { + children: ReactNode; +}): JSX.Element { const { children } = props; const fetchApi = useApi(fetchApiRef); const configApi = useApi(configApiRef); const Components = useApp().getComponents(); - const { loading, error, value } = useAsync(async () => { + const [state, actions] = useAsync(async () => { const baseUrl = configApi.getString('backend.baseUrl'); const response = await fetchApi.fetch(`${baseUrl}/public/index.html`); return response.ok; - }, [fetchApi, configApi]); + }); - if (loading) { + useMountEffect(actions.execute); + + if (state.status === 'not-executed' || state.status === 'loading') { return ; } // Request failed, or the public index is not available - if (error || !value) { + if (state.status === 'error' || !state.result) { return <>{children}; } diff --git a/plugins/auth-react/src/components/ExperimentalAppProtection/index.ts b/plugins/auth-react/src/components/ExperimentalAppProtection/index.ts new file mode 100644 index 0000000000..c29b1e438d --- /dev/null +++ b/plugins/auth-react/src/components/ExperimentalAppProtection/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { ExperimentalAppProtection } from './ExperimentalAppProtection'; diff --git a/plugins/auth-react/src/components/index.ts b/plugins/auth-react/src/components/index.ts index c744c9b0d8..a85fad95ae 100644 --- a/plugins/auth-react/src/components/index.ts +++ b/plugins/auth-react/src/components/index.ts @@ -19,3 +19,4 @@ export * from './RedirectToRoot'; export * from './CookieAuthRefreshProvider'; +export * from './ExperimentalAppProtection'; diff --git a/yarn.lock b/yarn.lock index 2155432223..666548dd95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5119,6 +5119,7 @@ __metadata: "@testing-library/react": ^14.0.0 "@testing-library/user-event": ^14.0.0 "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 + msw: ^1.0.0 peerDependencies: react: ^16.13.1 || ^17.0.0 || ^18.0.0 languageName: unknown @@ -27526,7 +27527,6 @@ __metadata: "@vitejs/plugin-react": ^4.0.4 cross-env: ^7.0.0 history: ^5.0.0 - msw: ^1.0.0 react: ^18.0.2 react-dom: ^18.0.2 react-router: ^6.3.0