From 589f2f04baccb35a1749f78e03a29e44df458e26 Mon Sep 17 00:00:00 2001 From: Wojciech Sikora Date: Wed, 21 Aug 2024 15:29:54 +0200 Subject: [PATCH 1/2] feat: add refreshTokenHandler option --- .changeset/wise-spoons-draw.md | 42 +++++++++++++++++ .../2.getting-started/2.middleware-module.md | 46 +++++++++++++++++-- .../sdk/src/modules/middlewareModule/types.ts | 25 ++++------ .../utils/getRequestSender.ts | 36 +++++++++++---- 4 files changed, 120 insertions(+), 29 deletions(-) create mode 100644 .changeset/wise-spoons-draw.md diff --git a/.changeset/wise-spoons-draw.md b/.changeset/wise-spoons-draw.md new file mode 100644 index 0000000000..eccf6ef11f --- /dev/null +++ b/.changeset/wise-spoons-draw.md @@ -0,0 +1,42 @@ +--- +"@vue-storefront/sdk": minor +--- + +[ADDED] new option to the `middlewareModule` - `refreshTokenHandler`. +This special handler can be used to handle 401 errors and refresh the token. +It is called before the generic `errorHandler`. +By default, it thrown an error which is being caught by the `errorHandler` and rethrown. + +Example: + +```ts +import { SdkHttpError } from "@vue-storefront/sdk"; + +const refereshToken = async () => { + // Refresh the token +}; + +const options: Options = { + apiUrl: "https://api.example.com", + refreshTokenHandler: async ({ + error, + methodName, + url, + params, + config, + httpClient, + }) => { + try { + await refreshToken(); + } catch (error) { + throw new SdkHttpError({ + statusCode: 401, + message: "Unauthorized", + cause: error, + }); + } + + throw error; + }, +}; +``` diff --git a/docs/content/4.sdk/2.getting-started/2.middleware-module.md b/docs/content/4.sdk/2.getting-started/2.middleware-module.md index 9179a46319..982c71cad9 100644 --- a/docs/content/4.sdk/2.getting-started/2.middleware-module.md +++ b/docs/content/4.sdk/2.getting-started/2.middleware-module.md @@ -294,7 +294,6 @@ You can add a custom error handler by passing it in the `errorHandler` option of ```ts import { SdkHttpError } from "@vue-storefront/sdk"; -import { refreshToken } from "./handlers/refreshToken"; // Custom implementation of the refresh token logic const options: Options = { apiUrl: "https://api.example.com", @@ -306,11 +305,48 @@ const options: Options = { config, httpClient, }) => { - if (error.status === 401 && methodName !== "login") { - // Refresh the token + const msg = `A request to ${url} with params ${JSON.stringify(params)} failed with an error: ${error.message}`; + + console.error(msg); + + throw new SdkHttpError({ + statusCode: error.statusCode || 500, + message: msg, + cause: error, + }); + }, +}; +``` + +### Add a custom refresh token handler + +You can add a custom refresh token handler by passing it in the `refreshTokenHandler` option of the `middlewareModule` function. + +```ts +import { SdkHttpError } from "@vue-storefront/sdk"; + +const refereshToken = async () => { + // Refresh the token +}; + +const options: Options = { + apiUrl: "https://api.example.com", + refreshTokenHandler: async ({ + error, + methodName, + url, + params, + config, + httpClient, + }) => { + try { await refreshToken(); - // Retry the request - return httpClient(url, params, config); + } catch (error) { + throw new SdkHttpError({ + statusCode: 401, + message: "Unauthorized", + cause: error, + }); } throw error; diff --git a/packages/sdk/src/modules/middlewareModule/types.ts b/packages/sdk/src/modules/middlewareModule/types.ts index 1a832db183..6b03f1266c 100644 --- a/packages/sdk/src/modules/middlewareModule/types.ts +++ b/packages/sdk/src/modules/middlewareModule/types.ts @@ -240,25 +240,18 @@ export type Options< * If not provided, errors will be thrown as is. * * This enables custom error handling, like retrying the request or refreshing tokens, depending on the error type and details of the request that failed. + */ + errorHandler?: ErrorHandler; + + /** + * Optional error handler for refreshing tokens. * - * @example - * ```typescript - * const options: Options = { - * apiUrl: "https://api.example.com", - * errorHandler: async ({ error, methodName, url, params, config, httpClient }) => { - * if (error.status === 401 && methodName !== "login") { - * // Refresh token - * await refreshToken(); - * // Retry the request - * return httpClient(url, params, config); - * } + * @remarks * - * throw error; - * }, - * }; - * ``` + * The refresh token handler is a specific error handler that is triggered when the request fails due to 401 Unauthorized error. + * It is going to be triggered before the `errorHandler`, which is more generic. */ - errorHandler?: ErrorHandler; + refreshTokenHandler?: ErrorHandler; /** * Unique identifier for CDN cache busting. diff --git a/packages/sdk/src/modules/middlewareModule/utils/getRequestSender.ts b/packages/sdk/src/modules/middlewareModule/utils/getRequestSender.ts index 659e232b50..e6b1b7d154 100644 --- a/packages/sdk/src/modules/middlewareModule/utils/getRequestSender.ts +++ b/packages/sdk/src/modules/middlewareModule/utils/getRequestSender.ts @@ -6,6 +6,7 @@ import { HTTPClient, ErrorHandler, RequestSender, + ErrorHandlerContext, } from "../types"; import { SdkHttpError } from "./SdkHttpError"; @@ -109,10 +110,15 @@ export const getRequestSender = (options: Options): RequestSender => { throw error; }; + const defaultRefreshHandler: ErrorHandler = async ({ error }) => { + throw error; + }; + return async (methodName, params, config?) => { const { httpClient = defaultHTTPClient, errorHandler = defaultErrorHandler, + refreshTokenHandler = defaultRefreshHandler, } = options; const { method, headers = {}, ...restConfig } = config ?? {}; const methodConfig = methodsRequestConfig[methodName] || {}; @@ -128,14 +134,28 @@ export const getRequestSender = (options: Options): RequestSender => { try { return await httpClient(finalUrl, finalParams, finalConfig); } catch (error) { - return await errorHandler({ - error, - methodName, - url: finalUrl, - params: finalParams, - config: finalConfig, - httpClient, - }); + try { + const handlerContext: ErrorHandlerContext = { + error, + methodName, + url: finalUrl, + params: finalParams, + config: finalConfig, + httpClient, + }; + return await refreshTokenHandler(handlerContext); + } catch (err) { + const handlerContext: ErrorHandlerContext = { + error: err, + methodName, + url: finalUrl, + params: finalParams, + config: finalConfig, + httpClient, + }; + + return await errorHandler(handlerContext); + } } }; }; From 041e1d628939253d0fbfc4e65b0d6a64703ea1ff Mon Sep 17 00:00:00 2001 From: Wojciech Sikora Date: Wed, 21 Aug 2024 15:31:53 +0200 Subject: [PATCH 2/2] refactor error names --- .../src/modules/middlewareModule/utils/getRequestSender.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sdk/src/modules/middlewareModule/utils/getRequestSender.ts b/packages/sdk/src/modules/middlewareModule/utils/getRequestSender.ts index e6b1b7d154..797181bd72 100644 --- a/packages/sdk/src/modules/middlewareModule/utils/getRequestSender.ts +++ b/packages/sdk/src/modules/middlewareModule/utils/getRequestSender.ts @@ -144,9 +144,9 @@ export const getRequestSender = (options: Options): RequestSender => { httpClient, }; return await refreshTokenHandler(handlerContext); - } catch (err) { + } catch (innerError) { const handlerContext: ErrorHandlerContext = { - error: err, + error: innerError, methodName, url: finalUrl, params: finalParams,