Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(auth): debounce refreshAuthTokens #12845

Merged
merged 14 commits into from
Jan 18, 2024
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
"tslint-config-airbnb": "^5.8.0",
"typedoc": "^0.17.0",
"typescript": "^4.3.5",
"typescript-coverage-report": "^0.8.0",
"typescript-coverage-report": "^0.6.4",
"uuid-validate": "^0.0.3",
"webpack": "^5.75.0",
"webpack-bundle-analyzer": "^4.7.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import { AuthConfig } from '@aws-amplify/core';
import {
assertTokenProviderConfig,
decodeJWT,
deDupeAsyncRequests,
deDupeAsyncFunction,
} from '@aws-amplify/core/internals/utils';
import { initiateAuth } from '../utils/clients/CognitoIdentityProvider';
import { getRegion } from '../utils/clients/CognitoIdentityProvider/utils';
import { assertAuthTokensWithRefreshToken } from '../utils/types';
import { AuthError } from '../../../errors/AuthError';
import { getUserContextData } from './userContextData';

const refreshAuthTokensCallback: TokenRefresher = async ({
const refreshAuthTokensFunction: TokenRefresher = async ({
tokens,
authConfig,
username,
Expand Down Expand Up @@ -74,4 +74,4 @@ const refreshAuthTokensCallback: TokenRefresher = async ({
};
};

export const refreshAuthTokens = deDupeAsyncRequests(refreshAuthTokensCallback);
export const refreshAuthTokens = deDupeAsyncFunction(refreshAuthTokensFunction);
56 changes: 28 additions & 28 deletions packages/core/__tests__/utils/deDupeAsyncRequests.test.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,50 @@
import { deDupeAsyncRequests } from '../../src/utils/deDupeAsyncRequests';
import { deDupeAsyncFunction } from '../../src/utils/deDupeAsyncFunction';

describe('test debounce callback', () => {
describe('dedupeAsyncFunction()', () => {
const numberOfConcurrentCalls = 10;
const mockServiceCallback = jest.fn();
const mockServiceFunction = jest.fn();
const mockReturnValue = { id: 1 };

beforeEach(() => {
mockServiceCallback.mockImplementation(async () => {});
mockServiceFunction.mockImplementation(async () => mockReturnValue);
});
afterEach(() => {
mockServiceCallback.mockClear();
mockServiceFunction.mockClear();
});

it('should allow to invoke the callback when there is no concurrent calls', async () => {
const debouncedCallback = deDupeAsyncRequests(mockServiceCallback);
it('should invoke the mockServiceFunction', async () => {
const deDupedFunction = deDupeAsyncFunction(mockServiceFunction);

debouncedCallback();
expect(mockServiceCallback).toHaveBeenCalledTimes(1);
deDupedFunction();
expect(mockServiceFunction).toHaveBeenCalledTimes(1);
});

it('should invoke the callback one time during concurrent sync calls', () => {
const debouncedCallback = deDupeAsyncRequests(mockServiceCallback);
it('should invoke the mockServiceFunction one time during concurrent sync calls', () => {
const deDupedFunction = deDupeAsyncFunction(mockServiceFunction);
for (let i = 0; i < numberOfConcurrentCalls; i++) {
debouncedCallback();
deDupedFunction();
}
expect(mockServiceCallback).toHaveBeenCalledTimes(1);
expect(mockServiceFunction).toHaveBeenCalledTimes(1);
});

it('should allow to invoke the callback again after the promise has being resolved', async () => {
const debouncedCallback = deDupeAsyncRequests(mockServiceCallback);
it('should return a value once the mockServiceFunction is resolved', async () => {
const deDupedFunction = deDupeAsyncFunction(mockServiceFunction);
expect(await deDupedFunction()).toEqual(mockReturnValue);
expect(mockServiceFunction).toHaveBeenCalledTimes(1);
});

it('should allow to invoke the mockServiceFunction again after the promise has being resolved', async () => {
const deDupedFunction = deDupeAsyncFunction(mockServiceFunction);
for (let i = 0; i < numberOfConcurrentCalls; i++) {
debouncedCallback();
expect(deDupedFunction()).toBeInstanceOf(Promise);
}

await debouncedCallback();
// resolves the promise
expect(await deDupedFunction()).toEqual(mockReturnValue);

debouncedCallback();
expect(mockServiceCallback).toHaveBeenCalledTimes(2);
});
// should allow to call the mockServiceFunction again
deDupedFunction();

it('should return a value once the callback is resolved', async () => {
const mockReturnValue = { id: 1 };

mockServiceCallback.mockImplementation(async () => mockReturnValue);
const debouncedCallback = deDupeAsyncRequests(mockServiceCallback);
const result = await debouncedCallback();
expect(result).toEqual(mockReturnValue);
expect(mockServiceCallback).toHaveBeenCalledTimes(1);
expect(mockServiceFunction).toHaveBeenCalledTimes(2);
});
});
2 changes: 1 addition & 1 deletion packages/core/src/libraryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export {
retry,
urlSafeDecode,
urlSafeEncode,
deDupeAsyncRequests,
deDupeAsyncFunction,
} from './utils';
export { parseAWSExports } from './parseAWSExports';
export { LegacyConfig } from './singleton/types';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

// this will make the tsc-complience-test to pass
// this will make the tsc-compliance-test to pass
type Awaited<T> = T extends null | undefined
? T // special case for `null | undefined` when not in `--strictNullChecks` mode
: T extends object & { then(onfulfilled: infer F, ...args: infer _): any } // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped
Expand All @@ -10,33 +10,29 @@ type Awaited<T> = T extends null | undefined
: never // the argument to `then` was not callable
: T; //
/**
* returns inflight promise if there hasn't been resolved yet
* returns in-flight promise if there is one
*
* @param callback - callback to be deDup.
* @param callback - callback to be deduped.
israx marked this conversation as resolved.
Show resolved Hide resolved
* @returns - the return type of the callback
*/
export const deDupeAsyncRequests = <A extends any[], R>(
callback: (...args: A) => Promise<R>
export const deDupeAsyncFunction = <A extends any[], R>(
fun: (...args: A) => Promise<R>
israx marked this conversation as resolved.
Show resolved Hide resolved
) => {
let inflightPromise: Promise<Awaited<R>> | undefined;
return async (...args: A): Promise<Awaited<R>> => {
if (inflightPromise) return inflightPromise;

if (!inflightPromise) {
inflightPromise = new Promise(async (resolve, reject) => {
try {
const result = await callback(...args);
resolve(result);
} catch (error) {
reject(error);
}
});
}
inflightPromise = new Promise(async (resolve, reject) => {
try {
const result = await fun(...args);
resolve(result);
} catch (error) {
reject(error);
} finally {
inflightPromise = undefined;
}
});

try {
return await inflightPromise;
} finally {
inflightPromise = undefined;
}
return await inflightPromise;
israx marked this conversation as resolved.
Show resolved Hide resolved
};
};
2 changes: 1 addition & 1 deletion packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ export {
export { urlSafeDecode } from './urlSafeDecode';
export { urlSafeEncode } from './urlSafeEncode';
export { deepFreeze } from './deepFreeze';
export { deDupeAsyncRequests } from './deDupeAsyncRequests';
export { deDupeAsyncFunction } from './deDupeAsyncFunction';
18 changes: 9 additions & 9 deletions yarn.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading