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
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 callback - callback to be deduped.
israx marked this conversation as resolved.
Show resolved Hide resolved
* @returns - the return type of the callback
*/
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;

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

return await inflightPromise;
israx marked this conversation as resolved.
Show resolved Hide resolved
};
};
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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😬

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