UNPKG

voluptasmollitia

Version:
104 lines (88 loc) 2.76 kB
/** * @license * Copyright 2020 Google LLC * * 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 { UserInternal } from '../../model/user'; import { AuthErrorCode } from '../errors'; // Refresh the token five minutes before expiration export const enum Duration { OFFSET = 5 * 1000 * 60, RETRY_BACKOFF_MIN = 30 * 1000, RETRY_BACKOFF_MAX = 16 * 60 * 1000 } export class ProactiveRefresh { private isRunning = false; // Node timers and browser timers return fundamentally different types. // We don't actually care what the value is but TS won't accept unknown and // we can't cast properly in both environments. // eslint-disable-next-line @typescript-eslint/no-explicit-any private timerId: any | null = null; private errorBackoff = Duration.RETRY_BACKOFF_MIN; constructor(private readonly user: UserInternal) {} _start(): void { if (this.isRunning) { return; } this.isRunning = true; this.schedule(); } _stop(): void { if (!this.isRunning) { return; } this.isRunning = false; if (this.timerId !== null) { clearTimeout(this.timerId); } } private getInterval(wasError: boolean): number { if (wasError) { const interval = this.errorBackoff; this.errorBackoff = Math.min( this.errorBackoff * 2, Duration.RETRY_BACKOFF_MAX ); return interval; } else { // Reset the error backoff this.errorBackoff = Duration.RETRY_BACKOFF_MIN; const expTime = this.user.stsTokenManager.expirationTime ?? 0; const interval = expTime - Date.now() - Duration.OFFSET; return Math.max(0, interval); } } private schedule(wasError = false): void { if (!this.isRunning) { // Just in case... return; } const interval = this.getInterval(wasError); this.timerId = setTimeout(async () => { await this.iteration(); }, interval); } private async iteration(): Promise<void> { try { await this.user.getIdToken(true); } catch (e) { // Only retry on network errors if (e.code === `auth/${AuthErrorCode.NETWORK_REQUEST_FAILED}`) { this.schedule(/* wasError */ true); } return; } this.schedule(); } }