refactor: apply review suggestions
Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
@@ -3,27 +3,22 @@
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { ApiRef } from '@backstage/core-plugin-api';
|
||||
import { AsyncState } from '@react-hookz/web';
|
||||
import { default as React_2 } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { UseAsyncActions } from '@react-hookz/web';
|
||||
|
||||
// @public
|
||||
export type AuthApi = {
|
||||
getCookie(): Promise<{
|
||||
expiresAt: string;
|
||||
}>;
|
||||
export type CookieAuthRefreshOptions = {
|
||||
pluginId: string;
|
||||
path?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export function CookieAuthRefreshProvider<T extends AuthApi>({
|
||||
apiRef,
|
||||
export function CookieAuthRefreshProvider({
|
||||
children,
|
||||
}: {
|
||||
apiRef: ApiRef<T>;
|
||||
children: ReactNode;
|
||||
}):
|
||||
...rest
|
||||
}: CookieAuthRefreshProviderProps):
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
@@ -33,22 +28,16 @@ export function CookieAuthRefreshProvider<T extends AuthApi>({
|
||||
| undefined;
|
||||
|
||||
// @public
|
||||
export function useCookieAuthRefresh<T extends AuthApi>({
|
||||
apiRef,
|
||||
}: {
|
||||
apiRef: ApiRef<T>;
|
||||
}): {
|
||||
state: AsyncState<
|
||||
| {
|
||||
expiresAt: string;
|
||||
}
|
||||
| undefined
|
||||
>;
|
||||
actions: UseAsyncActions<
|
||||
{
|
||||
expiresAt: string;
|
||||
},
|
||||
[]
|
||||
>;
|
||||
export type CookieAuthRefreshProviderProps = CookieAuthRefreshOptions & {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
// @public
|
||||
export function useCookieAuthRefresh({
|
||||
pluginId,
|
||||
path,
|
||||
}: CookieAuthRefreshOptions): {
|
||||
state: AsyncState<any>;
|
||||
actions: UseAsyncActions<any, []>;
|
||||
};
|
||||
```
|
||||
|
||||
@@ -34,8 +34,10 @@
|
||||
"dependencies": {
|
||||
"@backstage/core-components": "workspace:^",
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@material-ui/core": "^4.9.13",
|
||||
"@react-hookz/web": "^24.0.4"
|
||||
"@react-hookz/web": "^24.0.4",
|
||||
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^",
|
||||
|
||||
+64
-29
@@ -19,10 +19,15 @@ import { screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { CookieAuthRefreshProvider } from './CookieAuthRefreshProvider';
|
||||
import { TestApiProvider, renderInTestApp } from '@backstage/test-utils';
|
||||
import { createApiRef } from '@backstage/core-plugin-api';
|
||||
import { AuthApi } from '../../types';
|
||||
import { discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
describe('CookieAuthRefreshProvider', () => {
|
||||
const discoveryApiMock = {
|
||||
getBaseUrl: jest
|
||||
.fn()
|
||||
.mockResolvedValue('http://localhost:7000/techdocs/api'),
|
||||
};
|
||||
|
||||
function getExpiresAtInFuture() {
|
||||
const tenMinutesInMilliseconds = 10 * 60 * 1000;
|
||||
return new Date(Date.now() + tenMinutesInMilliseconds).toISOString();
|
||||
@@ -35,14 +40,18 @@ describe('CookieAuthRefreshProvider', () => {
|
||||
}));
|
||||
|
||||
it('should render a progress bar', async () => {
|
||||
const apiRef = createApiRef<AuthApi>({ id: 'auth-test' });
|
||||
const apiMock = {
|
||||
getCookie: jest.fn().mockReturnValue(new Promise(() => {})),
|
||||
const fetchApiMock = {
|
||||
fetch: jest.fn().mockReturnValue(new Promise(() => {})),
|
||||
};
|
||||
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[apiRef, apiMock]]}>
|
||||
<CookieAuthRefreshProvider apiRef={apiRef}>
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[fetchApiRef, fetchApiMock],
|
||||
[discoveryApiRef, discoveryApiMock],
|
||||
]}
|
||||
>
|
||||
<CookieAuthRefreshProvider pluginId="techdocs">
|
||||
<div>Test Content</div>
|
||||
</CookieAuthRefreshProvider>
|
||||
</TestApiProvider>,
|
||||
@@ -51,16 +60,20 @@ describe('CookieAuthRefreshProvider', () => {
|
||||
expect(screen.getByTestId('progress')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render a error panel', async () => {
|
||||
const apiRef = createApiRef<AuthApi>({ id: 'auth-test' });
|
||||
it('should render an error panel', async () => {
|
||||
const error = new Error('Failed to get cookie');
|
||||
const apiMock = {
|
||||
getCookie: jest.fn().mockRejectedValue(error),
|
||||
const fetchApiMock = {
|
||||
fetch: jest.fn().mockRejectedValue(error),
|
||||
};
|
||||
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[apiRef, apiMock]]}>
|
||||
<CookieAuthRefreshProvider apiRef={apiRef}>
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[fetchApiRef, fetchApiMock],
|
||||
[discoveryApiRef, discoveryApiMock],
|
||||
]}
|
||||
>
|
||||
<CookieAuthRefreshProvider pluginId="techdocs">
|
||||
<div>Test Content</div>
|
||||
</CookieAuthRefreshProvider>
|
||||
</TestApiProvider>,
|
||||
@@ -70,29 +83,46 @@ describe('CookieAuthRefreshProvider', () => {
|
||||
});
|
||||
|
||||
it('should call the api again when retry is clicked', async () => {
|
||||
const apiRef = createApiRef<AuthApi>({ id: 'auth-test' });
|
||||
const error = new Error('Failed to get cookie');
|
||||
const apiMock = {
|
||||
getCookie: jest.fn().mockRejectedValueOnce(error).mockResolvedValue({
|
||||
expiresAt: getExpiresAtInFuture(),
|
||||
}),
|
||||
const fetchApiMock = {
|
||||
fetch: jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(error)
|
||||
.mockResolvedValue({
|
||||
ok: true,
|
||||
json: jest.fn().mockResolvedValue({
|
||||
expiresAt: getExpiresAtInFuture(),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[apiRef, apiMock]]}>
|
||||
<CookieAuthRefreshProvider apiRef={apiRef}>
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[fetchApiRef, fetchApiMock],
|
||||
[discoveryApiRef, discoveryApiMock],
|
||||
]}
|
||||
>
|
||||
<CookieAuthRefreshProvider pluginId="techdocs">
|
||||
<div>Test Content</div>
|
||||
</CookieAuthRefreshProvider>
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
expect(apiMock.getCookie).toHaveBeenCalledTimes(1);
|
||||
await waitFor(() =>
|
||||
expect(fetchApiMock.fetch).toHaveBeenCalledWith(
|
||||
'http://localhost:7000/techdocs/api/cookie',
|
||||
{ credentials: 'include' },
|
||||
),
|
||||
);
|
||||
|
||||
expect(fetchApiMock.fetch).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(screen.getByText(error.message)).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByText('Retry'));
|
||||
|
||||
expect(apiMock.getCookie).toHaveBeenCalledTimes(2);
|
||||
expect(fetchApiMock.fetch).toHaveBeenCalledTimes(2);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText('Test Content')).toBeInTheDocument(),
|
||||
@@ -100,18 +130,23 @@ describe('CookieAuthRefreshProvider', () => {
|
||||
});
|
||||
|
||||
it('should render the children', async () => {
|
||||
const apiRef = createApiRef<AuthApi>({ id: 'auth-test' });
|
||||
const apiMock = {
|
||||
getCookie: jest.fn().mockResolvedValue({
|
||||
expiresAt: {
|
||||
const fetchApiMock = {
|
||||
fetch: jest.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: jest.fn().mockResolvedValue({
|
||||
expiresAt: getExpiresAtInFuture(),
|
||||
},
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[apiRef, apiMock]]}>
|
||||
<CookieAuthRefreshProvider apiRef={apiRef}>
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[fetchApiRef, fetchApiMock],
|
||||
[discoveryApiRef, discoveryApiMock],
|
||||
]}
|
||||
>
|
||||
<CookieAuthRefreshProvider pluginId="techdocs">
|
||||
<div>Test Content</div>
|
||||
</CookieAuthRefreshProvider>
|
||||
</TestApiProvider>,
|
||||
|
||||
+15
-11
@@ -16,27 +16,31 @@
|
||||
|
||||
import React, { ReactNode } from 'react';
|
||||
import { ErrorPanel } from '@backstage/core-components';
|
||||
import { ApiRef, useApp } from '@backstage/core-plugin-api';
|
||||
import { useApp } from '@backstage/core-plugin-api';
|
||||
import { Button } from '@material-ui/core';
|
||||
import { useCookieAuthRefresh } from '../../hooks';
|
||||
import { AuthApi } from '../../types';
|
||||
import { useCookieAuthRefresh, CookieAuthRefreshOptions } from '../../hooks';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Props for the {@link CookieAuthRefreshProvider} component.
|
||||
*/
|
||||
export type CookieAuthRefreshProviderProps = CookieAuthRefreshOptions & {
|
||||
// The children to render when the refresh is successful
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
* A provider that will refresh the cookie when it is about to expire.
|
||||
* It receives an `apiRef` and `children` as props, and expects that apiRef extends the `AuthApi` interface.
|
||||
*/
|
||||
export function CookieAuthRefreshProvider<T extends AuthApi>({
|
||||
apiRef,
|
||||
export function CookieAuthRefreshProvider({
|
||||
children,
|
||||
}: {
|
||||
apiRef: ApiRef<T>;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
...rest
|
||||
}: CookieAuthRefreshProviderProps) {
|
||||
const app = useApp();
|
||||
const { Progress } = app.getComponents();
|
||||
|
||||
const { state, actions } = useCookieAuthRefresh({ apiRef });
|
||||
const { state, actions } = useCookieAuthRefresh(rest);
|
||||
|
||||
if (state.status === 'error' && state.error) {
|
||||
return (
|
||||
|
||||
@@ -14,4 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { CookieAuthRefreshProvider } from './CookieAuthRefreshProvider';
|
||||
export {
|
||||
CookieAuthRefreshProvider,
|
||||
type CookieAuthRefreshProviderProps,
|
||||
} from './CookieAuthRefreshProvider';
|
||||
|
||||
@@ -14,4 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { useCookieAuthRefresh } from './useCookieAuthRefresh';
|
||||
export {
|
||||
useCookieAuthRefresh,
|
||||
type CookieAuthRefreshOptions,
|
||||
} from './useCookieAuthRefresh';
|
||||
|
||||
@@ -16,17 +16,29 @@
|
||||
|
||||
import React from 'react';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { createApiRef } from '@backstage/core-plugin-api';
|
||||
import { fetchApiRef, discoveryApiRef } from '@backstage/core-plugin-api';
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
import { useCookieAuthRefresh } from './useCookieAuthRefresh';
|
||||
import { AuthApi } from '../../types';
|
||||
|
||||
describe('useCookieAuthRefresh', () => {
|
||||
const discoveryApiMock = {
|
||||
getBaseUrl: jest
|
||||
.fn()
|
||||
.mockResolvedValue('http://localhost:7000/techdocs/api'),
|
||||
};
|
||||
|
||||
const now = 1710316886171;
|
||||
const tenMinutesInMilliseconds = 10 * 60 * 1000;
|
||||
const tenMinutesFromNowInMilliseconds = now + tenMinutesInMilliseconds;
|
||||
const expiresAt = new Date(tenMinutesFromNowInMilliseconds).toISOString();
|
||||
|
||||
const fetchApiMock = {
|
||||
fetch: jest.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: jest.fn().mockResolvedValue({ expiresAt }),
|
||||
}),
|
||||
};
|
||||
|
||||
type Listener = (event: { data: any }) => void;
|
||||
|
||||
let listeners: Listener[];
|
||||
@@ -34,6 +46,7 @@ describe('useCookieAuthRefresh', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers({ now });
|
||||
jest.clearAllMocks();
|
||||
listeners = [];
|
||||
channelMock = {
|
||||
postMessage: jest.fn((message: any) => {
|
||||
@@ -58,32 +71,53 @@ describe('useCookieAuthRefresh', () => {
|
||||
});
|
||||
|
||||
it('should return a loading status when the refresh is in progress', () => {
|
||||
const apiRef = createApiRef<AuthApi>({ id: 'auth-test' });
|
||||
const apiMock = {
|
||||
getCookie: jest.fn(),
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useCookieAuthRefresh({ apiRef }), {
|
||||
wrapper: ({ children }) => (
|
||||
<TestApiProvider apis={[[apiRef, apiMock]]}>{children}</TestApiProvider>
|
||||
),
|
||||
});
|
||||
const { result } = renderHook(
|
||||
() => useCookieAuthRefresh({ pluginId: 'techdocs' }),
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[
|
||||
fetchApiRef,
|
||||
{
|
||||
fetch: jest.fn(),
|
||||
},
|
||||
],
|
||||
[discoveryApiRef, discoveryApiMock],
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</TestApiProvider>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.state.status).toBe('loading');
|
||||
});
|
||||
|
||||
it('should return an error status when the refresh has failed', async () => {
|
||||
const apiRef = createApiRef<AuthApi>({ id: 'auth-test' });
|
||||
const error = new Error('Failed to get cookie');
|
||||
const apiMock = {
|
||||
getCookie: jest.fn().mockRejectedValue(error),
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useCookieAuthRefresh({ apiRef }), {
|
||||
wrapper: ({ children }) => (
|
||||
<TestApiProvider apis={[[apiRef, apiMock]]}>{children}</TestApiProvider>
|
||||
),
|
||||
});
|
||||
const { result } = renderHook(
|
||||
() => useCookieAuthRefresh({ pluginId: 'techdocs' }),
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[
|
||||
fetchApiRef,
|
||||
{
|
||||
fetch: jest.fn().mockRejectedValue(error),
|
||||
},
|
||||
],
|
||||
[discoveryApiRef, discoveryApiMock],
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</TestApiProvider>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => expect(result.current.state.status).toBe('error'));
|
||||
|
||||
@@ -91,38 +125,48 @@ describe('useCookieAuthRefresh', () => {
|
||||
});
|
||||
|
||||
it('should call the api to get the cookie and use it', async () => {
|
||||
const apiRef = createApiRef<AuthApi>({ id: 'auth-test' });
|
||||
const apiMock = {
|
||||
getCookie: jest.fn().mockResolvedValue({ expiresAt }),
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useCookieAuthRefresh({ apiRef }), {
|
||||
wrapper: ({ children }) => (
|
||||
<TestApiProvider apis={[[apiRef, apiMock]]}>{children}</TestApiProvider>
|
||||
),
|
||||
});
|
||||
|
||||
expect(apiMock.getCookie).toHaveBeenCalled();
|
||||
const { result } = renderHook(
|
||||
() => useCookieAuthRefresh({ pluginId: 'techdocs' }),
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[fetchApiRef, fetchApiMock],
|
||||
[discoveryApiRef, discoveryApiMock],
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</TestApiProvider>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(result.current.state.result).toMatchObject({ expiresAt }),
|
||||
expect(fetchApiMock.fetch).toHaveBeenCalledWith(
|
||||
'http://localhost:7000/techdocs/api/cookie',
|
||||
{ credentials: 'include' },
|
||||
),
|
||||
);
|
||||
|
||||
expect(result.current.state.result).toMatchObject({ expiresAt });
|
||||
});
|
||||
|
||||
it('should send a message to other tabs when the cookie is refreshed', async () => {
|
||||
const apiRef = createApiRef<AuthApi>({ id: 'auth-test' });
|
||||
const apiMock = {
|
||||
getCookie: jest.fn().mockResolvedValue({ expiresAt }),
|
||||
};
|
||||
|
||||
renderHook(() => useCookieAuthRefresh({ apiRef }), {
|
||||
renderHook(() => useCookieAuthRefresh({ pluginId: 'techdocs' }), {
|
||||
wrapper: ({ children }) => (
|
||||
<TestApiProvider apis={[[apiRef, apiMock]]}>{children}</TestApiProvider>
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[fetchApiRef, fetchApiMock],
|
||||
[discoveryApiRef, discoveryApiMock],
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</TestApiProvider>
|
||||
),
|
||||
});
|
||||
|
||||
expect(global.BroadcastChannel).toHaveBeenCalledWith(
|
||||
'auth-test-auth-cookie-channel',
|
||||
'techdocs-auth-cookie-channel',
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
@@ -139,14 +183,16 @@ describe('useCookieAuthRefresh', () => {
|
||||
});
|
||||
|
||||
it('should cancel the refresh when a message is received from another tab', async () => {
|
||||
const apiRef = createApiRef<AuthApi>({ id: 'auth-test' });
|
||||
const apiMock = {
|
||||
getCookie: jest.fn().mockResolvedValue({ expiresAt }),
|
||||
};
|
||||
|
||||
renderHook(() => useCookieAuthRefresh({ apiRef }), {
|
||||
renderHook(() => useCookieAuthRefresh({ pluginId: 'techdocs' }), {
|
||||
wrapper: ({ children }) => (
|
||||
<TestApiProvider apis={[[apiRef, apiMock]]}>{children}</TestApiProvider>
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[fetchApiRef, fetchApiMock],
|
||||
[discoveryApiRef, discoveryApiMock],
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</TestApiProvider>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -169,37 +215,35 @@ describe('useCookieAuthRefresh', () => {
|
||||
jest.advanceTimersByTime(tenMinutesInMilliseconds);
|
||||
|
||||
// should not call the api
|
||||
expect(apiMock.getCookie).toHaveBeenCalledTimes(1);
|
||||
expect(fetchApiMock.fetch).toHaveBeenCalledTimes(1);
|
||||
|
||||
// advance the timers in more 10 minutes to match the new expires at
|
||||
jest.advanceTimersByTime(tenMinutesInMilliseconds);
|
||||
|
||||
// should call the api
|
||||
await waitFor(() => expect(apiMock.getCookie).toHaveBeenCalledTimes(2));
|
||||
await waitFor(() => expect(fetchApiMock.fetch).toHaveBeenCalledTimes(2));
|
||||
});
|
||||
|
||||
it('should cancel the refresh when the component is unmounted', async () => {
|
||||
const apiRef = createApiRef<AuthApi>({ id: 'auth-test' });
|
||||
const apiMock = {
|
||||
getCookie: jest.fn().mockResolvedValue({ expiresAt }),
|
||||
};
|
||||
|
||||
const { result, unmount } = renderHook(
|
||||
() => useCookieAuthRefresh({ apiRef }),
|
||||
() => useCookieAuthRefresh({ pluginId: 'techdocs' }),
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<TestApiProvider apis={[[apiRef, apiMock]]}>
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[fetchApiRef, fetchApiMock],
|
||||
[discoveryApiRef, discoveryApiMock],
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</TestApiProvider>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
expect(apiMock.getCookie).toHaveBeenCalledTimes(1);
|
||||
await waitFor(() => expect(fetchApiMock.fetch).toHaveBeenCalledTimes(1));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(result.current.state.result).toMatchObject({ expiresAt }),
|
||||
);
|
||||
expect(result.current.state.result).toMatchObject({ expiresAt });
|
||||
|
||||
unmount();
|
||||
|
||||
@@ -213,27 +257,31 @@ describe('useCookieAuthRefresh', () => {
|
||||
jest.advanceTimersByTime(tenMinutesInMilliseconds);
|
||||
|
||||
// should not call the api after unmount
|
||||
await waitFor(() => expect(apiMock.getCookie).not.toHaveBeenCalledTimes(2));
|
||||
await waitFor(() =>
|
||||
expect(fetchApiMock.fetch).not.toHaveBeenCalledTimes(2),
|
||||
);
|
||||
});
|
||||
|
||||
it('should refresh the cookie when it is about to expire', async () => {
|
||||
const apiRef = createApiRef<AuthApi>({ id: 'auth-test' });
|
||||
const apiMock = {
|
||||
getCookie: jest.fn().mockResolvedValue({ expiresAt }),
|
||||
};
|
||||
|
||||
renderHook(() => useCookieAuthRefresh({ apiRef }), {
|
||||
renderHook(() => useCookieAuthRefresh({ pluginId: 'techdocs' }), {
|
||||
wrapper: ({ children }) => (
|
||||
<TestApiProvider apis={[[apiRef, apiMock]]}>{children}</TestApiProvider>
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[fetchApiRef, fetchApiMock],
|
||||
[discoveryApiRef, discoveryApiMock],
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</TestApiProvider>
|
||||
),
|
||||
});
|
||||
|
||||
expect(apiMock.getCookie).toHaveBeenCalledTimes(1);
|
||||
await waitFor(() => expect(fetchApiMock.fetch).toHaveBeenCalledTimes(1));
|
||||
|
||||
// advance the timers to the expiration date
|
||||
jest.advanceTimersByTime(tenMinutesInMilliseconds);
|
||||
|
||||
// should call the api
|
||||
await waitFor(() => expect(apiMock.getCookie).toHaveBeenCalledTimes(2));
|
||||
await waitFor(() => expect(fetchApiMock.fetch).toHaveBeenCalledTimes(2));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,9 +15,13 @@
|
||||
*/
|
||||
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { ApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
useApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { useAsync, useMountEffect } from '@react-hookz/web';
|
||||
import { AuthApi } from '../../types';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
|
||||
type CookieAuthRefreshMessage = MessageEvent<{
|
||||
action: string;
|
||||
@@ -28,22 +32,43 @@ type CookieAuthRefreshMessage = MessageEvent<{
|
||||
|
||||
/**
|
||||
* @public
|
||||
* It receives an `apiRef` as options, and expects that apiRef extends the `AuthApi` interface.
|
||||
* The options for the {@link useCookieAuthRefresh} hook.
|
||||
*/
|
||||
export type CookieAuthRefreshOptions = {
|
||||
// The plugin ID to used for discovering the API origin
|
||||
pluginId: string;
|
||||
// The path to used for calling the refresh cookie endpoint, default to '/cookie'
|
||||
path?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
* A hook that will refresh the cookie when it is about to expire.
|
||||
* @remarks
|
||||
* This hook expects a `BroadcastChannel` to be available in the global scope.
|
||||
*/
|
||||
export function useCookieAuthRefresh<T extends AuthApi>({
|
||||
apiRef,
|
||||
}: {
|
||||
apiRef: ApiRef<T>;
|
||||
}) {
|
||||
const api = useApi(apiRef);
|
||||
export function useCookieAuthRefresh({
|
||||
pluginId,
|
||||
path = '/cookie',
|
||||
}: CookieAuthRefreshOptions) {
|
||||
const fetchApi = useApi(fetchApiRef);
|
||||
const discoveryApi = useApi(discoveryApiRef);
|
||||
|
||||
const [channel] = useState(
|
||||
() => new BroadcastChannel(`${apiRef.id}-auth-cookie-channel`),
|
||||
() => new BroadcastChannel(`${pluginId}-auth-cookie-channel`),
|
||||
);
|
||||
|
||||
const [state, actions] = useAsync(async () => await api.getCookie());
|
||||
const [state, actions] = useAsync(async () => {
|
||||
const apiOrigin = await discoveryApi.getBaseUrl(pluginId);
|
||||
const requestUrl = `${apiOrigin}${path}`;
|
||||
const response = await fetchApi.fetch(`${requestUrl}`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
return await response.json();
|
||||
});
|
||||
|
||||
useMountEffect(actions.execute);
|
||||
|
||||
|
||||
@@ -25,4 +25,3 @@
|
||||
|
||||
export * from './components';
|
||||
export * from './hooks';
|
||||
export * from './types';
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Defines a minimal inteface for auth apis.
|
||||
*/
|
||||
export type AuthApi = { getCookie(): Promise<{ expiresAt: string }> };
|
||||
Reference in New Issue
Block a user