refactor: use cross tab timeout
Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
@@ -31,6 +31,8 @@ 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`.
|
||||
*
|
||||
@@ -40,7 +42,10 @@ export class TechDocsClient implements TechDocsApi {
|
||||
public configApi: Config;
|
||||
public discoveryApi: DiscoveryApi;
|
||||
private fetchApi: FetchApi;
|
||||
private cookieRefreshTimeoutId: NodeJS.Timeout | undefined;
|
||||
private locked = false;
|
||||
private readonly channel = new BroadcastChannel(
|
||||
'backstage-techdocs-cookie-refresh',
|
||||
);
|
||||
|
||||
constructor(options: {
|
||||
configApi: Config;
|
||||
@@ -50,6 +55,20 @@ 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 }> {
|
||||
@@ -64,42 +83,46 @@ export class TechDocsClient implements TechDocsApi {
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
public async issueUserCookie(): Promise<{ expiresAt: string }> {
|
||||
const expiresAtStorageKey = 'backstage-auth-cookie-last-refresh-expires-at';
|
||||
const formatedExpiresAt = localStorage.getItem(expiresAtStorageKey);
|
||||
const expiresAt = formatedExpiresAt ? new Date(formatedExpiresAt) : null;
|
||||
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;
|
||||
|
||||
// get a new cookie if it doesn't exist or has expired
|
||||
if (
|
||||
// first time issuing a cookie or user deleted the stored expiration date
|
||||
!expiresAt ||
|
||||
// the application was reloaded and the cookie was not refreshed
|
||||
expiresAt.getTime() < Date.now()
|
||||
) {
|
||||
return this.getCookie().then(response => {
|
||||
const newExpiresAt = new Date(response.expiresAt);
|
||||
// refresh the cookie 5 minutes before it expires
|
||||
newExpiresAt.setMinutes(newExpiresAt.getMinutes() - 5);
|
||||
// store the expiration date to keep it even if the application is reloaded
|
||||
localStorage.setItem(expiresAtStorageKey, newExpiresAt.toISOString());
|
||||
// maintain the timeout id per tab instance so we know that there is a timeout running
|
||||
this.cookieRefreshTimeoutId = setTimeout(
|
||||
this.issueUserCookie,
|
||||
newExpiresAt.getTime(),
|
||||
);
|
||||
return { expiresAt: newExpiresAt.toISOString() };
|
||||
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 });
|
||||
});
|
||||
}
|
||||
|
||||
// restart timeout when the cookie is still valid but the application was reloaded
|
||||
if (!this.cookieRefreshTimeoutId) {
|
||||
this.cookieRefreshTimeoutId = setTimeout(
|
||||
this.issueUserCookie,
|
||||
expiresAt.getTime(),
|
||||
);
|
||||
}
|
||||
|
||||
return { expiresAt: expiresAt.toISOString() };
|
||||
return this.refreshUserCookie({ expiresAt: this.expiresAt });
|
||||
}
|
||||
|
||||
async getApiOrigin(): Promise<string> {
|
||||
|
||||
Reference in New Issue
Block a user