refactor: move refresh logic back to TechDocsReaderPage
Co-authored-by: Patrik Oldsberg <poldsberg@gmail.com> Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
@@ -31,8 +31,6 @@ import {
|
||||
} from '@backstage/plugin-techdocs-react';
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
|
||||
const EXPIRES_AT_STORAGE_KEY = 'backstage-techdocs-cookie-expires-at';
|
||||
|
||||
/**
|
||||
* API to talk to `techdocs-backend`.
|
||||
*
|
||||
@@ -42,10 +40,6 @@ export class TechDocsClient implements TechDocsApi {
|
||||
public configApi: Config;
|
||||
public discoveryApi: DiscoveryApi;
|
||||
private fetchApi: FetchApi;
|
||||
private locked = false;
|
||||
private readonly channel = new BroadcastChannel(
|
||||
'backstage-techdocs-cookie-refresh',
|
||||
);
|
||||
|
||||
constructor(options: {
|
||||
configApi: Config;
|
||||
@@ -55,23 +49,9 @@ export class TechDocsClient implements TechDocsApi {
|
||||
this.configApi = options.configApi;
|
||||
this.discoveryApi = options.discoveryApi;
|
||||
this.fetchApi = options.fetchApi;
|
||||
this.channel.onmessage = event => {
|
||||
const { action, payload } = event.data;
|
||||
if (action === 'REFRESH_COOKIE') {
|
||||
this.locked = payload.locked;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
set expiresAt(value: string) {
|
||||
localStorage.setItem(EXPIRES_AT_STORAGE_KEY, value);
|
||||
}
|
||||
|
||||
get expiresAt(): string | null {
|
||||
return localStorage.getItem(EXPIRES_AT_STORAGE_KEY);
|
||||
}
|
||||
|
||||
private async getCookie(): Promise<{ expiresAt: string }> {
|
||||
public async getCookie(): Promise<{ expiresAt: string }> {
|
||||
const apiOrigin = await this.getApiOrigin();
|
||||
const requestUrl = `${apiOrigin}/cookie`;
|
||||
const response = await this.fetchApi.fetch(`${requestUrl}`, {
|
||||
@@ -83,48 +63,6 @@ export class TechDocsClient implements TechDocsApi {
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
private refreshUserCookie(options: { expiresAt: string }) {
|
||||
const { expiresAt } = options;
|
||||
const tenMinutesInMilliseconds = 10 * 60000;
|
||||
const intervalId = setInterval(() => {
|
||||
if (this.locked) return;
|
||||
if (Date.parse(expiresAt) - Date.now() - tenMinutesInMilliseconds > 0)
|
||||
return;
|
||||
|
||||
this.channel.postMessage({
|
||||
action: 'REFRESH_COOKIE',
|
||||
payload: { locked: true },
|
||||
});
|
||||
|
||||
this.issueUserCookie({ force: true })
|
||||
.then(() => clearInterval(intervalId))
|
||||
.finally(() => {
|
||||
this.locked = false;
|
||||
this.channel.postMessage({
|
||||
action: 'REFRESH_COOKIE',
|
||||
payload: { locked: false },
|
||||
});
|
||||
});
|
||||
}, tenMinutesInMilliseconds);
|
||||
|
||||
return { expiresAt };
|
||||
}
|
||||
|
||||
public async issueUserCookie(
|
||||
options: {
|
||||
force?: boolean;
|
||||
} = {},
|
||||
): Promise<{ expiresAt: string }> {
|
||||
const { force } = options;
|
||||
if (force || !this.expiresAt || Date.parse(this.expiresAt) < Date.now()) {
|
||||
return this.getCookie().then(({ expiresAt }) => {
|
||||
this.expiresAt = expiresAt;
|
||||
return this.refreshUserCookie({ expiresAt });
|
||||
});
|
||||
}
|
||||
return this.refreshUserCookie({ expiresAt: this.expiresAt });
|
||||
}
|
||||
|
||||
async getApiOrigin(): Promise<string> {
|
||||
return await this.discoveryApi.getBaseUrl('techdocs');
|
||||
}
|
||||
|
||||
@@ -14,7 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactNode, Children, ReactElement } from 'react';
|
||||
import React, {
|
||||
ReactNode,
|
||||
Children,
|
||||
ReactElement,
|
||||
useEffect,
|
||||
useState,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
import { useOutlet } from 'react-router-dom';
|
||||
|
||||
import { ErrorPanel, Page } from '@backstage/core-components';
|
||||
@@ -38,7 +45,7 @@ import {
|
||||
useApp,
|
||||
useRouteRefParams,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { useAsync } from 'react-use';
|
||||
import { useAsyncRetry } from 'react-use';
|
||||
|
||||
/* An explanation for the multiple ways of customizing the TechDocs reader page
|
||||
|
||||
@@ -151,12 +158,41 @@ 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 } = useAsync(async () => {
|
||||
return await techdocsApi.issueUserCookie();
|
||||
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);
|
||||
channel.onmessage = event => {
|
||||
const { action, payload } = event.data;
|
||||
if (action === 'COOKIE_REFRESHED') {
|
||||
stopCookieRefresh();
|
||||
stopCookieRefresh = startCookieRefresh(payload.expiresAt);
|
||||
}
|
||||
};
|
||||
return () => {
|
||||
stopCookieRefresh();
|
||||
};
|
||||
}, [value, channel, startCookieRefresh]);
|
||||
|
||||
if (error) {
|
||||
return <ErrorPanel error={error} />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user