Skip to content

Commit

Permalink
chore(auth): debounce refreshAuthTokens (#12845)
Browse files Browse the repository at this point in the history
* chore: add debounce callback helper

* chore: add unit tests

* chore: debounce fetchAuthSession

* chore: fix unit test

* chore: fix bundle size limits

* chore: update debounce logic

* chore: update dedup logic

* chore: debounce refreshAuthTokens

* chore: fix bundle size

* chore: address feedback

* chore: fix unit test

* chore: address feedback

* chore: update yarn.lock

* chore: address feedbak
  • Loading branch information
israx authored Jan 18, 2024
1 parent 9c916d1 commit 5fc446e
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import { AuthConfig } from '@aws-amplify/core';
import {
assertTokenProviderConfig,
decodeJWT,
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';

export const refreshAuthTokens: TokenRefresher = async ({
const refreshAuthTokensFunction: TokenRefresher = async ({
tokens,
authConfig,
username,
Expand Down Expand Up @@ -72,3 +73,5 @@ export const refreshAuthTokens: TokenRefresher = async ({
username,
};
};

export const refreshAuthTokens = deDupeAsyncFunction(refreshAuthTokensFunction);
16 changes: 8 additions & 8 deletions packages/aws-amplify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -300,13 +300,13 @@
"name": "[Analytics] record (Kinesis)",
"path": "./dist/esm/analytics/kinesis/index.mjs",
"import": "{ record }",
"limit": "44.47 kB"
"limit": "44.48 kB"
},
{
"name": "[Analytics] record (Kinesis Firehose)",
"path": "./dist/esm/analytics/kinesis-firehose/index.mjs",
"import": "{ record }",
"limit": "41.50 kB"
"limit": "41.54 kB"
},
{
"name": "[Analytics] record (Personalize)",
Expand Down Expand Up @@ -384,7 +384,7 @@
"name": "[Auth] confirmSignIn (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ confirmSignIn }",
"limit": "25.89 kB"
"limit": "25.94 kB"
},
{
"name": "[Auth] updateMFAPreference (Cognito)",
Expand All @@ -396,19 +396,19 @@
"name": "[Auth] fetchMFAPreference (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ fetchMFAPreference }",
"limit": "8.30 kB"
"limit": "8.35 kB"
},
{
"name": "[Auth] verifyTOTPSetup (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ verifyTOTPSetup }",
"limit": "9.13 kB"
"limit": "9.18 kB"
},
{
"name": "[Auth] updatePassword (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ updatePassword }",
"limit": "9.14 kB"
"limit": "9.19 kB"
},
{
"name": "[Auth] setUpTOTP (Cognito)",
Expand All @@ -420,7 +420,7 @@
"name": "[Auth] updateUserAttributes (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ updateUserAttributes }",
"limit": "8.41 kB"
"limit": "8.46 kB"
},
{
"name": "[Auth] getCurrentUser (Cognito)",
Expand All @@ -432,7 +432,7 @@
"name": "[Auth] confirmUserAttribute (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ confirmUserAttribute }",
"limit": "9.15 kB"
"limit": "9.19 kB"
},
{
"name": "[Auth] signInWithRedirect (Cognito)",
Expand Down
50 changes: 50 additions & 0 deletions packages/core/__tests__/utils/deDupeAsyncRequests.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { deDupeAsyncFunction } from '../../src/utils/deDupeAsyncFunction';

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

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

it('should invoke the mockServiceFunction', async () => {
const deDupedFunction = deDupeAsyncFunction(mockServiceFunction);

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

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

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++) {
expect(deDupedFunction()).toBeInstanceOf(Promise);
}

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

// should allow to call the mockServiceFunction again
deDupedFunction();

expect(mockServiceFunction).toHaveBeenCalledTimes(2);
});
});
1 change: 1 addition & 0 deletions packages/core/src/libraryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export {
retry,
urlSafeDecode,
urlSafeEncode,
deDupeAsyncFunction,
} from './utils';
export { parseAWSExports } from './parseAWSExports';
export { LegacyConfig } from './singleton/types';
Expand Down
6 changes: 2 additions & 4 deletions packages/core/src/singleton/apis/fetchAuthSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@

import { fetchAuthSession as fetchAuthSessionInternal } from './internal/fetchAuthSession';
import { Amplify } from '../Amplify';
import { AuthSession, FetchAuthSessionOptions } from '../Auth/types';
import { FetchAuthSessionOptions } from '../Auth/types';

export const fetchAuthSession = (
options?: FetchAuthSessionOptions
): Promise<AuthSession> => {
export const fetchAuthSession = (options?: FetchAuthSessionOptions) => {
return fetchAuthSessionInternal(Amplify, options);
};
38 changes: 38 additions & 0 deletions packages/core/src/utils/deDupeAsyncFunction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

// 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
? F extends (value: infer V, ...args: infer _) => any // if the argument to `then` is callable, extracts the first argument
? Awaited<V> // recursively unwrap the value
: never // the argument to `then` was not callable
: T; //
/**
* returns in-flight promise if there is one
*
* @param asyncFunction - asyncFunction to be deduped.
* @returns - the return type of the callback
*/
export const deDupeAsyncFunction = <A extends any[], R>(
asyncFunction: (...args: A) => Promise<R>
) => {
let inflightPromise: Promise<Awaited<R>> | undefined;
return async (...args: A): Promise<Awaited<R>> => {
if (inflightPromise) return inflightPromise;

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

return inflightPromise;
};
};
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ export {
export { urlSafeDecode } from './urlSafeDecode';
export { urlSafeEncode } from './urlSafeEncode';
export { deepFreeze } from './deepFreeze';
export { deDupeAsyncFunction } from './deDupeAsyncFunction';
2 changes: 1 addition & 1 deletion packages/datastore/src/sync/processors/subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ class SubscriptionProcessor {

try {
// retrieving current token info from Cognito UserPools
const session = await await fetchAuthSession();
const session = await fetchAuthSession();
oidcTokenPayload = session.tokens?.idToken?.payload;
} catch (err) {
// best effort to get jwt from Cognito
Expand Down

0 comments on commit 5fc446e

Please sign in to comment.