From 62b4e58375f6e8ec7bc08bcb311f08c0f472d3ba Mon Sep 17 00:00:00 2001 From: paras-fundwave <85985638+paras-fundwave@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:16:54 +0530 Subject: [PATCH] feat: maintain reference to refresh-promise to avoid repetitive calls (#7) * feat: maintain reference to refresh-promise to avoid repetitive calls when a promise exists note: refresh calls were made even if the tokens were updated recently and lock was released recently * update: use prod-env for releases on `main` branch * update: mark `refreshTokenPromise` as private * chore: break out of wait-loop @ getAccessToken --------- Co-authored-by: paras-verma Co-authored-by: github-actions[bot] --- .github/workflows/npm-publish.yml | 4 +- package-lock.json | 4 +- package.json | 2 +- src/index.js | 62 +++++++++++++++---------------- 4 files changed, 35 insertions(+), 37 deletions(-) diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 32402df..abd68e7 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -67,7 +67,7 @@ jobs: publish-npmjs: needs: release - environment: prod + environment: ${{ github.ref_name == 'main' && 'prod' || '' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -85,7 +85,7 @@ jobs: publish-gitlab: needs: release - environment: prod + environment: ${{ github.ref_name == 'main' && 'prod' || '' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/package-lock.json b/package-lock.json index 0ef7c44..1a173f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@fundwave/oidc-client", - "version": "2.0.0", + "version": "2.0.1-retain-refresh-promise-reference.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@fundwave/oidc-client", - "version": "2.0.0", + "version": "2.0.1-retain-refresh-promise-reference.2", "license": "MIT", "dependencies": { "jwt-decode": "^3.1.2" diff --git a/package.json b/package.json index efa85c9..915eb16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fundwave/oidc-client", - "version": "2.0.0", + "version": "2.0.1-retain-refresh-promise-reference.2", "description": "JS client for making server calls for an OIDC flow", "license": "MIT", "author": "The Fundwave Authors", diff --git a/src/index.js b/src/index.js index 3f8413c..b876c3c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,8 @@ import jwt_decode from "jwt-decode"; export class OIDCClient { + #refreshTokenPromise; + constructor(options) { this.refreshTokenLock = false; this.refreshPath = options?.refreshPath || "token/refresh"; @@ -40,13 +42,13 @@ export class OIDCClient { this.refreshTokenLock = false; } - async prepareHeaders(HEADERS) { - if (!HEADERS) HEADERS = this.BASE_HEADERS; + async prepareHeaders(headers) { + if (!headers) headers = this.BASE_HEADERS; const token = await this.getAccessToken(); - if (token) return { ...HEADERS, Authorization: `Bearer ${token}` }; - else return HEADERS; + if (token) return { ...headers, Authorization: `Bearer ${token}` }; + return headers; } async getAccessToken() { @@ -55,13 +57,13 @@ export class OIDCClient { return; try { - let count = 0; - while (this.refreshTokenLock && count < 15) { - await this._wait((count > 0) ? (200 * count) : undefined); //delays the next check of refreshTokenLock - count += 1; + for (let count = 0; this.refreshTokenLock && count < 15; count++) { + if (this.#refreshTokenPromise) break; + await this._wait((count) ? (200 * count) : undefined); //delays the next check of refreshTokenLock } - if (!this.verifyTokenValidity()) await this._refreshToken(); + if (this.#refreshTokenPromise) await this.#refreshTokenPromise; + else if (!this.verifyTokenValidity()) await this._refreshToken(); } catch (err) { console.log(err); sessionStorage.removeItem("token"); @@ -92,11 +94,9 @@ export class OIDCClient { } async _refreshToken() { - const headers = { ...this.BASE_HEADERS }; - - const refreshToken = localStorage.getItem("refreshToken"); - const token = sessionStorage.getItem("token"); + const refreshToken = localStorage.getItem("refreshToken"); + const headers = { ...this.BASE_HEADERS }; if (!refreshToken) throw "No refresh token"; @@ -105,35 +105,33 @@ export class OIDCClient { if (token) headers["Authorization"] = `Bearer ${token}`; headers["Refresh-Token"] = refreshToken; - let base = this.getBaseUrl(); + const base = this.getBaseUrl(); if (!base) throw new Error("Missing `baseUrl` argument for OIDCClient"); - if (!base.endsWith("/")) base = `${base}/`; - - let refreshPath = this.getRefreshPath(); - if (refreshPath.startsWith("/")) refreshPath = refreshPath.slice(1); - const serviceUrl = `${base}${refreshPath}`; + const refreshPath = this.getRefreshPath(); + const serviceUrl = base.replace(/\/?$/, "/").concat(refreshPath.replace(/^\/?/, "")); - return fetch(serviceUrl, { method: "GET", headers: headers }) + this.#refreshTokenPromise = fetch(serviceUrl, { method: "GET", headers: headers }) .then(async (response) => { - if (response.status === 403) { - throw 403; - } else { - const data = await response.json(); - const token = data?.["token"] || response.headers.get("token"); - const refreshToken = data?.["refreshToken"] || response.headers.get("refreshToken"); - - if (!token && !refreshToken) throw new Error("Couldn't fetch `access-token` or `refresh-token`"); - if (token) sessionStorage.setItem("token", token); - if (refreshToken) localStorage.setItem("refreshToken", refreshToken); - } + if (response.status === 403) throw 403; + + const data = await response.json(); + const token = data?.["token"] || response.headers.get("token"); + const refreshToken = data?.["refreshToken"] || response.headers.get("refreshToken"); + + if (!token && !refreshToken) throw new Error("Couldn't fetch `access-token` or `refresh-token`"); + if (token) sessionStorage.setItem("token", token); + if (refreshToken) localStorage.setItem("refreshToken", refreshToken); }) .catch((err) => { - console.log(err); + console.log("Failed to refresh tokens", err); throw err; }) .finally(() => { this.releaseRefreshTokenLock(); + this.#refreshTokenPromise = null; }); + + return this.#refreshTokenPromise; } }