diff --git a/.changeset/tidy-bears-swim.md b/.changeset/tidy-bears-swim.md new file mode 100644 index 000000000..e27b308fb --- /dev/null +++ b/.changeset/tidy-bears-swim.md @@ -0,0 +1,5 @@ +--- +'@commercetools/ts-client': patch +--- + +Fix concurrency issues in TypeScript v3 SDK diff --git a/packages/sdk-client-v3/src/client/client.ts b/packages/sdk-client-v3/src/client/client.ts index 43d181a8c..53411df87 100644 --- a/packages/sdk-client-v3/src/client/client.ts +++ b/packages/sdk-client-v3/src/client/client.ts @@ -166,18 +166,19 @@ export default function createClient(middlewares: ClientOptions): Client { process, execute(request: ClientRequest): Promise { validate('exec', request) - return new Promise((resolve, reject) => { - dispatch({ reject, resolve, ...request }) - .then((res) => { - if (res.error) return reject(res.error) - - if (res.originalRequest && _maskSensitiveHeaderData) { - res.originalRequest = maskAuthData(res.originalRequest) - } - - resolve(res) - }) - .catch(reject) + return new Promise(async (resolve, reject) => { + try { + const response = await dispatch({ reject, resolve, ...request }) + if (response.error) return reject(response.error) + + if (response.originalRequest && _maskSensitiveHeaderData) { + response.originalRequest = maskAuthData(response.originalRequest) + } + + resolve(response) + } catch (err) { + reject(err) + } }) }, } diff --git a/packages/sdk-client-v3/src/middleware/auth-middleware/anonymous-session-flow.ts b/packages/sdk-client-v3/src/middleware/auth-middleware/anonymous-session-flow.ts index d1dacffed..0daae15c2 100644 --- a/packages/sdk-client-v3/src/middleware/auth-middleware/anonymous-session-flow.ts +++ b/packages/sdk-client-v3/src/middleware/auth-middleware/anonymous-session-flow.ts @@ -1,12 +1,12 @@ +import { Mutex } from 'async-mutex' import fetch from 'node-fetch' import { AuthMiddlewareOptions, + ClientRequest, Middleware, MiddlewareRequest, MiddlewareResponse, Next, - RequestState, - RequestStateStore, Task, TokenCache, TokenStore, @@ -19,7 +19,7 @@ export default function createAuthMiddlewareForAnonymousSessionFlow( options: AuthMiddlewareOptions ): Middleware { const pendingTasks: Array = [] - const requestState = store(false) + const requestState = new Mutex() const tokenCache = options.tokenCache || store({ @@ -54,7 +54,14 @@ export default function createAuthMiddlewareForAnonymousSessionFlow( } // make request to coco - const requestWithAuth = await executeRequest(requestOptions) + let requestWithAuth: ClientRequest + + try { + await requestState.acquire() + requestWithAuth = await executeRequest(requestOptions) + } finally { + requestState.release() + } if (requestWithAuth) { return next(requestWithAuth) diff --git a/packages/sdk-client-v3/src/middleware/auth-middleware/password-flow.ts b/packages/sdk-client-v3/src/middleware/auth-middleware/password-flow.ts index 6a11b224c..bfd56d214 100644 --- a/packages/sdk-client-v3/src/middleware/auth-middleware/password-flow.ts +++ b/packages/sdk-client-v3/src/middleware/auth-middleware/password-flow.ts @@ -1,12 +1,12 @@ +import { Mutex } from 'async-mutex' import fetch from 'node-fetch' import { + ClientRequest, Middleware, MiddlewareRequest, MiddlewareResponse, Next, PasswordAuthMiddlewareOptions, - RequestState, - RequestStateStore, Task, } from '../../types/types' import { buildTokenCacheKey, store } from '../../utils' @@ -24,7 +24,7 @@ export default function createAuthMiddlewareForPasswordFlow( }) const pendingTasks: Array = [] - const requestState = store(false) + const requestState = new Mutex() const tokenCacheKey = buildTokenCacheKey(options) @@ -50,7 +50,15 @@ export default function createAuthMiddlewareForPasswordFlow( } // make request to coco - const requestWithAuth = await executeRequest(requestOptions) + let requestWithAuth: ClientRequest + + try { + await requestState.acquire() + requestWithAuth = await executeRequest(requestOptions) + } finally { + requestState.release() + } + if (requestWithAuth) { return next(requestWithAuth) } diff --git a/packages/sdk-client-v3/src/middleware/auth-middleware/refresh-token-flow.ts b/packages/sdk-client-v3/src/middleware/auth-middleware/refresh-token-flow.ts index cde4182e2..7208507f9 100644 --- a/packages/sdk-client-v3/src/middleware/auth-middleware/refresh-token-flow.ts +++ b/packages/sdk-client-v3/src/middleware/auth-middleware/refresh-token-flow.ts @@ -1,11 +1,11 @@ +import { Mutex } from 'async-mutex' import { + ClientRequest, Middleware, MiddlewareRequest, MiddlewareResponse, Next, RefreshAuthMiddlewareOptions, - RequestState, - RequestStateStore, Task, } from '../../types/types' import { store } from '../../utils' @@ -23,7 +23,7 @@ export default function createAuthMiddlewareForRefreshTokenFlow( }) const pendingTasks: Array = [] - const requestState = store(false) + const requestState = new Mutex() return (next: Next) => { return async (request: MiddlewareRequest): Promise => { @@ -46,7 +46,15 @@ export default function createAuthMiddlewareForRefreshTokenFlow( } // make request to coco - const requestWithAuth = await executeRequest(requestOptions) + let requestWithAuth: ClientRequest + + try { + await requestState.acquire() + requestWithAuth = await executeRequest(requestOptions) + } finally { + requestState.release() + } + if (requestWithAuth) { return next(requestWithAuth) } diff --git a/packages/sdk-client-v3/src/types/types.d.ts b/packages/sdk-client-v3/src/types/types.d.ts index 07e611b3b..7a845349e 100644 --- a/packages/sdk-client-v3/src/types/types.d.ts +++ b/packages/sdk-client-v3/src/types/types.d.ts @@ -142,8 +142,6 @@ export type RefreshAuthMiddlewareOptions = { httpClient?: Function } -export type RequestStateStore = any - /* Request */ type requestBaseOptions = { url: string @@ -151,7 +149,7 @@ type requestBaseOptions = { basicAuth: string request: MiddlewareRequest tokenCache: TokenCache, - requestState: RequestStateStore, + requestState: unknown, pendingTasks: Array, tokenCacheKey?: TokenCacheOptions, } diff --git a/packages/sdk-client-v3/tests/auth/anonymous-flow.test.ts b/packages/sdk-client-v3/tests/auth/anonymous-flow.test.ts index eb730075d..b12fd15bb 100644 --- a/packages/sdk-client-v3/tests/auth/anonymous-flow.test.ts +++ b/packages/sdk-client-v3/tests/auth/anonymous-flow.test.ts @@ -33,7 +33,7 @@ function createTestMiddlewareOptions(options) { describe('Anonymous Session Flow', () => { describe('Anonymous session flow', () => { test('should throw if `options` are not provided', () => { - const middlewareOptions = null + const middlewareOptions: any = null expect(() => buildRequestForAnonymousSessionFlow(middlewareOptions) @@ -202,7 +202,7 @@ describe('Anonymous Session Flow', () => { })) test('should not throw if `anonymousId` options is not provided.', () => - new Promise((resolve, reject) => { + new Promise(async (resolve, reject) => { const response = createTestResponse({ resolve, reject, @@ -232,9 +232,9 @@ describe('Anonymous Session Flow', () => { }, }) - createAuthMiddlewareForAnonymousSessionFlow(middlewareOptions)(next)( - createTestRequest({}) - ) + await createAuthMiddlewareForAnonymousSessionFlow(middlewareOptions)( + next + )(createTestRequest({})) expect(middlewareOptions.httpClient).toHaveBeenCalled() expect(middlewareOptions.httpClient).toHaveBeenCalledTimes(1) diff --git a/packages/sdk-client-v3/tests/auth/refresh-token.test.ts b/packages/sdk-client-v3/tests/auth/refresh-token.test.ts index 09896b499..3ec5dff32 100644 --- a/packages/sdk-client-v3/tests/auth/refresh-token.test.ts +++ b/packages/sdk-client-v3/tests/auth/refresh-token.test.ts @@ -34,7 +34,7 @@ describe('Refresh Token Flow', () => { describe('Refresh token flow auth request builder', () => { test('should throw if `options` are not provided.', () => { new Promise((resolve, reject) => { - const middlewareOptions = null + const middlewareOptions: any = null expect(() => buildRequestForRefreshTokenFlow(middlewareOptions) ).toThrow('Missing required options') @@ -269,7 +269,7 @@ describe('Refresh Token Flow', () => { })) test('should fetch and store token in tokenCache object', () => - new Promise((resolve, reject) => { + new Promise(async (resolve, reject) => { const response = createTestResponse({ resolve, reject, @@ -314,7 +314,7 @@ describe('Refresh Token Flow', () => { tokenCache, }) - createAuthMiddlewareForRefreshTokenFlow(middlewareOptions)(next)( + await createAuthMiddlewareForRefreshTokenFlow(middlewareOptions)(next)( createTestRequest({}) ) expect(middlewareOptions.httpClient).toHaveBeenCalledTimes(1)