aws-amplify
Version:
AWS Amplify is a JavaScript library for Frontend and mobile developers building cloud-enabled applications.
241 lines (222 loc) • 8.61 kB
text/typescript
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import {
AmplifyContext,
AuthSession,
AuthTokens,
FetchAuthSessionOptions,
Hub,
LibraryOptions,
ResourcesConfig,
createAmplifyContext,
getGlobalContext,
hasGlobalContext,
} from '@aws-amplify/core';
import {
AMPLIFY_SYMBOL,
AmplifyOutputsUnknown,
LegacyConfig,
parseAmplifyConfig,
selectSsrKeyValueStorage,
setGlobalContext,
} from '@aws-amplify/core/internals/utils';
import {
CognitoAWSCredentialsAndIdentityIdProvider,
DefaultIdentityIdStore,
cognitoCredentialsProvider,
cognitoUserPoolsTokenProvider,
} from './auth/cognito';
/**
* The `Amplify` singleton facade provides the v6-compatible, global-state
* `configure()` / `getConfig()` API. It builds a branded, frozen
* {@link AmplifyContext}, publishes it as the global context (so category APIs
* can be called without an explicit context argument), and dispatches the
* `configure` Hub event that side effects such as auth's OAuth listener depend
* on.
*
* @example
* ```ts
* import { Amplify } from 'aws-amplify';
* import config from './amplifyconfiguration.json';
*
* Amplify.configure(config);
* ```
*/
export const Amplify = {
/**
* Configures Amplify with the {@link resourceConfig} and {@link libraryOptions},
* publishing the resulting branded context as the global {@link AmplifyContext}.
*
* If `libraryOptions` is omitted on a reconfigure, the previously configured
* non-Auth `libraryOptions` are preserved (mirroring the previous singleton
* merge behavior); when provided they replace the previous ones. When
* `resourceConfig.Auth` is present and the caller does not supply
* `libraryOptions.Auth`, the default Cognito token/credentials providers are
* wired using cookie storage (`ssr: true`) or `localStorage` (default), and
* the singleton token provider's auth config is re-synced on every call so a
* reconfigure with a new `userPoolId` retargets token refresh.
*
* @param resourceConfig The {@link ResourcesConfig} object that is typically imported from the
* `amplifyconfiguration.json` / `amplify_outputs.json` file. It can also be an object literal
* created inline when calling `Amplify.configure`.
* @param libraryOptions The {@link LibraryOptions} additional options for the library.
*
* @example
* import config from './amplifyconfiguration.json';
*
* Amplify.configure(config);
*/
configure(
resourceConfig: ResourcesConfig | LegacyConfig | AmplifyOutputsUnknown,
libraryOptions?: LibraryOptions,
): void {
const resolvedResourceConfig = parseAmplifyConfig(resourceConfig);
const previousLibraryOptions = hasGlobalContext()
? getGlobalContext().libraryOptions
: undefined;
const resolvedLibraryOptions = resolveLibraryOptions(
resolvedResourceConfig,
libraryOptions,
previousLibraryOptions,
);
// Build a branded + frozen context (with a fresh per-context AuthClass)
// via the core factory, then publish it as the global context.
const ctx = createAmplifyContext(
resolvedResourceConfig,
resolvedLibraryOptions,
);
setGlobalContext(ctx);
// Notify listeners (e.g. auth's OAuth completion side effect) that the
// library has been (re)configured. The payload `data` carries the parsed
// ResourcesConfig so listeners can read `data.Auth.Cognito`.
Hub.dispatch(
'core',
{
event: 'configure',
data: resolvedResourceConfig,
},
'Configure',
AMPLIFY_SYMBOL,
);
},
/**
* Returns the {@link ResourcesConfig} object passed in as the `resourceConfig` parameter when
* calling `Amplify.configure`.
*
* If `configure()` has not been called yet, this warns and returns an empty
* config object (`{}`) rather than throwing, preserving the historical
* pre-context singleton behavior that callers (and release-gating e2e tests)
* depend on.
*
* @returns An {@link ResourcesConfig} object.
*/
getConfig(): ResourcesConfig {
if (!hasGlobalContext()) {
// Preserve the historical unconfigured UX from the core singleton:
// warn and return an empty config instead of throwing.
// eslint-disable-next-line no-console
console.warn(
`Amplify has not been configured. Please call Amplify.configure() before using this service.`,
);
return {};
}
return getGlobalContext().resourcesConfig;
},
/**
* Fetches the current auth session from the global context.
*
* @throws If `configure()` has not been called yet.
*/
fetchAuthSession(options?: FetchAuthSessionOptions): Promise<AuthSession> {
return getGlobalContext().fetchAuthSession(options);
},
/**
* Clears cached credentials in the global context.
*
* @throws If `configure()` has not been called yet.
*/
clearCredentials(): Promise<void> {
return getGlobalContext().clearCredentials();
},
/**
* Fetches auth tokens from the global context.
*
* @throws If `configure()` has not been called yet.
*/
getTokens(options: FetchAuthSessionOptions): Promise<AuthTokens | undefined> {
return getGlobalContext().getTokens(options);
},
};
/**
* Resolves the effective {@link LibraryOptions} for the global singleton,
* preserving the previous `DefaultAmplify`-style default-provider
* behavior (process-wide **singleton** Cognito providers, `setAuthConfig` /
* `setKeyValueStorage` refresh).
*
* - No `Auth` in the resource config → pass options through unchanged
* (preserving previously-resolved options when the caller supplies none).
* - Caller-provided `libraryOptions.Auth` → take precedence (no defaults).
* - Otherwise (Auth present, no caller-supplied Auth providers) → on **every**
* call, including reconfigures with or without other `libraryOptions`, refresh
* the singleton token provider's auth config + storage and inject the
* singleton token/credentials providers. Non-Auth library options are still
* carried forward from the previous configuration when the caller passes none.
*/
function resolveLibraryOptions(
resourceConfig: ResourcesConfig,
libraryOptions?: LibraryOptions,
previousLibraryOptions?: LibraryOptions,
): LibraryOptions {
// Pass-through: with no Auth in the resource config the singleton token
// provider is never involved, so simply preserve the previously configured
// libraryOptions when the caller supplies none (the core AmplifyClass
// singleton used to perform this merge), otherwise use what was given.
if (!resourceConfig.Auth) {
if (!libraryOptions && previousLibraryOptions) {
return previousLibraryOptions;
}
return libraryOptions ?? {};
}
// Caller-provided Auth providers always take precedence; never touch the
// singleton token provider in that case.
if (libraryOptions?.Auth) {
return libraryOptions;
}
// Auth is present and the caller did NOT supply their own Auth providers.
// Carry non-Auth library options forward from the previous configuration
// when the caller passes none (preserving prior behavior across
// reconfigure), but ALWAYS re-sync the
// singleton token provider below so the Auth providers never go stale.
const baseLibraryOptions = libraryOptions ?? previousLibraryOptions ?? {};
// Select the storage backing the default providers via core's shared
// helper (ssr → fresh cookie storage, else the localStorage-backed
// singleton). When ssr is set, reuse the SAME cookie storage instance for
// the identity-id store so tokens and identity ids share one cookie jar.
const resolvedKeyValueStorage = selectSsrKeyValueStorage(
baseLibraryOptions.ssr,
);
const resolvedCredentialsProvider = baseLibraryOptions.ssr
? new CognitoAWSCredentialsAndIdentityIdProvider(
new DefaultIdentityIdStore(resolvedKeyValueStorage),
)
: cognitoCredentialsProvider;
// Re-push the resolved auth config + key-value storage into the process-wide
// singleton token provider on EVERY configure call. AuthClass.getTokens
// delegates to this provider's own authConfig, which is only ever updated
// here; skipping this on a reconfigure (e.g. a new userPoolId with no
// libraryOptions) would leave token refresh pinned to the previous pool
// while getConfig()/credentials reflect the new one. Restores the #14819
// contract.
cognitoUserPoolsTokenProvider.setAuthConfig(resourceConfig.Auth);
cognitoUserPoolsTokenProvider.setKeyValueStorage(
// TODO: allow configure with a public interface
resolvedKeyValueStorage,
);
return {
...baseLibraryOptions,
Auth: {
tokenProvider: cognitoUserPoolsTokenProvider,
credentialsProvider: resolvedCredentialsProvider,
},
};
}