From 75e80e338a3e00d3766f4daf41676dc2e224d153 Mon Sep 17 00:00:00 2001
From: AllanZhengYP
Date: Wed, 22 Mar 2023 14:01:30 -0700
Subject: [PATCH 01/41] chore(core): bump to ts 5.0 (#11077)
---
packages/core/package.json | 28 +++++++------------
packages/core/src/Credentials.ts | 2 +-
.../core/src/OAuthHelper/FacebookOAuth.ts | 2 +-
packages/core/src/OAuthHelper/GoogleOAuth.ts | 2 +-
.../core/src/StorageHelper/reactnative.ts | 2 +-
.../core/src/clients/polyfills/fetch.node.ts | 0
packages/core/tsconfig.json | 10 +++++++
7 files changed, 24 insertions(+), 22 deletions(-)
create mode 100644 packages/core/src/clients/polyfills/fetch.node.ts
create mode 100644 packages/core/tsconfig.json
diff --git a/packages/core/package.json b/packages/core/package.json
index b37c50c7c27..f5787f35c4f 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -18,10 +18,10 @@
"test": "npm run lint && jest -w 1 --coverage",
"test:size": "size-limit",
"build-with-test": "npm test && npm run build",
- "build:cjs": "node ./build es5 && webpack && webpack --config ./webpack.config.dev.js",
- "build:esm": "rimraf lib-esm && node ./build es6",
- "build:cjs:watch": "node ./build es5 --watch",
- "build:esm:watch": "rimraf lib-esm && node ./build es6 --watch",
+ "build:cjs": "rimraf lib && tsc -m commonjs --outDir lib && webpack && webpack --config ./webpack.config.dev.js",
+ "build:esm": "rimraf lib-esm && tsc -m esnext --outDir lib-esm",
+ "build:cjs:watch": "rimraf lib && tsc -m commonjs --outDir lib --watch",
+ "build:esm:watch": "rimraf lib-esm && tsc -m esnext --outDir lib-esm --watch",
"build": "npm run clean && npm run generate-version && npm run build:esm && npm run build:cjs",
"generate-version": "genversion src/Platform/version.ts --es6 --semi",
"clean": "npm run clean:size && rimraf lib-esm lib dist",
@@ -51,7 +51,8 @@
"find": "^0.2.7",
"genversion": "^2.2.0",
"prepend-file": "^1.3.1",
- "react-native": "^0.64.1"
+ "react-native": "^0.64.1",
+ "typescript": "5.0.2"
},
"files": [
"lib",
@@ -81,13 +82,13 @@
"name": "Core (Hub)",
"path": "./lib-esm/index.js",
"import": "{ Hub }",
- "limit": "2 kB"
+ "limit": "2.05 kB"
},
{
"name": "Core (I18n)",
"path": "./lib-esm/index.js",
"import": "{ I18n }",
- "limit": "2 kB"
+ "limit": "2.05 kB"
},
{
"name": "Core (Logger)",
@@ -99,23 +100,14 @@
"name": "Core (Credentials)",
"path": "./lib-esm/index.js",
"import": "{ Credentials }",
- "limit": "34.5 kB"
+ "limit": "35.5 kB"
}
],
"jest": {
"globals": {
"ts-jest": {
"diagnostics": false,
- "tsConfig": {
- "lib": [
- "es5",
- "es2015",
- "dom",
- "esnext.asynciterable",
- "es2017.object"
- ],
- "allowJs": true
- }
+ "tsConfig": false
}
},
"transform": {
diff --git a/packages/core/src/Credentials.ts b/packages/core/src/Credentials.ts
index 443c489e6d4..b82357c304d 100644
--- a/packages/core/src/Credentials.ts
+++ b/packages/core/src/Credentials.ts
@@ -49,7 +49,7 @@ export class CredentialsClass {
private _storage;
private _storageSync;
private _identityId;
- private _nextCredentialsRefresh: Number;
+ private _nextCredentialsRefresh: number;
// Allow `Auth` to be injected for SSR, but Auth isn't a required dependency for Credentials
Auth = undefined;
diff --git a/packages/core/src/OAuthHelper/FacebookOAuth.ts b/packages/core/src/OAuthHelper/FacebookOAuth.ts
index 8c2a7765d6e..75cf8c2d2c5 100644
--- a/packages/core/src/OAuthHelper/FacebookOAuth.ts
+++ b/packages/core/src/OAuthHelper/FacebookOAuth.ts
@@ -6,7 +6,7 @@ import { NonRetryableError } from '../Util';
const logger = new Logger('CognitoCredentials');
-const waitForInit = new Promise((res, rej) => {
+const waitForInit = new Promise((res, rej) => {
if (!browserOrNode().isBrowser) {
logger.debug('not in the browser, directly resolved');
return res();
diff --git a/packages/core/src/OAuthHelper/GoogleOAuth.ts b/packages/core/src/OAuthHelper/GoogleOAuth.ts
index 6fa3e205960..81abd3cc9bb 100644
--- a/packages/core/src/OAuthHelper/GoogleOAuth.ts
+++ b/packages/core/src/OAuthHelper/GoogleOAuth.ts
@@ -6,7 +6,7 @@ import { NonRetryableError } from '../Util';
const logger = new Logger('CognitoCredentials');
-const waitForInit = new Promise((res, rej) => {
+const waitForInit = new Promise((res, rej) => {
if (!browserOrNode().isBrowser) {
logger.debug('not in the browser, directly resolved');
return res();
diff --git a/packages/core/src/StorageHelper/reactnative.ts b/packages/core/src/StorageHelper/reactnative.ts
index 2b2fa1d3ab0..ea112a22281 100644
--- a/packages/core/src/StorageHelper/reactnative.ts
+++ b/packages/core/src/StorageHelper/reactnative.ts
@@ -59,7 +59,7 @@ class MemoryStorage {
*/
static sync() {
if (!MemoryStorage.syncPromise) {
- MemoryStorage.syncPromise = new Promise((res, rej) => {
+ MemoryStorage.syncPromise = new Promise((res, rej) => {
AsyncStorage.getAllKeys((errKeys, keys) => {
if (errKeys) rej(errKeys);
const memoryKeys = keys.filter(key =>
diff --git a/packages/core/src/clients/polyfills/fetch.node.ts b/packages/core/src/clients/polyfills/fetch.node.ts
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json
new file mode 100644
index 00000000000..962b4d99d8c
--- /dev/null
+++ b/packages/core/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../tsconfig.base.json",
+ "compilerOptions": {
+ "importHelpers": false
+ },
+ "include": ["./src"],
+ "watchOptions": {
+ "excludeDirectories": ["lib*"]
+ }
+}
From 79f8ae8c043df80b1b59a2ce87fe8527090837f8 Mon Sep 17 00:00:00 2001
From: AllanZhengYP
Date: Mon, 27 Mar 2023 16:33:12 -0700
Subject: [PATCH 02/41] feat(clients): basic types and fetch handler (#11120)
---
packages/core/__tests__/clients/fetch-test.ts | 80 +++++++++++++++++++
packages/core/package.json | 1 +
packages/core/src/clients/fetch.ts | 51 ++++++++++++
packages/core/src/clients/types/core.ts | 42 ++++++++++
packages/core/src/clients/types/http.ts | 40 ++++++++++
packages/core/src/clients/types/index.ts | 16 ++++
6 files changed, 230 insertions(+)
create mode 100644 packages/core/__tests__/clients/fetch-test.ts
create mode 100644 packages/core/src/clients/fetch.ts
create mode 100644 packages/core/src/clients/types/core.ts
create mode 100644 packages/core/src/clients/types/http.ts
create mode 100644 packages/core/src/clients/types/index.ts
diff --git a/packages/core/__tests__/clients/fetch-test.ts b/packages/core/__tests__/clients/fetch-test.ts
new file mode 100644
index 00000000000..fedcb4fe51a
--- /dev/null
+++ b/packages/core/__tests__/clients/fetch-test.ts
@@ -0,0 +1,80 @@
+const mockUnfetch = jest.fn();
+jest.mock('isomorphic-unfetch', () => {
+ global['fetch'] = mockUnfetch;
+});
+
+import { fetchTransferHandler } from '../../src/clients/fetch';
+
+describe(fetchTransferHandler.name, () => {
+ const mockBody = {
+ text: jest.fn(),
+ blob: jest.fn(),
+ json: jest.fn(),
+ };
+ const mockFetchResponse = Object.assign(
+ {
+ status: 200,
+ headers: { forEach: jest.fn() },
+ body: {},
+ },
+ mockBody
+ );
+ const mockRequest = {
+ method: 'GET' as const,
+ headers: {},
+ url: new URL('https://foo.bar'),
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockUnfetch.mockResolvedValue(mockFetchResponse);
+ });
+
+ test('should support abort signal', async () => {
+ const signal = new AbortController().signal;
+ await fetchTransferHandler(mockRequest, { abortSignal: signal });
+ expect(mockUnfetch).toBeCalledTimes(1);
+ expect(mockUnfetch.mock.calls[0][1]).toEqual(
+ expect.objectContaining({ signal })
+ );
+ });
+
+ test('should support text() in response.body', async () => {
+ const { body } = await fetchTransferHandler(mockRequest, {});
+ if (!body) {
+ fail('body should exist');
+ }
+ await body.text();
+ expect(mockBody.text).toBeCalledTimes(1);
+ });
+
+ test('should support blob() in response.body', async () => {
+ const { body } = await fetchTransferHandler(mockRequest, {});
+ if (!body) {
+ fail('body should exist');
+ }
+ await body.blob();
+ expect(mockBody.blob).toBeCalledTimes(1);
+ });
+
+ test('should support json() in response.body', async () => {
+ const { body } = await fetchTransferHandler(mockRequest, {});
+ if (!body) {
+ fail('body should exist');
+ }
+ await body.json();
+ expect(mockBody.json).toBeCalledTimes(1);
+ });
+
+ test.each(['GET', 'HEAD', 'DELETE'])(
+ 'should ignore request payload for %s request',
+ async method => {
+ await fetchTransferHandler(
+ { ...mockRequest, method, body: 'Mock Body' },
+ {}
+ );
+ expect(mockUnfetch).toBeCalledTimes(1);
+ expect(mockUnfetch.mock.calls[0][0].body).toBeUndefined();
+ }
+ );
+});
diff --git a/packages/core/package.json b/packages/core/package.json
index f5787f35c4f..c13d981bfa6 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -67,6 +67,7 @@
"@aws-sdk/credential-provider-cognito-identity": "3.6.1",
"@aws-sdk/types": "3.6.1",
"@aws-sdk/util-hex-encoding": "3.6.1",
+ "isomorphic-unfetch": "^3.0.0",
"tslib": "^1.8.0",
"universal-cookie": "^4.0.4",
"zen-observable-ts": "0.8.19"
diff --git a/packages/core/src/clients/fetch.ts b/packages/core/src/clients/fetch.ts
new file mode 100644
index 00000000000..cdaf8231703
--- /dev/null
+++ b/packages/core/src/clients/fetch.ts
@@ -0,0 +1,51 @@
+import 'isomorphic-unfetch'; // TODO: remove this dependency in v6
+import { HttpRequest, HttpResponse, HttpTransferOptions } from './types/http';
+import { TransferHandler } from './types/core';
+
+const shouldSendBody = (method: string) =>
+ !['HEAD', 'GET', 'DELETE'].includes(method.toUpperCase());
+
+export const fetchTransferHandler: TransferHandler<
+ HttpRequest,
+ HttpResponse,
+ HttpTransferOptions
+> = async (request, options) => {
+ let resp: Response;
+ try {
+ resp = await fetch(request.url, {
+ method: request.method,
+ headers: request.headers,
+ body: shouldSendBody(request.method) ? request.body : undefined,
+ signal: options.abortSignal,
+ });
+ } catch (e) {
+ // TODO: needs to revise error handling in v6
+ if (e instanceof TypeError) {
+ throw new Error(`Network error`);
+ }
+ throw e;
+ }
+
+ const headersBag = {};
+ resp.headers?.forEach((key, value) => {
+ headersBag[key.toLowerCase()] = value;
+ });
+ const httpResponse = {
+ statusCode: resp.status,
+ headers: headersBag,
+ body: null,
+ };
+
+ if (resp.body) {
+ const bodyWithMixin = Object.assign(resp.body, {
+ text: () => resp.text(),
+ blob: () => resp.blob(),
+ json: () => resp.json(),
+ });
+ return {
+ ...httpResponse,
+ body: bodyWithMixin,
+ };
+ }
+ return httpResponse;
+};
diff --git a/packages/core/src/clients/types/core.ts b/packages/core/src/clients/types/core.ts
new file mode 100644
index 00000000000..3bc5b01eae5
--- /dev/null
+++ b/packages/core/src/clients/types/core.ts
@@ -0,0 +1,42 @@
+/**
+ * General None HTTP-specific request interface
+ */
+export interface Request {
+ url: URL;
+ body?: unknown;
+}
+
+export interface Response {
+ body: unknown;
+}
+
+export interface TransferHandler<
+ Input extends Request,
+ Output extends Response,
+ TransferOptions
+> {
+ (request: Input, options: TransferOptions): Promise;
+}
+
+/**
+ * A slimmed down version of the AWS SDK v3 middleware handler, only handling instantiated requests
+ */
+export type MiddlewareHandler<
+ Input extends Request,
+ Output extends Response,
+ MiddlewareOptions
+> = (request: Input, options: MiddlewareOptions) => Promise;
+
+export type MiddlewareContext = Record;
+
+/**
+ * A slimmed down version of the AWS SDK v3 middleware, only handling tasks after Serde.
+ */
+export type Middleware<
+ Input extends Request,
+ Output extends Response,
+ MiddlewareOptions
+> = (
+ next: MiddlewareHandler ,
+ context: MiddlewareContext
+) => MiddlewareHandler ;
diff --git a/packages/core/src/clients/types/http.ts b/packages/core/src/clients/types/http.ts
new file mode 100644
index 00000000000..c6f5753a235
--- /dev/null
+++ b/packages/core/src/clients/types/http.ts
@@ -0,0 +1,40 @@
+import { Request, Response, TransferHandler } from './core';
+
+/**
+ * Use basic Record interface to workaround fetch Header class not available in Node.js
+ * The header names must be lowercased.
+ * TODO: use LowerCase intrinsic when we can support typescript 4.0
+ */
+export type Headers = Record;
+
+export interface HttpRequest extends Request {
+ method: string;
+ headers: Headers;
+ body?: BodyInit;
+}
+
+/**
+ * Reduce the API surface of Fetch API's Body mixin to only the methods we need.
+ * In React Native, body.arrayBuffer() is not supported.
+ * body.formData() is not supported for now.
+ */
+export type ResponseBodyMixin = Pick;
+
+export interface HttpResponse extends Response {
+ body: (ResponseBodyMixin & ReadableStream) | null;
+ statusCode: number;
+ /**
+ * @see {@link HttpRequest.headers}
+ */
+ headers: Headers;
+}
+
+export interface HttpTransferOptions {
+ abortSignal?: AbortSignal;
+}
+
+export type HttpTransferHandler = TransferHandler<
+ HttpRequest,
+ HttpResponse,
+ HttpTransferOptions
+>;
diff --git a/packages/core/src/clients/types/index.ts b/packages/core/src/clients/types/index.ts
new file mode 100644
index 00000000000..11346a05c8f
--- /dev/null
+++ b/packages/core/src/clients/types/index.ts
@@ -0,0 +1,16 @@
+export {
+ Middleware,
+ MiddlewareContext,
+ MiddlewareHandler,
+ Request,
+ Response,
+ TransferHandler,
+} from './core';
+export {
+ Headers,
+ HttpRequest,
+ HttpResponse,
+ HttpTransferHandler,
+ HttpTransferOptions,
+ ResponseBodyMixin,
+} from './http';
From 85bc134f1975eaab7c85b2c72f78e30946e226c2 Mon Sep 17 00:00:00 2001
From: AllanZhengYP
Date: Fri, 7 Apr 2023 09:28:53 -0700
Subject: [PATCH 03/41] feat(clients): compose transfer handler with middleware
& retry middleware (#11188)
* feat(clients): middleware interface and retry middleware
* test(clients): retry middleare unit test
* fix(clients): middleware type to include options
* feat(clients): add retry middleware unit test and update interface
* chore(clients): update bundle size limit for fetch and retry
* chore(clients): add retry docs; fix format
* fix(clients): address feedbacks
---
.../clients/composeTransferHandler-test.ts | 65 +++++++
.../clients/retry-middleware-test.ts | 182 ++++++++++++++++++
packages/core/package.json | 12 ++
.../internal/composeTransferHandler.ts | 85 ++++++++
packages/core/src/clients/middleware/retry.ts | 104 ++++++++++
packages/core/src/clients/types/core.ts | 18 +-
6 files changed, 460 insertions(+), 6 deletions(-)
create mode 100644 packages/core/__tests__/clients/composeTransferHandler-test.ts
create mode 100644 packages/core/__tests__/clients/retry-middleware-test.ts
create mode 100644 packages/core/src/clients/internal/composeTransferHandler.ts
create mode 100644 packages/core/src/clients/middleware/retry.ts
diff --git a/packages/core/__tests__/clients/composeTransferHandler-test.ts b/packages/core/__tests__/clients/composeTransferHandler-test.ts
new file mode 100644
index 00000000000..875a8dcd395
--- /dev/null
+++ b/packages/core/__tests__/clients/composeTransferHandler-test.ts
@@ -0,0 +1,65 @@
+import { composeTransferHandler } from '../../src/clients/internal/composeTransferHandler';
+import {
+ Middleware,
+ Request,
+ Response,
+ TransferHandler,
+} from '../../src/clients/types';
+
+describe(composeTransferHandler.name, () => {
+ test('should call core handler', async () => {
+ type HandlerOptions = { foo: string };
+ const coreHandler: TransferHandler = jest
+ .fn()
+ .mockResolvedValue({ body: 'Response' } as Response);
+ const handler = composeTransferHandler(coreHandler, []);
+ const resp = await handler({ url: new URL('https://a.b') }, { foo: 'bar' });
+ expect(resp).toEqual({ body: 'Response' });
+ expect(coreHandler).toBeCalledWith(
+ { url: new URL('https://a.b') },
+ { foo: 'bar' }
+ );
+ });
+
+ test('should call execute middleware in order', async () => {
+ type OptionsType = { mockFnInOptions: (calledFrom: string) => void };
+ const middlewareA: Middleware =
+ (options: OptionsType) => (next, context) => async request => {
+ request.body += 'A';
+ options.mockFnInOptions('A');
+ const resp = await next(request);
+ resp.body += 'A';
+ return resp;
+ };
+ const middlewareB: Middleware =
+ (options: OptionsType) => (next, context) => async request => {
+ request.body += 'B';
+ options.mockFnInOptions('B');
+ const resp = await next(request);
+ resp.body += 'B';
+ return resp;
+ };
+ const coreHandler: TransferHandler = jest
+ .fn()
+ .mockResolvedValue({ body: '' } as Response);
+ const handler = composeTransferHandler<[OptionsType, OptionsType]>(
+ coreHandler,
+ [middlewareA, middlewareB]
+ );
+ const options = {
+ mockFnInOptions: jest.fn(),
+ };
+ const resp = await handler(
+ { url: new URL('https://a.b'), body: '' },
+ options
+ );
+ expect(resp).toEqual({ body: 'BA' });
+ expect(coreHandler).toBeCalledWith(
+ expect.objectContaining({ body: 'AB' }),
+ expect.anything()
+ );
+ // Validate middleware share a same option object
+ expect(options.mockFnInOptions).toHaveBeenNthCalledWith(1, 'A');
+ expect(options.mockFnInOptions).toHaveBeenNthCalledWith(2, 'B');
+ });
+});
diff --git a/packages/core/__tests__/clients/retry-middleware-test.ts b/packages/core/__tests__/clients/retry-middleware-test.ts
new file mode 100644
index 00000000000..41693159e98
--- /dev/null
+++ b/packages/core/__tests__/clients/retry-middleware-test.ts
@@ -0,0 +1,182 @@
+import { MiddlewareHandler } from '../../src/clients/types';
+import { composeTransferHandler } from '../../src/clients/internal/composeTransferHandler';
+import { retry, RetryOptions } from '../../src/clients/middleware/retry';
+
+jest.spyOn(global, 'setTimeout');
+jest.spyOn(global, 'clearTimeout');
+
+describe(`${retry.name} middleware`, () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ const defaultRetryOptions = {
+ retryDecider: () => true,
+ computeDelay: () => 1,
+ };
+ const defaultRequest = { url: new URL('https://a.b') };
+ const getRetryableHandler = (nextHandler: MiddlewareHandler) =>
+ composeTransferHandler<[RetryOptions]>(nextHandler, [retry]);
+
+ test('should retry specified times', async () => {
+ const nextHandler = jest.fn().mockResolvedValue('foo');
+ const retryableHandler = getRetryableHandler(nextHandler);
+ let resp;
+ try {
+ resp = await retryableHandler(defaultRequest, {
+ ...defaultRetryOptions,
+ maxAttempts: 6,
+ });
+ fail('this test should fail');
+ } catch (error) {
+ expect(nextHandler).toBeCalledTimes(6);
+ expect(error.message).toEqual('Retry attempts exhausted');
+ }
+ });
+
+ test('should call retry decider on whether response is retryable', async () => {
+ const nextHandler = jest.fn().mockResolvedValue('foo');
+ const retryableHandler = getRetryableHandler(nextHandler);
+ const retryDecider = jest
+ .fn()
+ .mockImplementation(response => response !== 'foo'); // retry if response is not foo
+ const resp = await retryableHandler(defaultRequest, {
+ ...defaultRetryOptions,
+ retryDecider,
+ });
+ expect.assertions(3);
+ expect(nextHandler).toBeCalledTimes(1);
+ expect(retryDecider).toBeCalledTimes(1);
+ expect(resp).toEqual('foo');
+ });
+
+ test('should call retry decider on whether error is retryable', async () => {
+ const nextHandler = jest
+ .fn()
+ .mockRejectedValue(new Error('UnretryableError'));
+ const retryableHandler = getRetryableHandler(nextHandler);
+ const retryDecider = jest
+ .fn()
+ .mockImplementation(
+ (resp, error) => error.message !== 'UnretryableError'
+ );
+ try {
+ const resp = await retryableHandler(defaultRequest, {
+ ...defaultRetryOptions,
+ retryDecider,
+ });
+ fail('this test should fail');
+ } catch (e) {
+ expect(e.message).toBe('UnretryableError');
+ expect(nextHandler).toBeCalledTimes(1);
+ expect(retryDecider).toBeCalledTimes(1);
+ expect(retryDecider).toBeCalledWith(undefined, expect.any(Error));
+ }
+ expect.assertions(4);
+ });
+
+ test('should call computeDelay for intervals', async () => {
+ const nextHandler = jest.fn().mockResolvedValue('foo');
+ const retryableHandler = getRetryableHandler(nextHandler);
+ const computeDelay = jest.fn().mockImplementation(retry => retry * 100);
+ try {
+ await retryableHandler(defaultRequest, {
+ ...defaultRetryOptions,
+ maxAttempts: 6,
+ computeDelay,
+ });
+ fail('this test should fail');
+ } catch (error) {
+ expect(error.message).toBe('Retry attempts exhausted');
+ expect(nextHandler).toBeCalledTimes(6);
+ expect(computeDelay).toBeCalledTimes(5); // no interval after last attempt
+ }
+ expect.assertions(3);
+ });
+
+ test('should throw error if request already cancelled', async () => {
+ const nextHandler = jest.fn().mockResolvedValue('foo');
+ const retryableHandler = getRetryableHandler(nextHandler);
+ const controller = new AbortController();
+ controller.abort();
+ try {
+ await retryableHandler(defaultRequest, {
+ ...defaultRetryOptions,
+ abortSignal: controller.signal,
+ });
+ fail('this test should fail');
+ } catch (error) {
+ expect(error.message).toBe('Request aborted');
+ expect(nextHandler).toBeCalledTimes(0);
+ }
+ expect.assertions(2);
+ });
+
+ test('can be cancelled', async () => {
+ // Not using fake timers because of Jest limit: https://github.com/facebook/jest/issues/7151
+ const nextHandler = jest.fn().mockResolvedValue('foo');
+ const retryableHandler = getRetryableHandler(nextHandler);
+ const controller = new AbortController();
+ const retryDecider = () => true;
+ const computeDelay = jest.fn().mockImplementation(attempt => {
+ if (attempt === 1) {
+ setTimeout(() => controller.abort(), 100);
+ }
+ return 200;
+ });
+ try {
+ await retryableHandler(defaultRequest, {
+ ...defaultRetryOptions,
+ abortSignal: controller.signal,
+ computeDelay,
+ retryDecider,
+ });
+ fail('this test should fail');
+ } catch (error) {
+ expect(error.message).toBe('Request aborted');
+ expect(setTimeout).toBeCalledTimes(2); // 1st attempt + mock back-off strategy
+ expect(clearTimeout).toBeCalledTimes(1); // cancel 2nd attempt
+ }
+ });
+
+ test('should support 2 retry middleware tracking the same retry count', async () => {
+ const coreHandler = jest
+ .fn()
+ .mockRejectedValueOnce(new Error('CoreRetryableError'))
+ .mockResolvedValue('foo');
+ const betweenRetryFunction = jest
+ .fn()
+ .mockRejectedValueOnce(new Error('MiddlewareRetryableError'))
+ .mockResolvedValue(void 0);
+ const betweenRetryMiddleware =
+ () => (next: any, context: any) => async (args: any) => {
+ await betweenRetryFunction(args, context);
+ return next(args);
+ };
+
+ const doubleRetryableHandler = composeTransferHandler<
+ [RetryOptions, {}, RetryOptions]
+ >(coreHandler, [retry, betweenRetryMiddleware, retry]);
+
+ const retryDecider = jest
+ .fn()
+ .mockImplementation((response, error: Error) => {
+ if (error && error.message.endsWith('RetryableError')) return true;
+ return false;
+ });
+ const computeDelay = jest.fn().mockReturnValue(0);
+ const response = await doubleRetryableHandler(defaultRequest, {
+ ...defaultRetryOptions,
+ retryDecider,
+ computeDelay,
+ });
+
+ expect(response).toEqual('foo');
+ expect(coreHandler).toBeCalledTimes(2);
+ expect(betweenRetryFunction).toBeCalledTimes(2);
+ expect(retryDecider).toBeCalledTimes(4);
+ // computeDelay is called by 2 retry middleware with continuous attempts count.
+ expect(computeDelay).toHaveBeenNthCalledWith(1, 1);
+ expect(computeDelay).toHaveBeenNthCalledWith(2, 2);
+ });
+});
diff --git a/packages/core/package.json b/packages/core/package.json
index c13d981bfa6..0daed355c3c 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -102,6 +102,18 @@
"path": "./lib-esm/index.js",
"import": "{ Credentials }",
"limit": "35.5 kB"
+ },
+ {
+ "name": "Custom clients (retry middleware)",
+ "path": "./lib-esm/clients/middleware/retry.js",
+ "import": "{ retry }",
+ "limit": "1.2 kB"
+ },
+ {
+ "name": "Custom clients (fetch handler)",
+ "path": "./lib-esm/clients/fetch.js",
+ "import": "{ fetchTransferHandler }",
+ "limit": "1.73 kB"
}
],
"jest": {
diff --git a/packages/core/src/clients/internal/composeTransferHandler.ts b/packages/core/src/clients/internal/composeTransferHandler.ts
new file mode 100644
index 00000000000..a9d342117d3
--- /dev/null
+++ b/packages/core/src/clients/internal/composeTransferHandler.ts
@@ -0,0 +1,85 @@
+import {
+ Middleware,
+ MiddlewareHandler,
+ TransferHandler,
+ Request as RequestBase,
+ Response as ResponseBase,
+} from '../types';
+
+/**
+ * Compose a transfer handler with a core transfer handler and a list of middleware.
+ * @param coreHandler Core transfer handler
+ * @param middleware List of middleware
+ * @returns A transfer handler whose option type is the union of the core
+ * transfer handler's option type and the middleware's option type.
+ * @internal
+ */
+export const composeTransferHandler =
+ <
+ MiddlewareOptionsArr extends any[] = [],
+ Request extends RequestBase = RequestBase,
+ Response extends ResponseBase = ResponseBase,
+ CoreHandler extends TransferHandler<
+ Request,
+ Response,
+ any
+ > = TransferHandler
+ >(
+ coreHandler: CoreHandler,
+ middleware: OptionToMiddleware
+ ) =>
+ (
+ request: Request,
+ options: MergeNoConflictKeys<
+ [...MiddlewareOptionsArr, InferOptionTypeFromTransferHandler]
+ >
+ ) => {
+ const context = {};
+ let composedHandler: MiddlewareHandler = (
+ request: Request
+ ) => coreHandler(request, options);
+ for (const m of middleware.reverse()) {
+ const resolvedMiddleware = m(options);
+ composedHandler = resolvedMiddleware(composedHandler, context);
+ }
+ return composedHandler(request);
+ };
+
+/**
+ * Type to convert a middleware option type to a middleware type with the given
+ * option type.
+ */
+type OptionToMiddleware<
+ Request extends RequestBase,
+ Response extends ResponseBase,
+ Options extends any[]
+> = Options extends []
+ ? []
+ : Options extends [infer LastOption]
+ ? [Middleware]
+ : Options extends [infer FirstOption, ...infer RestOptions]
+ ? [
+ Middleware,
+ ...OptionToMiddleware
+ ]
+ : never;
+
+/**
+ * Type to intersect multiple types if they have no conflict keys.
+ */
+type MergeNoConflictKeys = Options extends [
+ infer OnlyOption
+]
+ ? OnlyOption
+ : Options extends [infer FirstOption, infer SecondOption]
+ ? FirstOption & SecondOption
+ : Options extends [infer FirstOption, ...infer RestOptions]
+ ? FirstOption & MergeNoConflictKeys
+ : never;
+
+/**
+ * Type to infer the option type of a transfer handler type.
+ */
+type InferOptionTypeFromTransferHandler<
+ T extends TransferHandler
+> = Parameters[1];
diff --git a/packages/core/src/clients/middleware/retry.ts b/packages/core/src/clients/middleware/retry.ts
new file mode 100644
index 00000000000..04817dc1662
--- /dev/null
+++ b/packages/core/src/clients/middleware/retry.ts
@@ -0,0 +1,104 @@
+import {
+ MiddlewareContext,
+ MiddlewareHandler,
+ Request,
+ Response,
+} from '../types/core';
+
+const DEFAULT_RETRY_ATTEMPTS = 3;
+
+/**
+ * Configuration of the retry middleware
+ */
+export interface RetryOptions {
+ /**
+ * Function to decide if the request should be retried.
+ *
+ * @param response Response of the request.
+ * @param error Optional error thrown from previous attempts.
+ * @returns True if the request should be retried.
+ */
+ retryDecider: (response: Response, error?: unknown) => boolean;
+ /**
+ * Function to compute the delay in milliseconds before the next retry based
+ * on the number of attempts.
+ * @param attempt Current number of attempts, including the first attempt.
+ * @returns Delay in milliseconds.
+ */
+ computeDelay: (attempt: number) => number;
+ /**
+ * Maximum number of retry attempts, starting from 1. Defaults to 3.
+ */
+ maxAttempts?: number;
+
+ /**
+ * Optional AbortSignal to abort the retry attempts.
+ */
+ abortSignal?: AbortSignal;
+}
+
+/**
+ * Retry middleware
+ */
+export const retry =
+ (options: RetryOptions) =>
+ (next: MiddlewareHandler, context: MiddlewareContext) => {
+ if (options.maxAttempts < 1) {
+ throw new Error('maxAttempts must be greater than 0');
+ }
+ return async function retry(request: Request) {
+ const {
+ maxAttempts = DEFAULT_RETRY_ATTEMPTS,
+ retryDecider,
+ computeDelay,
+ abortSignal,
+ } = options;
+ let error = undefined;
+ let attemptsCount = context.attemptsCount ?? 0;
+ let response;
+ while (!abortSignal?.aborted && attemptsCount < maxAttempts) {
+ error = undefined;
+ response = undefined;
+ try {
+ response = await next(request);
+ } catch (e) {
+ error = e;
+ }
+ if (retryDecider(response, error)) {
+ attemptsCount += 1;
+ if (!abortSignal?.aborted && attemptsCount < maxAttempts) {
+ // prevent sleep for last attempt or cancelled request;
+ const delay = computeDelay(attemptsCount);
+ await cancellableSleep(delay, abortSignal);
+ }
+ context.attemptsCount = attemptsCount;
+ continue;
+ } else if (response) {
+ return response;
+ } else {
+ throw error;
+ }
+ }
+ throw abortSignal?.aborted
+ ? new Error('Request aborted')
+ : error ?? new Error('Retry attempts exhausted');
+ };
+ };
+
+const cancellableSleep = (timeoutMs: number, abortSignal?: AbortSignal) => {
+ if (abortSignal?.aborted) {
+ return Promise.resolve();
+ }
+ let timeoutId;
+ let sleepPromiseResolveFn;
+ const sleepPromise = new Promise(resolve => {
+ sleepPromiseResolveFn = resolve;
+ timeoutId = setTimeout(resolve, timeoutMs);
+ });
+ abortSignal?.addEventListener('abort', function cancelSleep(event) {
+ clearTimeout(timeoutId);
+ abortSignal?.removeEventListener('abort', cancelSleep);
+ sleepPromiseResolveFn();
+ });
+ return sleepPromise;
+};
diff --git a/packages/core/src/clients/types/core.ts b/packages/core/src/clients/types/core.ts
index 3bc5b01eae5..18407723a7d 100644
--- a/packages/core/src/clients/types/core.ts
+++ b/packages/core/src/clients/types/core.ts
@@ -23,11 +23,15 @@ export interface TransferHandler<
*/
export type MiddlewareHandler<
Input extends Request,
- Output extends Response,
- MiddlewareOptions
-> = (request: Input, options: MiddlewareOptions) => Promise;
+ Output extends Response
+> = (request: Input) => Promise;
-export type MiddlewareContext = Record;
+export type MiddlewareContext = {
+ /**
+ * The number of times the request has been attempted. This is set by retry middleware
+ */
+ attemptsCount?: number;
+};
/**
* A slimmed down version of the AWS SDK v3 middleware, only handling tasks after Serde.
@@ -37,6 +41,8 @@ export type Middleware<
Output extends Response,
MiddlewareOptions
> = (
- next: MiddlewareHandler ,
+ options: MiddlewareOptions
+) => (
+ next: MiddlewareHandler ,
context: MiddlewareContext
-) => MiddlewareHandler ;
+) => MiddlewareHandler ;
From 7fc0540a44ffe4b70320996b0142ed18d6365303 Mon Sep 17 00:00:00 2001
From: AllanZhengYP
Date: Tue, 11 Apr 2023 09:43:24 -0700
Subject: [PATCH 04/41] fix(retry): add metadata to returns from retry
middldeware (#11212)
* fix(retry): add metadata to returns from retry middldeware; fix minor bugs
* feat(clients): address retry middleware feedbacks
Co-authored-by: Chris F <5827964+cshfang@users.noreply.github.com>
---
.../clients/retry-middleware-test.ts | 28 ++++++----
packages/core/package.json | 2 +-
packages/core/src/clients/middleware/retry.ts | 51 +++++++++++++------
packages/core/src/clients/types/core.ts | 12 ++---
packages/core/src/clients/types/index.ts | 1 -
5 files changed, 61 insertions(+), 33 deletions(-)
diff --git a/packages/core/__tests__/clients/retry-middleware-test.ts b/packages/core/__tests__/clients/retry-middleware-test.ts
index 41693159e98..327978c810c 100644
--- a/packages/core/__tests__/clients/retry-middleware-test.ts
+++ b/packages/core/__tests__/clients/retry-middleware-test.ts
@@ -1,4 +1,4 @@
-import { MiddlewareHandler } from '../../src/clients/types';
+import { HttpResponse, MiddlewareHandler } from '../../src/clients/types';
import { composeTransferHandler } from '../../src/clients/internal/composeTransferHandler';
import { retry, RetryOptions } from '../../src/clients/middleware/retry';
@@ -15,11 +15,16 @@ describe(`${retry.name} middleware`, () => {
computeDelay: () => 1,
};
const defaultRequest = { url: new URL('https://a.b') };
+ const defaultResponse: HttpResponse = {
+ body: 'foo' as any,
+ statusCode: 200,
+ headers: {},
+ };
const getRetryableHandler = (nextHandler: MiddlewareHandler) =>
composeTransferHandler<[RetryOptions]>(nextHandler, [retry]);
test('should retry specified times', async () => {
- const nextHandler = jest.fn().mockResolvedValue('foo');
+ const nextHandler = jest.fn().mockResolvedValue(defaultResponse);
const retryableHandler = getRetryableHandler(nextHandler);
let resp;
try {
@@ -35,11 +40,11 @@ describe(`${retry.name} middleware`, () => {
});
test('should call retry decider on whether response is retryable', async () => {
- const nextHandler = jest.fn().mockResolvedValue('foo');
+ const nextHandler = jest.fn().mockResolvedValue(defaultResponse);
const retryableHandler = getRetryableHandler(nextHandler);
const retryDecider = jest
.fn()
- .mockImplementation(response => response !== 'foo'); // retry if response is not foo
+ .mockImplementation(response => response.body !== 'foo'); // retry if response is not foo
const resp = await retryableHandler(defaultRequest, {
...defaultRetryOptions,
retryDecider,
@@ -47,7 +52,7 @@ describe(`${retry.name} middleware`, () => {
expect.assertions(3);
expect(nextHandler).toBeCalledTimes(1);
expect(retryDecider).toBeCalledTimes(1);
- expect(resp).toEqual('foo');
+ expect(resp).toEqual({ ...defaultResponse, $metadata: { attempts: 1 } });
});
test('should call retry decider on whether error is retryable', async () => {
@@ -76,7 +81,7 @@ describe(`${retry.name} middleware`, () => {
});
test('should call computeDelay for intervals', async () => {
- const nextHandler = jest.fn().mockResolvedValue('foo');
+ const nextHandler = jest.fn().mockResolvedValue(defaultResponse);
const retryableHandler = getRetryableHandler(nextHandler);
const computeDelay = jest.fn().mockImplementation(retry => retry * 100);
try {
@@ -95,7 +100,7 @@ describe(`${retry.name} middleware`, () => {
});
test('should throw error if request already cancelled', async () => {
- const nextHandler = jest.fn().mockResolvedValue('foo');
+ const nextHandler = jest.fn().mockResolvedValue(defaultResponse);
const retryableHandler = getRetryableHandler(nextHandler);
const controller = new AbortController();
controller.abort();
@@ -114,7 +119,7 @@ describe(`${retry.name} middleware`, () => {
test('can be cancelled', async () => {
// Not using fake timers because of Jest limit: https://github.com/facebook/jest/issues/7151
- const nextHandler = jest.fn().mockResolvedValue('foo');
+ const nextHandler = jest.fn().mockResolvedValue(defaultResponse);
const retryableHandler = getRetryableHandler(nextHandler);
const controller = new AbortController();
const retryDecider = () => true;
@@ -143,7 +148,7 @@ describe(`${retry.name} middleware`, () => {
const coreHandler = jest
.fn()
.mockRejectedValueOnce(new Error('CoreRetryableError'))
- .mockResolvedValue('foo');
+ .mockResolvedValue(defaultResponse);
const betweenRetryFunction = jest
.fn()
.mockRejectedValueOnce(new Error('MiddlewareRetryableError'))
@@ -171,7 +176,10 @@ describe(`${retry.name} middleware`, () => {
computeDelay,
});
- expect(response).toEqual('foo');
+ expect(response).toEqual({
+ ...defaultResponse,
+ $metadata: { attempts: 3 },
+ });
expect(coreHandler).toBeCalledTimes(2);
expect(betweenRetryFunction).toBeCalledTimes(2);
expect(retryDecider).toBeCalledTimes(4);
diff --git a/packages/core/package.json b/packages/core/package.json
index 0daed355c3c..8a1fe5a05d2 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -107,7 +107,7 @@
"name": "Custom clients (retry middleware)",
"path": "./lib-esm/clients/middleware/retry.js",
"import": "{ retry }",
- "limit": "1.2 kB"
+ "limit": "1.22 kB"
},
{
"name": "Custom clients (fetch handler)",
diff --git a/packages/core/src/clients/middleware/retry.ts b/packages/core/src/clients/middleware/retry.ts
index 04817dc1662..8350bd5cdbb 100644
--- a/packages/core/src/clients/middleware/retry.ts
+++ b/packages/core/src/clients/middleware/retry.ts
@@ -1,3 +1,4 @@
+import { MetadataBearer } from '@aws-sdk/types';
import {
MiddlewareContext,
MiddlewareHandler,
@@ -14,11 +15,11 @@ export interface RetryOptions {
/**
* Function to decide if the request should be retried.
*
- * @param response Response of the request.
+ * @param response Optional response of the request.
* @param error Optional error thrown from previous attempts.
* @returns True if the request should be retried.
*/
- retryDecider: (response: Response, error?: unknown) => boolean;
+ retryDecider: (response?: Response, error?: unknown) => boolean;
/**
* Function to compute the delay in milliseconds before the next retry based
* on the number of attempts.
@@ -30,7 +31,6 @@ export interface RetryOptions {
* Maximum number of retry attempts, starting from 1. Defaults to 3.
*/
maxAttempts?: number;
-
/**
* Optional AbortSignal to abort the retry attempts.
*/
@@ -40,22 +40,24 @@ export interface RetryOptions {
/**
* Retry middleware
*/
-export const retry =
- (options: RetryOptions) =>
- (next: MiddlewareHandler, context: MiddlewareContext) => {
- if (options.maxAttempts < 1) {
- throw new Error('maxAttempts must be greater than 0');
- }
- return async function retry(request: Request) {
+export const retry = (options: RetryOptions) => {
+ if (options.maxAttempts < 1) {
+ throw new Error('maxAttempts must be greater than 0');
+ }
+ return (
+ next: MiddlewareHandler,
+ context: MiddlewareContext
+ ) =>
+ async function retry(request: Request) {
const {
maxAttempts = DEFAULT_RETRY_ATTEMPTS,
retryDecider,
computeDelay,
abortSignal,
} = options;
- let error = undefined;
+ let error: Error;
let attemptsCount = context.attemptsCount ?? 0;
- let response;
+ let response: Response;
while (!abortSignal?.aborted && attemptsCount < maxAttempts) {
error = undefined;
response = undefined;
@@ -64,18 +66,24 @@ export const retry =
} catch (e) {
error = e;
}
+ // context.attemptsCount may be updated after calling next handler which may retry the request by itself.
+ attemptsCount =
+ context.attemptsCount > attemptsCount
+ ? context.attemptsCount
+ : attemptsCount + 1;
+ context.attemptsCount = attemptsCount;
if (retryDecider(response, error)) {
- attemptsCount += 1;
if (!abortSignal?.aborted && attemptsCount < maxAttempts) {
// prevent sleep for last attempt or cancelled request;
const delay = computeDelay(attemptsCount);
await cancellableSleep(delay, abortSignal);
}
- context.attemptsCount = attemptsCount;
continue;
} else if (response) {
+ updateMetadataAttempts(response, attemptsCount);
return response;
} else {
+ updateMetadataAttempts(error, attemptsCount);
throw error;
}
}
@@ -83,7 +91,7 @@ export const retry =
? new Error('Request aborted')
: error ?? new Error('Retry attempts exhausted');
};
- };
+};
const cancellableSleep = (timeoutMs: number, abortSignal?: AbortSignal) => {
if (abortSignal?.aborted) {
@@ -102,3 +110,16 @@ const cancellableSleep = (timeoutMs: number, abortSignal?: AbortSignal) => {
});
return sleepPromise;
};
+
+const isMetadataBearer = (response: unknown): response is MetadataBearer =>
+ typeof response?.['$metadata'] === 'object';
+
+const updateMetadataAttempts = (
+ nextHandlerOutput: Object,
+ attempts: number
+) => {
+ if (isMetadataBearer(nextHandlerOutput)) {
+ nextHandlerOutput.$metadata.attempts = attempts;
+ }
+ nextHandlerOutput['$metadata'] = { attempts };
+};
diff --git a/packages/core/src/clients/types/core.ts b/packages/core/src/clients/types/core.ts
index 18407723a7d..f7605fccf22 100644
--- a/packages/core/src/clients/types/core.ts
+++ b/packages/core/src/clients/types/core.ts
@@ -33,6 +33,11 @@ export type MiddlewareContext = {
attemptsCount?: number;
};
+type ConfiguredMiddleware = (
+ next: MiddlewareHandler ,
+ context: MiddlewareContext
+) => MiddlewareHandler ;
+
/**
* A slimmed down version of the AWS SDK v3 middleware, only handling tasks after Serde.
*/
@@ -40,9 +45,4 @@ export type Middleware<
Input extends Request,
Output extends Response,
MiddlewareOptions
-> = (
- options: MiddlewareOptions
-) => (
- next: MiddlewareHandler ,
- context: MiddlewareContext
-) => MiddlewareHandler ;
+> = (options: MiddlewareOptions) => ConfiguredMiddleware ;
diff --git a/packages/core/src/clients/types/index.ts b/packages/core/src/clients/types/index.ts
index 11346a05c8f..6cbcd31edaa 100644
--- a/packages/core/src/clients/types/index.ts
+++ b/packages/core/src/clients/types/index.ts
@@ -1,6 +1,5 @@
export {
Middleware,
- MiddlewareContext,
MiddlewareHandler,
Request,
Response,
From 080edeed65cd97b84fb4f7e584eb11de342bffdf Mon Sep 17 00:00:00 2001
From: Allan Zheng
Date: Tue, 11 Apr 2023 10:52:52 -0700
Subject: [PATCH 05/41] chore(core): update size limit
---
packages/core/package.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/core/package.json b/packages/core/package.json
index eecd48b4946..f303dbd8e59 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -89,7 +89,7 @@
"name": "Core (I18n)",
"path": "./lib-esm/index.js",
"import": "{ I18n }",
- "limit": "2.1 kB"
+ "limit": "2.12 kB"
},
{
"name": "Core (Logger)",
@@ -107,7 +107,7 @@
"name": "Custom clients (retry middleware)",
"path": "./lib-esm/clients/middleware/retry.js",
"import": "{ retry }",
- "limit": "1.22 kB"
+ "limit": "1.23 kB"
},
{
"name": "Custom clients (fetch handler)",
From 67e40171385f02d0c9448fdc3e036d63e009ea34 Mon Sep 17 00:00:00 2001
From: AllanZhengYP
Date: Fri, 14 Apr 2023 16:10:29 -0700
Subject: [PATCH 06/41] feat(clients): cognito identity client (#11213)
* feat(clients): implement service API composer
* chore(clients): rename middleware interface
* feat(clients): middleware handler interface can be non request/response interface
* chore(clients): move fetch handler to handlers folder
* feat(clients): implement user agent middleware
* feat(clients): implement unauth aws transfer handler
* feat(clients): implement api handler composer
* feat(clients): implement cognito-identity client
* chore(clients): update bundle size limit
* feat(clients): add serde utils; remove retry in api handler composer
* feat(clients): integrate cognito-identity client to Credentials class
* fix(clients): make retryDecider interface async
* chore: move cognito client to dev dep
* chore(clients): use Pascale path name
* fix(clients): handle fetch response.body undefined in RN
* chore: publish under v5-custom-clients dist-tag
* fix(clients): read body once for errors
---
package.json | 1 +
packages/api-graphql/package.json | 2 +-
packages/api-rest/package.json | 2 +-
packages/api/package.json | 2 +-
packages/auth/package.json | 2 +-
packages/core/__tests__/Credentials-test.ts | 97 ++++------
packages/core/__tests__/Util-test.ts | 82 ---------
.../clients/composeApiHandler-test.ts | 63 +++++++
packages/core/__tests__/clients/fetch-test.ts | 10 +-
.../clients/retry-middleware-test.ts | 15 +-
packages/core/package.json | 19 +-
.../src/AwsClients/CognitoIdentity/base.ts | 83 +++++++++
.../getCredentialsForIdentity.ts | 62 +++++++
.../src/AwsClients/CognitoIdentity/getId.ts | 53 ++++++
.../src/AwsClients/CognitoIdentity/index.ts | 6 +
packages/core/src/Credentials.ts | 171 ++++++++----------
.../core/src/Util/CognitoIdentityClient.ts | 48 -----
.../core/src/clients/{ => handlers}/fetch.ts | 38 ++--
.../src/clients/handlers/unauthenticated.ts | 11 ++
.../src/clients/internal/composeApiHandler.ts | 44 +++++
.../middleware/retry/defaultRetryDecider.ts | 63 +++++++
.../src/clients/middleware/retry/index.ts | 3 +
.../middleware/retry/jitteredBackoff.ts | 11 ++
.../{retry.ts => retry/middleware.ts} | 28 ++-
.../src/clients/middleware/userAgent/index.ts | 1 +
.../middleware/userAgent/middleware.ts | 33 ++++
packages/core/src/clients/serde/index.ts | 2 +
packages/core/src/clients/serde/json.ts | 50 +++++
.../core/src/clients/serde/responseInfo.ts | 17 ++
packages/core/src/clients/types/aws.ts | 19 ++
packages/core/src/clients/types/core.ts | 15 +-
packages/core/src/clients/types/http.ts | 2 +-
packages/core/src/clients/types/index.ts | 2 +
packages/datastore/package.json | 2 +-
packages/geo/package.json | 2 +-
packages/interactions/package.json | 2 +-
packages/notifications/package.json | 4 +-
packages/pubsub/package.json | 4 +-
38 files changed, 723 insertions(+), 348 deletions(-)
create mode 100644 packages/core/__tests__/clients/composeApiHandler-test.ts
create mode 100644 packages/core/src/AwsClients/CognitoIdentity/base.ts
create mode 100644 packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
create mode 100644 packages/core/src/AwsClients/CognitoIdentity/getId.ts
create mode 100644 packages/core/src/AwsClients/CognitoIdentity/index.ts
delete mode 100644 packages/core/src/Util/CognitoIdentityClient.ts
rename packages/core/src/clients/{ => handlers}/fetch.ts (60%)
create mode 100644 packages/core/src/clients/handlers/unauthenticated.ts
create mode 100644 packages/core/src/clients/internal/composeApiHandler.ts
create mode 100644 packages/core/src/clients/middleware/retry/defaultRetryDecider.ts
create mode 100644 packages/core/src/clients/middleware/retry/index.ts
create mode 100644 packages/core/src/clients/middleware/retry/jitteredBackoff.ts
rename packages/core/src/clients/middleware/{retry.ts => retry/middleware.ts} (82%)
create mode 100644 packages/core/src/clients/middleware/userAgent/index.ts
create mode 100644 packages/core/src/clients/middleware/userAgent/middleware.ts
create mode 100644 packages/core/src/clients/serde/index.ts
create mode 100644 packages/core/src/clients/serde/json.ts
create mode 100644 packages/core/src/clients/serde/responseInfo.ts
create mode 100644 packages/core/src/clients/types/aws.ts
diff --git a/package.json b/package.json
index 7f5edbff1ec..be298a79285 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,7 @@
"publish:next": "lerna publish --canary --force-publish \"*\" --yes --dist-tag=next --preid=next${PREID_HASH_SUFFIX} --exact --no-verify-access",
"publish:release": "lerna publish --conventional-commits --yes --message 'chore(release): Publish [ci skip]' --no-verify-access",
"publish:verdaccio": "lerna publish --no-push --canary minor --dist-tag=unstable --preid=unstable --exact --force-publish --yes --no-verify-access",
+ "publish:v5/custom-clients": "lerna publish --canary --force-publish \"*\" --yes --dist-tag=v5-custom-clients --preid=v5-custom-clients${PREID_HASH_SUFFIX} --exact --no-verify-access",
"ts-coverage": "lerna run ts-coverage"
},
"husky": {
diff --git a/packages/api-graphql/package.json b/packages/api-graphql/package.json
index e22d977791b..ab9094e0a29 100644
--- a/packages/api-graphql/package.json
+++ b/packages/api-graphql/package.json
@@ -64,7 +64,7 @@
"name": "API (GraphQL client)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, GraphQLAPI }",
- "limit": "104.5 kB"
+ "limit": "86.2 kB"
}
],
"jest": {
diff --git a/packages/api-rest/package.json b/packages/api-rest/package.json
index c9236288c21..4df69cf3de8 100644
--- a/packages/api-rest/package.json
+++ b/packages/api-rest/package.json
@@ -55,7 +55,7 @@
"name": "API (rest client)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, RestAPI }",
- "limit": "47.5 kB"
+ "limit": "28.8 kB"
}
],
"jest": {
diff --git a/packages/api/package.json b/packages/api/package.json
index 23e266bcf9b..46acc34ab3d 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -66,7 +66,7 @@
"name": "API (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, API }",
- "limit": "106 kB"
+ "limit": "87.5 kB"
}
],
"jest": {
diff --git a/packages/auth/package.json b/packages/auth/package.json
index b97df8c6852..daf04401aa0 100644
--- a/packages/auth/package.json
+++ b/packages/auth/package.json
@@ -58,7 +58,7 @@
"name": "Auth (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Auth }",
- "limit": "73 kB"
+ "limit": "53.4 kB"
}
],
"jest": {
diff --git a/packages/core/__tests__/Credentials-test.ts b/packages/core/__tests__/Credentials-test.ts
index 09397ded039..527b70eb29d 100644
--- a/packages/core/__tests__/Credentials-test.ts
+++ b/packages/core/__tests__/Credentials-test.ts
@@ -1,14 +1,13 @@
import { CredentialsClass as Credentials } from '../src/Credentials';
import { Amplify } from '../src/Amplify';
-import { Hub } from '../src/Hub';
import {
- CognitoIdentityClient,
- GetCredentialsForIdentityCommand,
- GetIdCommand,
-} from '@aws-sdk/client-cognito-identity';
-jest.mock('@aws-sdk/client-cognito-identity');
-import { fromCognitoIdentity } from '@aws-sdk/credential-provider-cognito-identity';
-jest.mock('@aws-sdk/credential-provider-cognito-identity');
+ getCredentialsForIdentity,
+ getId,
+} from '../src/AwsClients/CognitoIdentity';
+import { Hub } from '../src/Hub';
+
+jest.mock('../src/AwsClients/CognitoIdentity');
+
const session = {};
const user = {
@@ -108,39 +107,20 @@ describe('Credentials test', () => {
const region = 'us-west-2';
beforeAll(() => {
- CognitoIdentityClient.mockImplementation(params => {
- return {
- send: params => {
- if (params instanceof GetIdCommand) {
- return { IdentityId: '123' };
- }
- if (params instanceof GetCredentialsForIdentityCommand) {
- return {
- Credentials: {
- AccessKeyId: 'accessKey',
- Expiration: 0,
- SecretKey: 'secretKey',
- SessionToken: 'sessionToken',
- },
- IdentityId: '123',
- };
- }
- },
- middlewareStack: {
- add: (next, _) => {},
- },
- };
- });
-
- fromCognitoIdentity.mockImplementation(params => {
- return async () => {
- return {};
- };
+ (getId as jest.Mock).mockResolvedValue({ IdentityId: '123' });
+ (getCredentialsForIdentity as jest.Mock).mockResolvedValue({
+ Credentials: {
+ AccessKeyId: 'accessKey',
+ Expiration: 0,
+ SecretKey: 'secretKey',
+ SessionToken: 'sessionToken',
+ },
+ IdentityId: '123',
});
});
test('should use identityPoolRegion param for credentials for federation', async () => {
- expect.assertions(2);
+ expect.assertions(1);
const credentials = new Credentials(null);
@@ -157,14 +137,11 @@ describe('Credentials test', () => {
identity_id: '123',
});
- expect(CognitoIdentityClient).toHaveBeenCalledWith(
- expect.objectContaining({ region: identityPoolRegion })
- );
-
- expect(fromCognitoIdentity).toBeCalledWith(
+ expect(getCredentialsForIdentity).toHaveBeenCalledWith(
+ expect.objectContaining({ region: identityPoolRegion }),
expect.objectContaining({
- identityId: '123',
- logins: {
+ IdentityId: '123',
+ Logins: {
'accounts.google.com': 'token',
},
})
@@ -172,7 +149,7 @@ describe('Credentials test', () => {
});
test('should use identityPoolRegion param for credentials from session', async () => {
- expect.assertions(2);
+ expect.assertions(1);
const credentials = new Credentials(null);
@@ -195,20 +172,19 @@ describe('Credentials test', () => {
await credentials._setCredentialsFromSession(session);
- expect(CognitoIdentityClient).toHaveBeenCalledWith(
- expect.objectContaining({ region: identityPoolRegion })
+ expect(getId).toBeCalledWith(
+ expect.objectContaining({ region: identityPoolRegion }),
+ {
+ IdentityPoolId: identityPoolId,
+ Logins: {
+ [`cognito-idp.${region}.amazonaws.com/${userPoolId}`]: 'token',
+ },
+ }
);
-
- expect(GetIdCommand).toBeCalledWith({
- IdentityPoolId: identityPoolId,
- Logins: {
- [`cognito-idp.${region}.amazonaws.com/${userPoolId}`]: 'token',
- },
- });
});
test('should use identityPoolRegion param for credentials for guest', async () => {
- expect.assertions(2);
+ expect.assertions(1);
const credentials = new Credentials(null);
@@ -221,13 +197,12 @@ describe('Credentials test', () => {
await credentials._setCredentialsForGuest();
- expect(CognitoIdentityClient).toHaveBeenCalledWith(
- expect.objectContaining({ region: identityPoolRegion })
+ expect(getId).toBeCalledWith(
+ expect.objectContaining({ region: identityPoolRegion }),
+ {
+ IdentityPoolId: identityPoolId,
+ }
);
-
- expect(GetIdCommand).toBeCalledWith({
- IdentityPoolId: identityPoolId,
- });
});
});
diff --git a/packages/core/__tests__/Util-test.ts b/packages/core/__tests__/Util-test.ts
index 59ca2a9f38a..23ab3b84c58 100644
--- a/packages/core/__tests__/Util-test.ts
+++ b/packages/core/__tests__/Util-test.ts
@@ -6,15 +6,6 @@ import Reachability from '../src/Util/Reachability';
import { ConsoleLogger as Logger } from '../src/Logger';
import { urlSafeDecode, urlSafeEncode } from '../src/Util/StringUtils';
import { DateUtils } from '../src/Util/DateUtils';
-import {
- createCognitoIdentityClient,
- middlewareArgs,
-} from '../src/Util/CognitoIdentityClient';
-import { BuildMiddleware, HttpRequest } from '@aws-sdk/types';
-import {
- GetCredentialsForIdentityCommand,
- GetIdCommand,
-} from '@aws-sdk/client-cognito-identity';
Logger.LOG_LEVEL = 'DEBUG';
@@ -60,79 +51,6 @@ describe('Util', () => {
});
});
- describe('cognito identity client test', () => {
- test('client should be instantiated', async () => {
- const cognitoClient = createCognitoIdentityClient({
- region: 'us-west-1',
- });
- expect(cognitoClient).toBeTruthy();
- expect.assertions(1);
- });
-
- test('middlewareArgs helper should merge headers into request object', async () => {
- const args = middlewareArgs({
- request: {
- headers: {
- 'test-header': '1234',
- },
- },
- input: {},
- });
- expect(args.request.headers['test-header']).toEqual('1234');
- expect(args.request.headers['cache-control']).toEqual('no-store');
- expect.assertions(2);
- });
-
- test('headers should be added by middleware on GetIdCommand', async () => {
- const requestCacheHeaderValidator: BuildMiddleware =
- next => async args => {
- // middleware intercept the request and return it early
- const request = args.request as HttpRequest;
- const { headers } = request;
- expect(headers['cache-control']).toEqual('no-store');
- return { output: {} as any, response: {} as any };
- };
-
- const client = createCognitoIdentityClient({ region: 'us-west-1' });
- client.middlewareStack.addRelativeTo(requestCacheHeaderValidator, {
- relation: 'after',
- toMiddleware: 'cacheControlMiddleWare',
- });
-
- await client.send(
- new GetIdCommand({
- IdentityPoolId: 'us-west-1:12345678-1234-1234-1234-123456789000',
- })
- );
- expect.assertions(1);
- });
-
- test('headers should be added by middleware on GetCredentialsForIdentityCommand', async () => {
- const requestCacheHeaderValidator: BuildMiddleware =
- next => async args => {
- // middleware intercept the request and return it early
- const request = args.request as HttpRequest;
- const { headers } = request;
- expect(headers['cache-control']).toEqual('no-store');
- return { output: {} as any, response: {} as any };
- };
-
- const client = createCognitoIdentityClient({ region: 'us-west-1' });
- client.middlewareStack.addRelativeTo(requestCacheHeaderValidator, {
- relation: 'after',
- toMiddleware: 'cacheControlMiddleWare',
- });
- await client.send(
- new GetCredentialsForIdentityCommand({
- IdentityId: '1234',
- Logins: {},
- })
- );
-
- expect.assertions(1);
- });
- });
-
test('jitteredExponential retry happy case', async () => {
const resolveAt = 3;
let attempts = 0;
diff --git a/packages/core/__tests__/clients/composeApiHandler-test.ts b/packages/core/__tests__/clients/composeApiHandler-test.ts
new file mode 100644
index 00000000000..792d33b47bb
--- /dev/null
+++ b/packages/core/__tests__/clients/composeApiHandler-test.ts
@@ -0,0 +1,63 @@
+import { composeServiceApi } from '../../src/clients/internal/composeApiHandler';
+
+describe(composeServiceApi.name, () => {
+ const defaultRequest = {
+ url: new URL('https://a.b'),
+ method: 'GET',
+ headers: {},
+ };
+ const defaultResponse = { body: 'Response', statusCode: 200, headers: {} };
+ const defaultConfig = {
+ endpointResolver: jest.fn().mockReturnValue('https://a.b'),
+ };
+ test('should call transfer handler with resolved config', async () => {
+ const mockTransferHandler = jest.fn().mockResolvedValue(defaultResponse);
+ const config = {
+ ...defaultConfig,
+ foo: 'bar',
+ };
+ const api = composeServiceApi(
+ mockTransferHandler,
+ input => defaultRequest,
+ async output => ({
+ Result: 'from API',
+ }),
+ defaultConfig
+ );
+ const output = await api({ bar: 'baz', foo: 'foo' }, 'Input');
+ expect(mockTransferHandler).toBeCalledTimes(1);
+ expect(mockTransferHandler).toBeCalledWith(
+ defaultRequest,
+ expect.objectContaining({
+ bar: 'baz',
+ foo: 'foo',
+ })
+ );
+ });
+
+ test('should call serializer and deserializer', async () => {
+ const mockTransferHandler = jest.fn().mockResolvedValue(defaultResponse);
+ const defaultConfig = {
+ foo: 'bar',
+ endpointResolver: jest.fn().mockReturnValue('https://a.b'),
+ };
+ const mockSerializer = jest.fn().mockReturnValue({ body: 'Serialized' });
+ const mockDeserializer = jest.fn().mockReturnValue({
+ Result: 'from server',
+ });
+ const api = composeServiceApi(
+ mockTransferHandler,
+ mockSerializer,
+ mockDeserializer,
+ defaultConfig
+ );
+ const output = await api({ bar: 'baz', foo: 'foo' }, 'Input');
+ expect(mockSerializer).toBeCalledTimes(1);
+ expect(mockSerializer).toBeCalledWith(
+ 'Input',
+ defaultConfig.endpointResolver.mock.results[0].value
+ );
+ expect(mockDeserializer).toBeCalledTimes(1);
+ expect(mockDeserializer).toBeCalledWith(defaultResponse);
+ });
+});
diff --git a/packages/core/__tests__/clients/fetch-test.ts b/packages/core/__tests__/clients/fetch-test.ts
index fedcb4fe51a..506672a8000 100644
--- a/packages/core/__tests__/clients/fetch-test.ts
+++ b/packages/core/__tests__/clients/fetch-test.ts
@@ -3,7 +3,7 @@ jest.mock('isomorphic-unfetch', () => {
global['fetch'] = mockUnfetch;
});
-import { fetchTransferHandler } from '../../src/clients/fetch';
+import { fetchTransferHandler } from '../../src/clients/handlers/fetch';
describe(fetchTransferHandler.name, () => {
const mockBody = {
@@ -39,6 +39,14 @@ describe(fetchTransferHandler.name, () => {
);
});
+ test('should support headers', async () => {
+ mockFetchResponse.headers.forEach.mockImplementation((cb: any) => {
+ cb('foo', 'bar');
+ });
+ const { headers } = await fetchTransferHandler(mockRequest, {});
+ expect(headers).toEqual({ bar: 'foo' });
+ });
+
test('should support text() in response.body', async () => {
const { body } = await fetchTransferHandler(mockRequest, {});
if (!body) {
diff --git a/packages/core/__tests__/clients/retry-middleware-test.ts b/packages/core/__tests__/clients/retry-middleware-test.ts
index 327978c810c..3bbfe5f7b8b 100644
--- a/packages/core/__tests__/clients/retry-middleware-test.ts
+++ b/packages/core/__tests__/clients/retry-middleware-test.ts
@@ -1,17 +1,20 @@
import { HttpResponse, MiddlewareHandler } from '../../src/clients/types';
import { composeTransferHandler } from '../../src/clients/internal/composeTransferHandler';
-import { retry, RetryOptions } from '../../src/clients/middleware/retry';
+import {
+ retryMiddleware,
+ RetryOptions,
+} from '../../src/clients/middleware/retry';
jest.spyOn(global, 'setTimeout');
jest.spyOn(global, 'clearTimeout');
-describe(`${retry.name} middleware`, () => {
+describe(`${retryMiddleware.name} middleware`, () => {
beforeEach(() => {
jest.clearAllMocks();
});
const defaultRetryOptions = {
- retryDecider: () => true,
+ retryDecider: async () => true,
computeDelay: () => 1,
};
const defaultRequest = { url: new URL('https://a.b') };
@@ -21,7 +24,7 @@ describe(`${retry.name} middleware`, () => {
headers: {},
};
const getRetryableHandler = (nextHandler: MiddlewareHandler) =>
- composeTransferHandler<[RetryOptions]>(nextHandler, [retry]);
+ composeTransferHandler<[RetryOptions]>(nextHandler, [retryMiddleware]);
test('should retry specified times', async () => {
const nextHandler = jest.fn().mockResolvedValue(defaultResponse);
@@ -122,7 +125,7 @@ describe(`${retry.name} middleware`, () => {
const nextHandler = jest.fn().mockResolvedValue(defaultResponse);
const retryableHandler = getRetryableHandler(nextHandler);
const controller = new AbortController();
- const retryDecider = () => true;
+ const retryDecider = async () => true;
const computeDelay = jest.fn().mockImplementation(attempt => {
if (attempt === 1) {
setTimeout(() => controller.abort(), 100);
@@ -161,7 +164,7 @@ describe(`${retry.name} middleware`, () => {
const doubleRetryableHandler = composeTransferHandler<
[RetryOptions, {}, RetryOptions]
- >(coreHandler, [retry, betweenRetryMiddleware, retry]);
+ >(coreHandler, [retryMiddleware, betweenRetryMiddleware, retryMiddleware]);
const retryDecider = jest
.fn()
diff --git a/packages/core/package.json b/packages/core/package.json
index f303dbd8e59..a659c43c428 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -47,6 +47,7 @@
},
"homepage": "https://aws-amplify.github.io/",
"devDependencies": {
+ "@aws-sdk/client-cognito-identity": "3.6.1",
"@react-native-async-storage/async-storage": "1.15.17",
"find": "^0.2.7",
"genversion": "^2.2.0",
@@ -63,8 +64,6 @@
"dependencies": {
"@aws-crypto/sha256-js": "1.2.2",
"@aws-sdk/client-cloudwatch-logs": "3.6.1",
- "@aws-sdk/client-cognito-identity": "3.6.1",
- "@aws-sdk/credential-provider-cognito-identity": "3.6.1",
"@aws-sdk/types": "3.6.1",
"@aws-sdk/util-hex-encoding": "3.6.1",
"isomorphic-unfetch": "^3.0.0",
@@ -101,19 +100,25 @@
"name": "Core (Credentials)",
"path": "./lib-esm/index.js",
"import": "{ Credentials }",
- "limit": "35.5 kB"
+ "limit": "11.54 kB"
},
{
"name": "Custom clients (retry middleware)",
- "path": "./lib-esm/clients/middleware/retry.js",
- "import": "{ retry }",
- "limit": "1.23 kB"
+ "path": "./lib-esm/clients/middleware/retry/middleware.js",
+ "import": "{ retryMiddleware }",
+ "limit": "1.24 kB"
},
{
"name": "Custom clients (fetch handler)",
- "path": "./lib-esm/clients/fetch.js",
+ "path": "./lib-esm/clients/handlers/fetch.js",
"import": "{ fetchTransferHandler }",
"limit": "1.73 kB"
+ },
+ {
+ "name": "Custom clients (unauthenticated handler)",
+ "path": "./lib-esm/clients/handlers/unauthenticated.js",
+ "import": "{ unauthenticatedHandler }",
+ "limit": "2.75 kB"
}
],
"jest": {
diff --git a/packages/core/src/AwsClients/CognitoIdentity/base.ts b/packages/core/src/AwsClients/CognitoIdentity/base.ts
new file mode 100644
index 00000000000..7a06754bbfc
--- /dev/null
+++ b/packages/core/src/AwsClients/CognitoIdentity/base.ts
@@ -0,0 +1,83 @@
+import type {
+ Endpoint,
+ Headers,
+ HttpRequest,
+ HttpResponse,
+ Middleware,
+} from '../../clients/types';
+import { composeTransferHandler } from '../../clients/internal/composeTransferHandler';
+import { unauthenticatedHandler } from '../../clients/handlers/unauthenticated';
+import {
+ jitteredBackoff,
+ getRetryDecider,
+} from '../../clients/middleware/retry';
+import { parseJsonError } from '../../clients/serde/json';
+
+/**
+ * The service name used to sign requests if the API requires authentication.
+ */
+const SERVICE_NAME = 'cognito-identity';
+
+/**
+ * The endpoint resolver function that returns the endpoint URL for a given region.
+ */
+const endpointResolver = (endpointOptions: { region: string }) => ({
+ url: new URL(
+ `https://cognito-identity.${endpointOptions.region}.amazonaws.com`
+ ),
+});
+
+/**
+ * A Cognito Identity-specific middleware that disables caching for all requests.
+ */
+const disableCacheMiddleware: Middleware =
+ () => (next, context) =>
+ async function disableCacheMiddleware(request) {
+ request.headers['cache-control'] = 'no-store';
+ return next(request);
+ };
+
+/**
+ * A Cognito Identity-specific transfer handler that does NOT sign requests, and
+ * disables caching.
+ *
+ * @internal
+ */
+export const cognitoIdentityTransferHandler = composeTransferHandler<
+ [Parameters[0]],
+ HttpRequest,
+ HttpResponse,
+ typeof unauthenticatedHandler
+>(unauthenticatedHandler, [disableCacheMiddleware]);
+
+/**
+ * @internal
+ */
+export const defaultConfigs = {
+ service: SERVICE_NAME,
+ endpointResolver,
+ retryDecider: getRetryDecider(parseJsonError),
+ computeDelay: jitteredBackoff,
+};
+
+/**
+ * @internal
+ */
+export const sharedHeaders = (operation: string): Headers => ({
+ 'content-type': 'application/x-amz-json-1.1',
+ 'x-amz-target': `AWSCognitoIdentityService.${operation}`,
+});
+
+/**
+ * @internal
+ */
+export const buildHttpRpcRequest = (
+ { url }: Endpoint,
+ headers: Headers,
+ body: any
+): HttpRequest => ({
+ headers,
+ url,
+ body,
+ method: 'POST',
+});
diff --git a/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts b/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
new file mode 100644
index 00000000000..69baf86169f
--- /dev/null
+++ b/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
@@ -0,0 +1,62 @@
+import type {
+ GetCredentialsForIdentityCommandInput,
+ GetCredentialsForIdentityCommandOutput,
+ Credentials,
+} from '@aws-sdk/client-cognito-identity';
+import {
+ buildHttpRpcRequest,
+ cognitoIdentityTransferHandler,
+ defaultConfigs,
+ sharedHeaders,
+} from './base';
+import { composeServiceApi } from '../../clients/internal/composeApiHandler';
+import { Endpoint, HttpRequest, HttpResponse } from '../../clients/types';
+import {
+ parseJsonBody,
+ parseJsonError,
+ parseMetadata,
+} from '../../clients/serde';
+
+export type {
+ GetCredentialsForIdentityCommandInput,
+ GetCredentialsForIdentityCommandOutput,
+} from '@aws-sdk/client-cognito-identity';
+
+const getCredentialsForIdentitySerializer = (
+ input: GetCredentialsForIdentityCommandInput,
+ endpoint: Endpoint
+): HttpRequest => {
+ const headers = sharedHeaders('GetCredentialsForIdentity');
+ const body = JSON.stringify(input);
+ return buildHttpRpcRequest(endpoint, headers, body);
+};
+
+const getCredentialsForIdentityDeserializer = async (
+ response: HttpResponse
+): Promise => {
+ if (response.statusCode >= 300) {
+ const error = await parseJsonError(response);
+ throw error;
+ } else {
+ const body = await parseJsonBody(response);
+ return {
+ IdentityId: body.IdentityId,
+ Credentials: de_Credentials(body.Credentials),
+ $metadata: parseMetadata(response),
+ };
+ }
+};
+
+const de_Credentials = (output: unknown = {}): Credentials => ({
+ AccessKeyId: output['AccessKeyId'] as string,
+ SecretKey: output['SecretKey'] as string,
+ SessionToken: output['SessionToken'] as string,
+ Expiration: new Date((output['Expiration'] as number) * 1000),
+});
+
+export const getCredentialsForIdentity = composeServiceApi(
+ cognitoIdentityTransferHandler,
+ getCredentialsForIdentitySerializer,
+ getCredentialsForIdentityDeserializer,
+ defaultConfigs
+);
diff --git a/packages/core/src/AwsClients/CognitoIdentity/getId.ts b/packages/core/src/AwsClients/CognitoIdentity/getId.ts
new file mode 100644
index 00000000000..09f3917a736
--- /dev/null
+++ b/packages/core/src/AwsClients/CognitoIdentity/getId.ts
@@ -0,0 +1,53 @@
+import type {
+ GetIdCommandInput,
+ GetIdCommandOutput,
+} from '@aws-sdk/client-cognito-identity';
+import {
+ buildHttpRpcRequest,
+ cognitoIdentityTransferHandler,
+ defaultConfigs,
+ sharedHeaders,
+} from './base';
+import { composeServiceApi } from '../../clients/internal/composeApiHandler';
+import { Endpoint, HttpRequest, HttpResponse } from '../../clients/types';
+import {
+ parseJsonBody,
+ parseJsonError,
+ parseMetadata,
+} from '../../clients/serde';
+
+export type {
+ GetIdCommandInput,
+ GetIdCommandOutput,
+} from '@aws-sdk/client-cognito-identity';
+
+const getIdSerializer = (
+ input: GetIdCommandInput,
+ endpoint: Endpoint
+): HttpRequest => {
+ const headers = sharedHeaders('GetId');
+ const body = JSON.stringify(input);
+ return buildHttpRpcRequest(endpoint, headers, body);
+};
+
+const getIdDeserializer = async (
+ response: HttpResponse
+): Promise => {
+ if (response.statusCode >= 300) {
+ const error = await parseJsonError(response);
+ throw error;
+ } else {
+ const body = await parseJsonBody(response);
+ return {
+ IdentityId: body.IdentityId,
+ $metadata: parseMetadata(response),
+ };
+ }
+};
+
+export const getId = composeServiceApi(
+ cognitoIdentityTransferHandler,
+ getIdSerializer,
+ getIdDeserializer,
+ defaultConfigs
+);
diff --git a/packages/core/src/AwsClients/CognitoIdentity/index.ts b/packages/core/src/AwsClients/CognitoIdentity/index.ts
new file mode 100644
index 00000000000..5938830707b
--- /dev/null
+++ b/packages/core/src/AwsClients/CognitoIdentity/index.ts
@@ -0,0 +1,6 @@
+export { getId, GetIdCommandInput, GetIdCommandOutput } from './getId';
+export {
+ getCredentialsForIdentity,
+ GetCredentialsForIdentityCommandInput,
+ GetCredentialsForIdentityCommandOutput,
+} from './getCredentialsForIdentity';
diff --git a/packages/core/src/Credentials.ts b/packages/core/src/Credentials.ts
index b82357c304d..32f4b08d1ff 100644
--- a/packages/core/src/Credentials.ts
+++ b/packages/core/src/Credentials.ts
@@ -5,20 +5,9 @@ import { FacebookOAuth, GoogleOAuth } from './OAuthHelper';
import { jitteredExponentialRetry } from './Util';
import { ICredentials } from './types';
import { Amplify } from './Amplify';
-import {
- fromCognitoIdentity,
- FromCognitoIdentityParameters,
- fromCognitoIdentityPool,
- FromCognitoIdentityPoolParameters,
-} from '@aws-sdk/credential-provider-cognito-identity';
-import {
- GetIdCommand,
- GetCredentialsForIdentityCommand,
-} from '@aws-sdk/client-cognito-identity';
-import { CredentialProvider } from '@aws-sdk/types';
+import { getId, getCredentialsForIdentity } from './AwsClients/CognitoIdentity';
import { parseAWSExports } from './parseAWSExports';
import { Hub } from './Hub';
-import { createCognitoIdentityClient } from './Util/CognitoIdentityClient';
const logger = new Logger('Credentials');
@@ -291,47 +280,29 @@ export class CredentialsClass {
const identityId = (this._identityId = await this._getGuestIdentityId());
- const cognitoClient = createCognitoIdentityClient({
- region: identityPoolRegion || region,
- });
+ const cognitoConfig = { region: identityPoolRegion ?? region };
- let credentials = undefined;
- if (identityId) {
- const cognitoIdentityParams: FromCognitoIdentityParameters = {
- identityId,
- client: cognitoClient,
- };
- credentials = fromCognitoIdentity(cognitoIdentityParams)();
- } else {
- /*
- Retreiving identityId with GetIdCommand to mimic the behavior in the following code in aws-sdk-v3:
- https://git.io/JeDxU
-
- Note: Retreive identityId from CredentialsProvider once aws-sdk-js v3 supports this.
- */
- const credentialsProvider: CredentialProvider = async () => {
- const { IdentityId } = await cognitoClient.send(
- new GetIdCommand({
- IdentityPoolId: identityPoolId,
- })
- );
+ const credentialsProvider = async () => {
+ if (!identityId) {
+ const { IdentityId } = await getId(cognitoConfig, {
+ IdentityPoolId: identityPoolId,
+ });
this._identityId = IdentityId;
- const cognitoIdentityParams: FromCognitoIdentityParameters = {
- client: cognitoClient,
- identityId: IdentityId,
- };
-
- const credentialsFromCognitoIdentity = fromCognitoIdentity(
- cognitoIdentityParams
- );
-
- return credentialsFromCognitoIdentity();
- };
-
- credentials = credentialsProvider().catch(async err => {
- throw err;
+ }
+ const { Credentials } = await getCredentialsForIdentity(cognitoConfig, {
+ IdentityId: this._identityId,
});
- }
+ return {
+ identityId: this._identityId,
+ accessKeyId: Credentials.AccessKeyId,
+ secretAccessKey: Credentials.SecretKey,
+ sessionToken: Credentials.SessionToken,
+ expiration: Credentials.Expiration,
+ };
+ };
+ let credentials = credentialsProvider().catch(async err => {
+ throw err;
+ });
return this._loadCredentials(credentials, 'guest', false, null)
.then(res => {
@@ -347,23 +318,25 @@ export class CredentialsClass {
logger.debug('Failed to load guest credentials');
await this._removeGuestIdentityId();
- const credentialsProvider: CredentialProvider = async () => {
- const { IdentityId } = await cognitoClient.send(
- new GetIdCommand({
- IdentityPoolId: identityPoolId,
- })
- );
+ const credentialsProvider = async () => {
+ const { IdentityId } = await getId(cognitoConfig, {
+ IdentityPoolId: identityPoolId,
+ });
this._identityId = IdentityId;
- const cognitoIdentityParams: FromCognitoIdentityParameters = {
- client: cognitoClient,
- identityId: IdentityId,
- };
-
- const credentialsFromCognitoIdentity = fromCognitoIdentity(
- cognitoIdentityParams
+ const { Credentials } = await getCredentialsForIdentity(
+ cognitoConfig,
+ {
+ IdentityId,
+ }
);
- return credentialsFromCognitoIdentity();
+ return {
+ identityId: IdentityId,
+ accessKeyId: Credentials.AccessKeyId,
+ secretAccessKey: Credentials.SecretKey,
+ sessionToken: Credentials.SessionToken,
+ expiration: Credentials.Expiration,
+ };
};
credentials = credentialsProvider().catch(async err => {
@@ -378,7 +351,8 @@ export class CredentialsClass {
}
private _setCredentialsFromFederation(params) {
- const { provider, token, identity_id } = params;
+ const { provider, token } = params;
+ let { identity_id } = params;
const domains = {
google: 'accounts.google.com',
facebook: 'graph.facebook.com',
@@ -407,26 +381,33 @@ export class CredentialsClass {
);
}
- const cognitoClient = createCognitoIdentityClient({
- region: identityPoolRegion || region,
- });
+ const cognitoConfig = { region: identityPoolRegion ?? region };
- let credentials = undefined;
- if (identity_id) {
- const cognitoIdentityParams: FromCognitoIdentityParameters = {
+ const credentialsProvider = async () => {
+ if (!identity_id) {
+ const { IdentityId } = await getId(cognitoConfig, {
+ IdentityPoolId: identityPoolId,
+ Logins: logins,
+ });
+ identity_id = IdentityId;
+ }
+ const { Credentials } = await getCredentialsForIdentity(cognitoConfig, {
+ IdentityId: identity_id,
+ Logins: logins,
+ });
+ return {
identityId: identity_id,
- logins,
- client: cognitoClient,
- };
- credentials = fromCognitoIdentity(cognitoIdentityParams)();
- } else {
- const cognitoIdentityParams: FromCognitoIdentityPoolParameters = {
- logins,
- identityPoolId,
- client: cognitoClient,
+ accessKeyId: Credentials.AccessKeyId,
+ secretAccessKey: Credentials.SecretKey,
+ sessionToken: Credentials.SessionToken,
+ expiration: Credentials.Expiration,
};
- credentials = fromCognitoIdentityPool(cognitoIdentityParams)();
- }
+ };
+
+ const credentials = credentialsProvider().catch(async err => {
+ throw err;
+ });
+
return this._loadCredentials(credentials, 'federated', true, params);
}
@@ -449,9 +430,7 @@ export class CredentialsClass {
const logins = {};
logins[key] = idToken;
- const cognitoClient = createCognitoIdentityClient({
- region: identityPoolRegion || region,
- });
+ const cognitoConfig = { region: identityPoolRegion ?? region };
/*
Retreiving identityId with GetIdCommand to mimic the behavior in the following code in aws-sdk-v3:
@@ -459,7 +438,7 @@ export class CredentialsClass {
Note: Retreive identityId from CredentialsProvider once aws-sdk-js v3 supports this.
*/
- const credentialsProvider: CredentialProvider = async () => {
+ const credentialsProvider = async () => {
// try to fetch the local stored guest identity, if found, we will associate it with the logins
const guestIdentityId = await this._getGuestIdentityId();
@@ -467,12 +446,10 @@ export class CredentialsClass {
if (!guestIdentityId) {
// for a first-time user, this will return a brand new identity
// for a returning user, this will retrieve the previous identity assocaited with the logins
- const { IdentityId } = await cognitoClient.send(
- new GetIdCommand({
- IdentityPoolId: identityPoolId,
- Logins: logins,
- })
- );
+ const { IdentityId } = await getId(cognitoConfig, {
+ IdentityPoolId: identityPoolId,
+ Logins: logins,
+ });
generatedOrRetrievedIdentityId = IdentityId;
}
@@ -481,12 +458,10 @@ export class CredentialsClass {
// single source of truth for the primary identity associated with the logins
// only if a guest identity is used for a first-time user, that guest identity will become its primary identity
IdentityId: primaryIdentityId,
- } = await cognitoClient.send(
- new GetCredentialsForIdentityCommand({
- IdentityId: guestIdentityId || generatedOrRetrievedIdentityId,
- Logins: logins,
- })
- );
+ } = await getCredentialsForIdentity(cognitoConfig, {
+ IdentityId: guestIdentityId || generatedOrRetrievedIdentityId,
+ Logins: logins,
+ });
this._identityId = primaryIdentityId;
if (guestIdentityId) {
diff --git a/packages/core/src/Util/CognitoIdentityClient.ts b/packages/core/src/Util/CognitoIdentityClient.ts
deleted file mode 100644
index ded7d0346a0..00000000000
--- a/packages/core/src/Util/CognitoIdentityClient.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-import {
- CognitoIdentityClient,
- CognitoIdentityClientConfig,
-} from '@aws-sdk/client-cognito-identity';
-import { Provider } from '@aws-sdk/types';
-import { getAmplifyUserAgent } from '../Platform';
-
-/**
- * Returns a CognitoIdentityClient with middleware
- * @param {CognitoIdentityClientConfig} config
- * @return {CognitoIdentityClient}
- */
-export function createCognitoIdentityClient(
- config: CognitoIdentityClientConfig
-): CognitoIdentityClient {
- const client = new CognitoIdentityClient({
- region: config.region,
- customUserAgent: getAmplifyUserAgent(),
- });
-
- client.middlewareStack.add(
- (next, _) => (args: any) => {
- return next(middlewareArgs(args));
- },
- {
- step: 'build',
- name: 'cacheControlMiddleWare',
- }
- );
-
- return client;
-}
-
-export function middlewareArgs(args: { request: any; input: any }) {
- return {
- ...args,
- request: {
- ...args.request,
- headers: {
- ...args.request.headers,
- 'cache-control': 'no-store',
- },
- },
- };
-}
diff --git a/packages/core/src/clients/fetch.ts b/packages/core/src/clients/handlers/fetch.ts
similarity index 60%
rename from packages/core/src/clients/fetch.ts
rename to packages/core/src/clients/handlers/fetch.ts
index cdaf8231703..b3eeb71bc4d 100644
--- a/packages/core/src/clients/fetch.ts
+++ b/packages/core/src/clients/handlers/fetch.ts
@@ -1,6 +1,6 @@
import 'isomorphic-unfetch'; // TODO: remove this dependency in v6
-import { HttpRequest, HttpResponse, HttpTransferOptions } from './types/http';
-import { TransferHandler } from './types/core';
+import { HttpRequest, HttpResponse, HttpTransferOptions } from '../types/http';
+import { TransferHandler } from '../types/core';
const shouldSendBody = (method: string) =>
!['HEAD', 'GET', 'DELETE'].includes(method.toUpperCase());
@@ -21,31 +21,31 @@ export const fetchTransferHandler: TransferHandler<
} catch (e) {
// TODO: needs to revise error handling in v6
if (e instanceof TypeError) {
- throw new Error(`Network error`);
+ throw new Error('Network error');
}
throw e;
}
- const headersBag = {};
- resp.headers?.forEach((key, value) => {
- headersBag[key.toLowerCase()] = value;
+ const headers = {};
+ resp.headers?.forEach((value, key) => {
+ headers[key.toLowerCase()] = value;
});
const httpResponse = {
statusCode: resp.status,
- headers: headersBag,
+ headers,
body: null,
};
- if (resp.body) {
- const bodyWithMixin = Object.assign(resp.body, {
- text: () => resp.text(),
- blob: () => resp.blob(),
- json: () => resp.json(),
- });
- return {
- ...httpResponse,
- body: bodyWithMixin,
- };
- }
- return httpResponse;
+ // resp.body is a ReadableStream according to Fetch API spec, but React Native
+ // does not implement it.
+ const bodyWithMixin = Object.assign(resp.body ?? {}, {
+ text: () => resp.text(),
+ blob: () => resp.blob(),
+ json: () => resp.json(),
+ });
+
+ return {
+ ...httpResponse,
+ body: bodyWithMixin,
+ };
};
diff --git a/packages/core/src/clients/handlers/unauthenticated.ts b/packages/core/src/clients/handlers/unauthenticated.ts
new file mode 100644
index 00000000000..8370b3bf7ef
--- /dev/null
+++ b/packages/core/src/clients/handlers/unauthenticated.ts
@@ -0,0 +1,11 @@
+import { retryMiddleware, RetryOptions } from '../middleware/retry';
+import { userAgentMiddleware, UserAgentOptions } from '../middleware/userAgent';
+import { composeTransferHandler } from '../internal/composeTransferHandler';
+import { fetchTransferHandler } from './fetch';
+import { HttpRequest, HttpResponse } from '../types';
+
+export const unauthenticatedHandler = composeTransferHandler<
+ [UserAgentOptions, RetryOptions],
+ HttpRequest,
+ HttpResponse
+>(fetchTransferHandler, [userAgentMiddleware, retryMiddleware]);
diff --git a/packages/core/src/clients/internal/composeApiHandler.ts b/packages/core/src/clients/internal/composeApiHandler.ts
new file mode 100644
index 00000000000..5bef77695a0
--- /dev/null
+++ b/packages/core/src/clients/internal/composeApiHandler.ts
@@ -0,0 +1,44 @@
+import { ServiceClientOptions } from '../types/aws';
+import { TransferHandler, Endpoint } from '../types/core';
+import { HttpRequest, HttpResponse } from '../types/http';
+
+export const composeServiceApi = <
+ TransferHandlerOptions,
+ Input,
+ Output,
+ DefaultConfig extends Partial
+>(
+ transferHandler: TransferHandler<
+ HttpRequest,
+ HttpResponse,
+ TransferHandlerOptions
+ >,
+ serializer: (input: Input, endpoint: Endpoint) => HttpRequest,
+ deserializer: (output: HttpResponse) => Promise,
+ defaultConfig: DefaultConfig
+) => {
+ return async (
+ config: OptionalizeKey<
+ TransferHandlerOptions & ServiceClientOptions,
+ keyof DefaultConfig
+ >,
+ input: Input
+ ) => {
+ const resolvedConfig = {
+ ...defaultConfig,
+ ...config,
+ } as unknown as TransferHandlerOptions & ServiceClientOptions;
+ const endpoint = await resolvedConfig.endpointResolver({
+ region: resolvedConfig.region,
+ });
+ const request = serializer(input, endpoint);
+ const response = await transferHandler(request, {
+ ...resolvedConfig,
+ });
+ return await deserializer(response);
+ };
+};
+
+type OptionalizeKey = Omit & {
+ [P in K & keyof T]?: T[P];
+};
diff --git a/packages/core/src/clients/middleware/retry/defaultRetryDecider.ts b/packages/core/src/clients/middleware/retry/defaultRetryDecider.ts
new file mode 100644
index 00000000000..14fa4d319ec
--- /dev/null
+++ b/packages/core/src/clients/middleware/retry/defaultRetryDecider.ts
@@ -0,0 +1,63 @@
+import { HttpResponse, ErrorParser } from '../../types';
+
+/**
+ * Get retry decider function
+ * @param errorParser Function to load JavaScript error from HTTP response
+ */
+export const getRetryDecider =
+ (errorParser: ErrorParser) =>
+ async (response?: HttpResponse, error?: Error): Promise => {
+ const { name: errorCode } = error ?? (await errorParser(response)) ?? {};
+ const statusCode = response?.statusCode;
+ return (
+ isConnectionError(error) ||
+ isThrottlingError(statusCode, errorCode) ||
+ isClockSkewError(errorCode) ||
+ isServerSideError(statusCode, errorCode)
+ );
+ };
+
+// reference: https://github.com/aws/aws-sdk-js-v3/blob/ab0e7be36e7e7f8a0c04834357aaad643c7912c3/packages/service-error-classification/src/constants.ts#L22-L37
+const THROTTLING_ERROR_CODES = [
+ 'BandwidthLimitExceeded',
+ 'EC2ThrottledException',
+ 'LimitExceededException',
+ 'PriorRequestNotComplete',
+ 'ProvisionedThroughputExceededException',
+ 'RequestLimitExceeded',
+ 'RequestThrottled',
+ 'RequestThrottledException',
+ 'SlowDown',
+ 'ThrottledException',
+ 'Throttling',
+ 'ThrottlingException',
+ 'TooManyRequestsException',
+];
+
+const CLOCK_SKEW_ERROR_CODES = [
+ 'AuthFailure',
+ 'InvalidSignatureException',
+ 'RequestExpired',
+ 'RequestInTheFuture',
+ 'RequestTimeTooSkewed',
+ 'SignatureDoesNotMatch',
+ 'BadRequestException', // API Gateway
+];
+
+const TIMEOUT_ERROR_CODES = [
+ 'TimeoutError',
+ 'RequestTimeout',
+ 'RequestTimeoutException',
+];
+
+const isThrottlingError = (statusCode?: number, errorCode?: string) =>
+ statusCode === 429 || THROTTLING_ERROR_CODES.includes(errorCode);
+
+const isConnectionError = (error?: Error) => error?.name === 'Network error';
+
+const isClockSkewError = (errorCode?: string) =>
+ CLOCK_SKEW_ERROR_CODES.includes(errorCode);
+
+const isServerSideError = (statusCode?: number, errorCode?: string) =>
+ [500, 502, 503, 504].includes(statusCode) ||
+ TIMEOUT_ERROR_CODES.includes(errorCode);
diff --git a/packages/core/src/clients/middleware/retry/index.ts b/packages/core/src/clients/middleware/retry/index.ts
new file mode 100644
index 00000000000..1d68913ee9e
--- /dev/null
+++ b/packages/core/src/clients/middleware/retry/index.ts
@@ -0,0 +1,3 @@
+export { RetryOptions, retryMiddleware } from './middleware';
+export { jitteredBackoff } from './jitteredBackoff';
+export { getRetryDecider } from './defaultRetryDecider';
diff --git a/packages/core/src/clients/middleware/retry/jitteredBackoff.ts b/packages/core/src/clients/middleware/retry/jitteredBackoff.ts
new file mode 100644
index 00000000000..d7c04dcf76e
--- /dev/null
+++ b/packages/core/src/clients/middleware/retry/jitteredBackoff.ts
@@ -0,0 +1,11 @@
+import type { RetryOptions } from './middleware';
+// TODO: remove this dependency in v6
+import { jitteredBackoff as jitteredBackoffUtil } from '../../../Util/Retry';
+
+const MAX_DELAY_MS = 5 * 60 * 1000;
+
+export const jitteredBackoff: RetryOptions['computeDelay'] = attempt => {
+ const delayFunction = jitteredBackoffUtil();
+ const delay = delayFunction(attempt);
+ return delay === false ? MAX_DELAY_MS : delay;
+};
diff --git a/packages/core/src/clients/middleware/retry.ts b/packages/core/src/clients/middleware/retry/middleware.ts
similarity index 82%
rename from packages/core/src/clients/middleware/retry.ts
rename to packages/core/src/clients/middleware/retry/middleware.ts
index 8350bd5cdbb..c7b1ddfd7a4 100644
--- a/packages/core/src/clients/middleware/retry.ts
+++ b/packages/core/src/clients/middleware/retry/middleware.ts
@@ -4,14 +4,14 @@ import {
MiddlewareHandler,
Request,
Response,
-} from '../types/core';
+} from '../../types/core';
const DEFAULT_RETRY_ATTEMPTS = 3;
/**
* Configuration of the retry middleware
*/
-export interface RetryOptions {
+export interface RetryOptions {
/**
* Function to decide if the request should be retried.
*
@@ -19,7 +19,7 @@ export interface RetryOptions {
* @param error Optional error thrown from previous attempts.
* @returns True if the request should be retried.
*/
- retryDecider: (response?: Response, error?: unknown) => boolean;
+ retryDecider: (response?: TResponse, error?: unknown) => Promise;
/**
* Function to compute the delay in milliseconds before the next retry based
* on the number of attempts.
@@ -40,15 +40,17 @@ export interface RetryOptions {
/**
* Retry middleware
*/
-export const retry = (options: RetryOptions) => {
+export const retryMiddleware = (
+ options: RetryOptions
+) => {
if (options.maxAttempts < 1) {
throw new Error('maxAttempts must be greater than 0');
}
return (
- next: MiddlewareHandler,
+ next: MiddlewareHandler,
context: MiddlewareContext
) =>
- async function retry(request: Request) {
+ async function retryMiddleware(request: TInput) {
const {
maxAttempts = DEFAULT_RETRY_ATTEMPTS,
retryDecider,
@@ -57,7 +59,7 @@ export const retry = (options: RetryOptions) => {
} = options;
let error: Error;
let attemptsCount = context.attemptsCount ?? 0;
- let response: Response;
+ let response: TOutput;
while (!abortSignal?.aborted && attemptsCount < maxAttempts) {
error = undefined;
response = undefined;
@@ -72,7 +74,7 @@ export const retry = (options: RetryOptions) => {
? context.attemptsCount
: attemptsCount + 1;
context.attemptsCount = attemptsCount;
- if (retryDecider(response, error)) {
+ if (await retryDecider(response, error)) {
if (!abortSignal?.aborted && attemptsCount < maxAttempts) {
// prevent sleep for last attempt or cancelled request;
const delay = computeDelay(attemptsCount);
@@ -111,8 +113,14 @@ const cancellableSleep = (timeoutMs: number, abortSignal?: AbortSignal) => {
return sleepPromise;
};
-const isMetadataBearer = (response: unknown): response is MetadataBearer =>
- typeof response?.['$metadata'] === 'object';
+/**
+ * Check if the response is a contains `$metadata` property.
+ *
+ * @internal Amplify internal use only
+ */
+export const isMetadataBearer = (
+ response: unknown
+): response is MetadataBearer => typeof response?.['$metadata'] === 'object';
const updateMetadataAttempts = (
nextHandlerOutput: Object,
diff --git a/packages/core/src/clients/middleware/userAgent/index.ts b/packages/core/src/clients/middleware/userAgent/index.ts
new file mode 100644
index 00000000000..db583db354b
--- /dev/null
+++ b/packages/core/src/clients/middleware/userAgent/index.ts
@@ -0,0 +1 @@
+export { userAgentMiddleware, UserAgentOptions } from './middleware';
diff --git a/packages/core/src/clients/middleware/userAgent/middleware.ts b/packages/core/src/clients/middleware/userAgent/middleware.ts
new file mode 100644
index 00000000000..0e582f246cb
--- /dev/null
+++ b/packages/core/src/clients/middleware/userAgent/middleware.ts
@@ -0,0 +1,33 @@
+import { HttpRequest, HttpResponse } from '../../types/http';
+import { Middleware } from '../../types/core';
+
+export interface UserAgentOptions {
+ userAgentHeader?: string;
+ userAgentValue?: string;
+}
+
+// TODO: incorporate new user agent design
+export const userAgentMiddleware: Middleware<
+ HttpRequest,
+ HttpResponse,
+ UserAgentOptions
+> =
+ ({
+ userAgentHeader = 'x-amz-user-agent',
+ userAgentValue = '',
+ }: UserAgentOptions) =>
+ next => {
+ return async function userAgentMiddleware(request) {
+ if (userAgentValue.trim().length === 0) {
+ const result = await next(request);
+ return result;
+ } else {
+ const headerName = userAgentHeader.toLowerCase();
+ request.headers[headerName] = request.headers[headerName]
+ ? `${request.headers[headerName]} ${userAgentValue}`
+ : userAgentValue;
+ const response = await next(request);
+ return response;
+ }
+ };
+ };
diff --git a/packages/core/src/clients/serde/index.ts b/packages/core/src/clients/serde/index.ts
new file mode 100644
index 00000000000..b1fa64e5ab4
--- /dev/null
+++ b/packages/core/src/clients/serde/index.ts
@@ -0,0 +1,2 @@
+export { parseMetadata } from './responseInfo';
+export { parseJsonBody, parseJsonError } from './json';
diff --git a/packages/core/src/clients/serde/json.ts b/packages/core/src/clients/serde/json.ts
new file mode 100644
index 00000000000..e4b0b860a55
--- /dev/null
+++ b/packages/core/src/clients/serde/json.ts
@@ -0,0 +1,50 @@
+import { ErrorParser, HttpResponse } from '../types';
+import { parseMetadata } from './responseInfo';
+
+/**
+ * Utility functions for serializing and deserializing of JSON protocol in general(including: REST-JSON, JSON-RPC, etc.)
+ * The utility functions here must be mindful of only reading the response body once for each response in any
+ * deserializer code path.
+ */
+
+/**
+ * Error parser for AWS JSON protocol.
+ */
+export const parseJsonError: ErrorParser = async (response?: HttpResponse) => {
+ if (!response || response.statusCode < 300) {
+ return;
+ }
+ const body = await parseJsonBody(response);
+ const sanitizeErrorCode = (rawValue: string | number): string => {
+ const [cleanValue] = rawValue.toString().split(/[\,\:]+/);
+ if (cleanValue.includes('#')) {
+ return cleanValue.split('#')[1];
+ }
+ return cleanValue;
+ };
+ const code = sanitizeErrorCode(
+ response.headers['x-amzn-errortype'] ??
+ body.code ??
+ body.__type ??
+ 'UnknownError'
+ );
+ const message = body.message ?? body.Message ?? 'Unknown error';
+ const error = new Error(message);
+ return Object.assign(error, {
+ name: code,
+ $metadata: parseMetadata(response),
+ });
+};
+
+/**
+ * Parse JSON response body to JavaScript object.
+ */
+export const parseJsonBody = async (response: HttpResponse): Promise => {
+ if (!response.body) {
+ throw new Error('Missing response payload');
+ }
+ const output = await response.body.json();
+ return Object.assign(output, {
+ $metadata: parseMetadata(response),
+ });
+};
diff --git a/packages/core/src/clients/serde/responseInfo.ts b/packages/core/src/clients/serde/responseInfo.ts
new file mode 100644
index 00000000000..3298b1e584c
--- /dev/null
+++ b/packages/core/src/clients/serde/responseInfo.ts
@@ -0,0 +1,17 @@
+import { ResponseMetadata } from '@aws-sdk/types';
+import { isMetadataBearer } from '../middleware/retry/middleware';
+import { HttpResponse } from '../types/http';
+
+export const parseMetadata = (response: HttpResponse): ResponseMetadata => {
+ const { headers, statusCode } = response;
+ return {
+ ...(isMetadataBearer(response) ? response.$metadata : {}),
+ httpStatusCode: statusCode,
+ requestId:
+ headers['x-amzn-requestid'] ??
+ headers['x-amzn-request-id'] ??
+ headers['x-amz-request-id'],
+ extendedRequestId: headers['x-amz-id-2'],
+ cfId: headers['x-amz-cf-id'],
+ };
+};
diff --git a/packages/core/src/clients/types/aws.ts b/packages/core/src/clients/types/aws.ts
new file mode 100644
index 00000000000..c01a33ca76e
--- /dev/null
+++ b/packages/core/src/clients/types/aws.ts
@@ -0,0 +1,19 @@
+import { Endpoint } from './core';
+import { HttpResponse } from './http';
+
+export type { Credentials } from '@aws-sdk/types';
+
+export type SourceData = string | ArrayBuffer | ArrayBufferView;
+
+export interface ServiceClientOptions {
+ region: string;
+ endpointResolver: (input: { region: string }) => Endpoint;
+}
+
+/**
+ * parse errors from given response. If no error code is found, return undefined.
+ * This function is protocol-specific (e.g. JSON, XML, etc.)
+ */
+export type ErrorParser = (
+ response?: HttpResponse
+) => Promise;
diff --git a/packages/core/src/clients/types/core.ts b/packages/core/src/clients/types/core.ts
index f7605fccf22..31d94fea24c 100644
--- a/packages/core/src/clients/types/core.ts
+++ b/packages/core/src/clients/types/core.ts
@@ -21,11 +21,14 @@ export interface TransferHandler<
/**
* A slimmed down version of the AWS SDK v3 middleware handler, only handling instantiated requests
*/
-export type MiddlewareHandler<
- Input extends Request,
- Output extends Response
-> = (request: Input) => Promise;
+export type MiddlewareHandler = (
+ request: Input
+) => Promise;
+/**
+ * The context object to store states across the middleware chain and retry
+ * attempts if retry middleware exists.
+ */
export type MiddlewareContext = {
/**
* The number of times the request has been attempted. This is set by retry middleware
@@ -46,3 +49,7 @@ export type Middleware<
Output extends Response,
MiddlewareOptions
> = (options: MiddlewareOptions) => ConfiguredMiddleware ;
+
+export interface Endpoint {
+ url: URL;
+}
diff --git a/packages/core/src/clients/types/http.ts b/packages/core/src/clients/types/http.ts
index c6f5753a235..eb23f2ce9f0 100644
--- a/packages/core/src/clients/types/http.ts
+++ b/packages/core/src/clients/types/http.ts
@@ -21,7 +21,7 @@ export interface HttpRequest extends Request {
export type ResponseBodyMixin = Pick;
export interface HttpResponse extends Response {
- body: (ResponseBodyMixin & ReadableStream) | null;
+ body: (ResponseBodyMixin & ReadableStream) | ResponseBodyMixin;
statusCode: number;
/**
* @see {@link HttpRequest.headers}
diff --git a/packages/core/src/clients/types/index.ts b/packages/core/src/clients/types/index.ts
index 6cbcd31edaa..007a461994b 100644
--- a/packages/core/src/clients/types/index.ts
+++ b/packages/core/src/clients/types/index.ts
@@ -4,6 +4,7 @@ export {
Request,
Response,
TransferHandler,
+ Endpoint,
} from './core';
export {
Headers,
@@ -13,3 +14,4 @@ export {
HttpTransferOptions,
ResponseBodyMixin,
} from './http';
+export { Credentials, ServiceClientOptions, ErrorParser } from './aws';
diff --git a/packages/datastore/package.json b/packages/datastore/package.json
index d2b475a6fda..16978cff0c3 100644
--- a/packages/datastore/package.json
+++ b/packages/datastore/package.json
@@ -72,7 +72,7 @@
"name": "DataStore (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, DataStore }",
- "limit": "155 kB"
+ "limit": "135.5 kB"
}
],
"jest": {
diff --git a/packages/geo/package.json b/packages/geo/package.json
index 8f653bba16b..e31bbd73e72 100644
--- a/packages/geo/package.json
+++ b/packages/geo/package.json
@@ -57,7 +57,7 @@
"name": "Geo (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Geo }",
- "limit": "65 kB"
+ "limit": "50.2 kB"
}
],
"jest": {
diff --git a/packages/interactions/package.json b/packages/interactions/package.json
index 299d7e3c782..9ecece35138 100644
--- a/packages/interactions/package.json
+++ b/packages/interactions/package.json
@@ -59,7 +59,7 @@
"name": "Interactions (top-level class with Lex v2)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Interactions, AWSLexV2Provider }",
- "limit": "87 kB"
+ "limit": "74.1 kB"
}
],
"jest": {
diff --git a/packages/notifications/package.json b/packages/notifications/package.json
index 93267d11276..0f10696f4aa 100644
--- a/packages/notifications/package.json
+++ b/packages/notifications/package.json
@@ -60,13 +60,13 @@
"name": "Notifications (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Notifications }",
- "limit": "71 kB"
+ "limit": "59 kB"
},
{
"name": "Notifications (with Analytics)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Notifications, Analytics }",
- "limit": "71 kB"
+ "limit": "59 kB"
}
],
"devDependencies": {
diff --git a/packages/pubsub/package.json b/packages/pubsub/package.json
index bd4895ea6e4..4b2528959a7 100644
--- a/packages/pubsub/package.json
+++ b/packages/pubsub/package.json
@@ -63,13 +63,13 @@
"name": "PubSub (IoT provider)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, PubSub, AWSIoTProvider }",
- "limit": "97.5 kB"
+ "limit": "78.5 kB"
},
{
"name": "PubSub (Mqtt provider)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, PubSub, MqttOverWSProvider }",
- "limit": "97.5 kB"
+ "limit": "78.4 kB"
}
],
"jest": {
From bc79b21c62688cb1d53d9177156b0333ac9792d2 Mon Sep 17 00:00:00 2001
From: AllanZhengYP
Date: Tue, 18 Apr 2023 11:42:11 -0700
Subject: [PATCH 07/41] chore(clients): enable tagged release of custom clients
(#11267)
---
.circleci/config.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index e163bf6d756..6978303b8a2 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1615,6 +1615,7 @@ releasable_branches: &releasable_branches
- release
- main
- next
+ - v5/custom-clients
# List of test browsers that are always used in every E2E test. Tests that aren't expected to interact with browser APIs
# should use `minimal_browser_list` to keep test execution time low.
From b5938877563e9bd02791501a6569757083d2e88a Mon Sep 17 00:00:00 2001
From: AllanZhengYP
Date: Tue, 18 Apr 2023 14:10:16 -0700
Subject: [PATCH 08/41] test(clients): add functional test to cognito identity
client (#11266)
* test(clients): add cognito-identity functional test
* chore(clients): prefer destructuring parameters at top
---
.../AwsClients/CognitoIdentity-test.ts | 210 ++++++++++++++++++
.../getCredentialsForIdentity.ts | 12 +-
.../src/AwsClients/CognitoIdentity/getId.ts | 14 +-
.../src/AwsClients/CognitoIdentity/index.ts | 6 +-
packages/core/src/clients/handlers/fetch.ts | 18 +-
.../src/clients/internal/composeApiHandler.ts | 3 +
.../clients/middleware/retry/middleware.ts | 17 +-
7 files changed, 245 insertions(+), 35 deletions(-)
create mode 100644 packages/core/__tests__/AwsClients/CognitoIdentity-test.ts
diff --git a/packages/core/__tests__/AwsClients/CognitoIdentity-test.ts b/packages/core/__tests__/AwsClients/CognitoIdentity-test.ts
new file mode 100644
index 00000000000..6f27f5ce4e8
--- /dev/null
+++ b/packages/core/__tests__/AwsClients/CognitoIdentity-test.ts
@@ -0,0 +1,210 @@
+/**
+ * @jest-environment jsdom
+ */
+
+import { fetchTransferHandler } from '../../src/clients/handlers/fetch';
+import {
+ getId,
+ GetIdInput,
+ getCredentialsForIdentity,
+ GetIdOutput,
+ GetCredentialsForIdentityInput,
+ GetCredentialsForIdentityOutput,
+} from '../../src/AwsClients/CognitoIdentity';
+import { HttpResponse } from '../../src/clients/types';
+jest.mock('../../src/clients/handlers/fetch');
+
+const mockJsonResponse = ({
+ status,
+ headers,
+ body,
+}: {
+ status: number;
+ headers: Record;
+ body: any;
+}): HttpResponse => {
+ const responseBody = {
+ json: async () => body,
+ blob: async () => fail('blob() should not be called'),
+ text: async () => fail('text() should not be called'),
+ } as HttpResponse['body'];
+ return {
+ statusCode: status,
+ headers,
+ body: responseBody,
+ };
+};
+
+describe('cognito-identity service client', () => {
+ const REQUEST_ID = 'ff1ca798-b930-4b81-9ef3-c02e770188af';
+ const IDENTITY_ID = 'us-east-1:88859bc9-0149-4183-bf10-39e36EXAMPLE';
+ const handlerOptions = {
+ region: 'us-east-1',
+ };
+
+ // API reference: https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetId.html
+ describe('getId', () => {
+ const IDENTITY_POOL_ID = 'us-east-1:177a950c-2c08-43f0-9983-28727EXAMPLE';
+ const ACCOUNT_ID = '123456789012';
+ const params: GetIdInput = {
+ IdentityPoolId: IDENTITY_POOL_ID,
+ AccountId: ACCOUNT_ID,
+ };
+
+ test('happy case', async () => {
+ const expectedRequest = {
+ url: new URL('https://cognito-identity.us-east-1.amazonaws.com/'),
+ method: 'POST',
+ headers: expect.objectContaining({
+ 'cache-control': 'no-store',
+ 'content-type': 'application/x-amz-json-1.1',
+ 'x-amz-target': 'AWSCognitoIdentityService.GetId',
+ }),
+ body: JSON.stringify({
+ IdentityPoolId: IDENTITY_POOL_ID,
+ AccountId: ACCOUNT_ID,
+ }),
+ };
+ const succeedResponse = {
+ status: 200,
+ headers: {
+ 'x-amzn-requestid': REQUEST_ID,
+ },
+ body: {
+ IdentityId: IDENTITY_ID,
+ },
+ };
+ const expectedOutput: GetIdOutput = {
+ IdentityId: IDENTITY_ID,
+ $metadata: expect.objectContaining({
+ attempts: 1,
+ requestId: REQUEST_ID,
+ httpStatusCode: 200,
+ }),
+ };
+ (fetchTransferHandler as jest.Mock).mockResolvedValue(
+ mockJsonResponse(succeedResponse)
+ );
+ const response = await getId(handlerOptions, params);
+ expect(response).toEqual(expectedOutput);
+ expect(fetchTransferHandler).toBeCalledWith(
+ expectedRequest,
+ expect.anything()
+ );
+ });
+
+ test('error case', async () => {
+ const failureResponse = {
+ status: 400,
+ headers: {
+ 'x-amzn-requestid': REQUEST_ID,
+ 'x-amzn-errortype': 'NotAuthorizedException',
+ },
+ body: {
+ __type: 'NotAuthorizedException',
+ message: `Identity pool ${IDENTITY_POOL_ID} does not exist.`,
+ },
+ };
+ const expectedError = {
+ name: 'NotAuthorizedException',
+ message: failureResponse.body.message,
+ };
+ (fetchTransferHandler as jest.Mock).mockResolvedValue(
+ mockJsonResponse(failureResponse)
+ );
+ expect.assertions(1);
+ try {
+ await getId(handlerOptions, params);
+ fail('test should fail');
+ } catch (e) {
+ expect(e).toEqual(expect.objectContaining(expectedError));
+ }
+ });
+ });
+
+ // API reference: https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetCredentialsForIdentity.html
+ describe('getCredentialsForIdentity', () => {
+ const CREDENTIALS = {
+ SecretKey: 'secretKey',
+ SessionToken: 'sessionToken',
+ Expiration: 1442877512.0,
+ AccessKeyId: 'accessKeyId',
+ };
+ const params: GetCredentialsForIdentityInput = { IdentityId: IDENTITY_ID };
+
+ test('happy case', async () => {
+ const succeedResponse = {
+ status: 200,
+ headers: {
+ 'x-amzn-requestid': REQUEST_ID,
+ },
+ body: {
+ Credentials: CREDENTIALS,
+ IdentityId: IDENTITY_ID,
+ },
+ };
+ const expectedOutput: GetCredentialsForIdentityOutput = {
+ Credentials: {
+ ...CREDENTIALS,
+ Expiration: new Date(CREDENTIALS.Expiration * 1000),
+ },
+ IdentityId: IDENTITY_ID,
+ $metadata: expect.objectContaining({
+ attempts: 1,
+ requestId: REQUEST_ID,
+ httpStatusCode: 200,
+ }),
+ };
+ const expectedRequest = {
+ url: new URL('https://cognito-identity.us-east-1.amazonaws.com/'),
+ method: 'POST',
+ headers: expect.objectContaining({
+ 'cache-control': 'no-store',
+ 'content-type': 'application/x-amz-json-1.1',
+ 'x-amz-target': 'AWSCognitoIdentityService.GetCredentialsForIdentity',
+ }),
+ body: JSON.stringify({
+ IdentityId: IDENTITY_ID,
+ }),
+ };
+
+ (fetchTransferHandler as jest.Mock).mockResolvedValue(
+ mockJsonResponse(succeedResponse)
+ );
+ const response = await getCredentialsForIdentity(handlerOptions, params);
+ expect(response).toEqual(expectedOutput);
+ expect(fetchTransferHandler).toBeCalledWith(
+ expectedRequest,
+ expect.anything()
+ );
+ });
+
+ test('error case', async () => {
+ const failureResponse = {
+ status: 400,
+ headers: {
+ 'x-amzn-requestid': REQUEST_ID,
+ 'x-amzn-errortype': 'NotAuthorizedException',
+ },
+ body: {
+ __type: 'NotAuthorizedException',
+ message: `Identity ${IDENTITY_ID} does not exist.`,
+ },
+ };
+ const expectedError = {
+ name: 'NotAuthorizedException',
+ message: failureResponse.body.message,
+ };
+ (fetchTransferHandler as jest.Mock).mockResolvedValue(
+ mockJsonResponse(failureResponse)
+ );
+ expect.assertions(1);
+ try {
+ await getCredentialsForIdentity(handlerOptions, params);
+ fail('test should fail');
+ } catch (e) {
+ expect(e).toEqual(expect.objectContaining(expectedError));
+ }
+ });
+ });
+});
diff --git a/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts b/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
index 69baf86169f..cec9252054b 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
@@ -1,6 +1,6 @@
import type {
- GetCredentialsForIdentityCommandInput,
- GetCredentialsForIdentityCommandOutput,
+ GetCredentialsForIdentityCommandInput as GetCredentialsForIdentityInput,
+ GetCredentialsForIdentityCommandOutput as GetCredentialsForIdentityOutput,
Credentials,
} from '@aws-sdk/client-cognito-identity';
import {
@@ -18,12 +18,12 @@ import {
} from '../../clients/serde';
export type {
- GetCredentialsForIdentityCommandInput,
- GetCredentialsForIdentityCommandOutput,
+ GetCredentialsForIdentityCommandInput as GetCredentialsForIdentityInput,
+ GetCredentialsForIdentityCommandOutput as GetCredentialsForIdentityOutput,
} from '@aws-sdk/client-cognito-identity';
const getCredentialsForIdentitySerializer = (
- input: GetCredentialsForIdentityCommandInput,
+ input: GetCredentialsForIdentityInput,
endpoint: Endpoint
): HttpRequest => {
const headers = sharedHeaders('GetCredentialsForIdentity');
@@ -33,7 +33,7 @@ const getCredentialsForIdentitySerializer = (
const getCredentialsForIdentityDeserializer = async (
response: HttpResponse
-): Promise => {
+): Promise => {
if (response.statusCode >= 300) {
const error = await parseJsonError(response);
throw error;
diff --git a/packages/core/src/AwsClients/CognitoIdentity/getId.ts b/packages/core/src/AwsClients/CognitoIdentity/getId.ts
index 09f3917a736..f5d20b07d12 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/getId.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/getId.ts
@@ -1,6 +1,6 @@
-import type {
- GetIdCommandInput,
- GetIdCommandOutput,
+import {
+ GetIdCommandInput as GetIdInput,
+ GetIdCommandOutput as GetIdOutput,
} from '@aws-sdk/client-cognito-identity';
import {
buildHttpRpcRequest,
@@ -17,12 +17,12 @@ import {
} from '../../clients/serde';
export type {
- GetIdCommandInput,
- GetIdCommandOutput,
+ GetIdCommandInput as GetIdInput,
+ GetIdCommandOutput as GetIdOutput,
} from '@aws-sdk/client-cognito-identity';
const getIdSerializer = (
- input: GetIdCommandInput,
+ input: GetIdInput,
endpoint: Endpoint
): HttpRequest => {
const headers = sharedHeaders('GetId');
@@ -32,7 +32,7 @@ const getIdSerializer = (
const getIdDeserializer = async (
response: HttpResponse
-): Promise => {
+): Promise => {
if (response.statusCode >= 300) {
const error = await parseJsonError(response);
throw error;
diff --git a/packages/core/src/AwsClients/CognitoIdentity/index.ts b/packages/core/src/AwsClients/CognitoIdentity/index.ts
index 5938830707b..111726c1561 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/index.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/index.ts
@@ -1,6 +1,6 @@
-export { getId, GetIdCommandInput, GetIdCommandOutput } from './getId';
+export { getId, GetIdInput, GetIdOutput } from './getId';
export {
getCredentialsForIdentity,
- GetCredentialsForIdentityCommandInput,
- GetCredentialsForIdentityCommandOutput,
+ GetCredentialsForIdentityInput,
+ GetCredentialsForIdentityOutput,
} from './getCredentialsForIdentity';
diff --git a/packages/core/src/clients/handlers/fetch.ts b/packages/core/src/clients/handlers/fetch.ts
index b3eeb71bc4d..4c3b49109db 100644
--- a/packages/core/src/clients/handlers/fetch.ts
+++ b/packages/core/src/clients/handlers/fetch.ts
@@ -9,14 +9,14 @@ export const fetchTransferHandler: TransferHandler<
HttpRequest,
HttpResponse,
HttpTransferOptions
-> = async (request, options) => {
+> = async ({ url, method, headers, body }, { abortSignal }) => {
let resp: Response;
try {
- resp = await fetch(request.url, {
- method: request.method,
- headers: request.headers,
- body: shouldSendBody(request.method) ? request.body : undefined,
- signal: options.abortSignal,
+ resp = await fetch(url, {
+ method,
+ headers,
+ body: shouldSendBody(method) ? body : undefined,
+ signal: abortSignal,
});
} catch (e) {
// TODO: needs to revise error handling in v6
@@ -26,13 +26,13 @@ export const fetchTransferHandler: TransferHandler<
throw e;
}
- const headers = {};
+ const responseHeaders = {};
resp.headers?.forEach((value, key) => {
- headers[key.toLowerCase()] = value;
+ responseHeaders[key.toLowerCase()] = value;
});
const httpResponse = {
statusCode: resp.status,
- headers,
+ headers: responseHeaders,
body: null,
};
diff --git a/packages/core/src/clients/internal/composeApiHandler.ts b/packages/core/src/clients/internal/composeApiHandler.ts
index 5bef77695a0..d2a67e348ad 100644
--- a/packages/core/src/clients/internal/composeApiHandler.ts
+++ b/packages/core/src/clients/internal/composeApiHandler.ts
@@ -31,6 +31,9 @@ export const composeServiceApi = <
const endpoint = await resolvedConfig.endpointResolver({
region: resolvedConfig.region,
});
+ // Unlike AWS SDK clients, a serializer should NOT populate the `host` or `content-length` headers.
+ // Both of these headers are prohibited per Spec(https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name).
+ // They will be populated automatically by browser, or node-fetch polyfill.
const request = serializer(input, endpoint);
const response = await transferHandler(request, {
...resolvedConfig,
diff --git a/packages/core/src/clients/middleware/retry/middleware.ts b/packages/core/src/clients/middleware/retry/middleware.ts
index c7b1ddfd7a4..d5bdb30d8f9 100644
--- a/packages/core/src/clients/middleware/retry/middleware.ts
+++ b/packages/core/src/clients/middleware/retry/middleware.ts
@@ -40,10 +40,13 @@ export interface RetryOptions {
/**
* Retry middleware
*/
-export const retryMiddleware = (
- options: RetryOptions
-) => {
- if (options.maxAttempts < 1) {
+export const retryMiddleware = ({
+ maxAttempts = DEFAULT_RETRY_ATTEMPTS,
+ retryDecider,
+ computeDelay,
+ abortSignal,
+}: RetryOptions) => {
+ if (maxAttempts < 1) {
throw new Error('maxAttempts must be greater than 0');
}
return (
@@ -51,12 +54,6 @@ export const retryMiddleware = (
context: MiddlewareContext
) =>
async function retryMiddleware(request: TInput) {
- const {
- maxAttempts = DEFAULT_RETRY_ATTEMPTS,
- retryDecider,
- computeDelay,
- abortSignal,
- } = options;
let error: Error;
let attemptsCount = context.attemptsCount ?? 0;
let response: TOutput;
From 475f26bc4d0c415ede4eaf4d5126023a43fb44de Mon Sep 17 00:00:00 2001
From: AllanZhengYP
Date: Wed, 19 Apr 2023 17:26:40 -0700
Subject: [PATCH 09/41] feat(clients): add useragent to cognito identity
(#11269)
---
packages/auth/package.json | 2 +-
packages/core/__tests__/AwsClients/CognitoIdentity-test.ts | 2 ++
packages/core/package.json | 2 +-
packages/core/src/AwsClients/CognitoIdentity/base.ts | 2 ++
4 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/packages/auth/package.json b/packages/auth/package.json
index daf04401aa0..e9c911eeab5 100644
--- a/packages/auth/package.json
+++ b/packages/auth/package.json
@@ -58,7 +58,7 @@
"name": "Auth (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Auth }",
- "limit": "53.4 kB"
+ "limit": "53.5 kB"
}
],
"jest": {
diff --git a/packages/core/__tests__/AwsClients/CognitoIdentity-test.ts b/packages/core/__tests__/AwsClients/CognitoIdentity-test.ts
index 6f27f5ce4e8..cb7e6d8234a 100644
--- a/packages/core/__tests__/AwsClients/CognitoIdentity-test.ts
+++ b/packages/core/__tests__/AwsClients/CognitoIdentity-test.ts
@@ -59,6 +59,7 @@ describe('cognito-identity service client', () => {
'cache-control': 'no-store',
'content-type': 'application/x-amz-json-1.1',
'x-amz-target': 'AWSCognitoIdentityService.GetId',
+ 'x-amz-user-agent': expect.stringContaining('aws-amplify'),
}),
body: JSON.stringify({
IdentityPoolId: IDENTITY_POOL_ID,
@@ -162,6 +163,7 @@ describe('cognito-identity service client', () => {
'cache-control': 'no-store',
'content-type': 'application/x-amz-json-1.1',
'x-amz-target': 'AWSCognitoIdentityService.GetCredentialsForIdentity',
+ 'x-amz-user-agent': expect.stringContaining('aws-amplify'),
}),
body: JSON.stringify({
IdentityId: IDENTITY_ID,
diff --git a/packages/core/package.json b/packages/core/package.json
index a659c43c428..36649702a8f 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -100,7 +100,7 @@
"name": "Core (Credentials)",
"path": "./lib-esm/index.js",
"import": "{ Credentials }",
- "limit": "11.54 kB"
+ "limit": "11.7 kB"
},
{
"name": "Custom clients (retry middleware)",
diff --git a/packages/core/src/AwsClients/CognitoIdentity/base.ts b/packages/core/src/AwsClients/CognitoIdentity/base.ts
index 7a06754bbfc..68af09bb824 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/base.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/base.ts
@@ -12,6 +12,7 @@ import {
getRetryDecider,
} from '../../clients/middleware/retry';
import { parseJsonError } from '../../clients/serde/json';
+import { getAmplifyUserAgent } from '../../Platform';
/**
* The service name used to sign requests if the API requires authentication.
@@ -58,6 +59,7 @@ export const defaultConfigs = {
endpointResolver,
retryDecider: getRetryDecider(parseJsonError),
computeDelay: jitteredBackoff,
+ userAgentValue: getAmplifyUserAgent(), // TODO: use getAmplifyUserAgentString() when available.
};
/**
From 0cebaf5ca2f5e7d00a0aff1819ac104f347d551c Mon Sep 17 00:00:00 2001
From: AllanZhengYP
Date: Mon, 24 Apr 2023 13:13:34 -0700
Subject: [PATCH 10/41] chore(clients): add license header to files (#11292)
---
packages/core/src/AwsClients/CognitoIdentity/base.ts | 3 +++
.../AwsClients/CognitoIdentity/getCredentialsForIdentity.ts | 3 +++
packages/core/src/AwsClients/CognitoIdentity/getId.ts | 3 +++
packages/core/src/AwsClients/CognitoIdentity/index.ts | 3 +++
packages/core/src/clients/handlers/fetch.ts | 3 +++
packages/core/src/clients/handlers/unauthenticated.ts | 3 +++
packages/core/src/clients/internal/composeApiHandler.ts | 3 +++
packages/core/src/clients/internal/composeTransferHandler.ts | 3 +++
.../core/src/clients/middleware/retry/defaultRetryDecider.ts | 3 +++
packages/core/src/clients/middleware/retry/index.ts | 3 +++
packages/core/src/clients/middleware/retry/jitteredBackoff.ts | 3 +++
packages/core/src/clients/middleware/retry/middleware.ts | 3 +++
packages/core/src/clients/middleware/userAgent/index.ts | 3 +++
packages/core/src/clients/middleware/userAgent/middleware.ts | 3 +++
packages/core/src/clients/polyfills/fetch.node.ts | 0
packages/core/src/clients/serde/index.ts | 3 +++
packages/core/src/clients/serde/json.ts | 3 +++
packages/core/src/clients/serde/responseInfo.ts | 3 +++
packages/core/src/clients/types/aws.ts | 3 +++
packages/core/src/clients/types/core.ts | 3 +++
packages/core/src/clients/types/http.ts | 3 +++
packages/core/src/clients/types/index.ts | 3 +++
22 files changed, 63 insertions(+)
delete mode 100644 packages/core/src/clients/polyfills/fetch.node.ts
diff --git a/packages/core/src/AwsClients/CognitoIdentity/base.ts b/packages/core/src/AwsClients/CognitoIdentity/base.ts
index 68af09bb824..ea597ca9388 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/base.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/base.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
import type {
Endpoint,
Headers,
diff --git a/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts b/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
index cec9252054b..38991c96914 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
import type {
GetCredentialsForIdentityCommandInput as GetCredentialsForIdentityInput,
GetCredentialsForIdentityCommandOutput as GetCredentialsForIdentityOutput,
diff --git a/packages/core/src/AwsClients/CognitoIdentity/getId.ts b/packages/core/src/AwsClients/CognitoIdentity/getId.ts
index f5d20b07d12..0c914ec510e 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/getId.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/getId.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
import {
GetIdCommandInput as GetIdInput,
GetIdCommandOutput as GetIdOutput,
diff --git a/packages/core/src/AwsClients/CognitoIdentity/index.ts b/packages/core/src/AwsClients/CognitoIdentity/index.ts
index 111726c1561..8c30ac96c50 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/index.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/index.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
export { getId, GetIdInput, GetIdOutput } from './getId';
export {
getCredentialsForIdentity,
diff --git a/packages/core/src/clients/handlers/fetch.ts b/packages/core/src/clients/handlers/fetch.ts
index 4c3b49109db..a111d64f620 100644
--- a/packages/core/src/clients/handlers/fetch.ts
+++ b/packages/core/src/clients/handlers/fetch.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
import 'isomorphic-unfetch'; // TODO: remove this dependency in v6
import { HttpRequest, HttpResponse, HttpTransferOptions } from '../types/http';
import { TransferHandler } from '../types/core';
diff --git a/packages/core/src/clients/handlers/unauthenticated.ts b/packages/core/src/clients/handlers/unauthenticated.ts
index 8370b3bf7ef..4f19a2a894b 100644
--- a/packages/core/src/clients/handlers/unauthenticated.ts
+++ b/packages/core/src/clients/handlers/unauthenticated.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
import { retryMiddleware, RetryOptions } from '../middleware/retry';
import { userAgentMiddleware, UserAgentOptions } from '../middleware/userAgent';
import { composeTransferHandler } from '../internal/composeTransferHandler';
diff --git a/packages/core/src/clients/internal/composeApiHandler.ts b/packages/core/src/clients/internal/composeApiHandler.ts
index d2a67e348ad..9be85193167 100644
--- a/packages/core/src/clients/internal/composeApiHandler.ts
+++ b/packages/core/src/clients/internal/composeApiHandler.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
import { ServiceClientOptions } from '../types/aws';
import { TransferHandler, Endpoint } from '../types/core';
import { HttpRequest, HttpResponse } from '../types/http';
diff --git a/packages/core/src/clients/internal/composeTransferHandler.ts b/packages/core/src/clients/internal/composeTransferHandler.ts
index a9d342117d3..81f26d6bba8 100644
--- a/packages/core/src/clients/internal/composeTransferHandler.ts
+++ b/packages/core/src/clients/internal/composeTransferHandler.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
import {
Middleware,
MiddlewareHandler,
diff --git a/packages/core/src/clients/middleware/retry/defaultRetryDecider.ts b/packages/core/src/clients/middleware/retry/defaultRetryDecider.ts
index 14fa4d319ec..9ea345e1bd4 100644
--- a/packages/core/src/clients/middleware/retry/defaultRetryDecider.ts
+++ b/packages/core/src/clients/middleware/retry/defaultRetryDecider.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
import { HttpResponse, ErrorParser } from '../../types';
/**
diff --git a/packages/core/src/clients/middleware/retry/index.ts b/packages/core/src/clients/middleware/retry/index.ts
index 1d68913ee9e..c48fcce770c 100644
--- a/packages/core/src/clients/middleware/retry/index.ts
+++ b/packages/core/src/clients/middleware/retry/index.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
export { RetryOptions, retryMiddleware } from './middleware';
export { jitteredBackoff } from './jitteredBackoff';
export { getRetryDecider } from './defaultRetryDecider';
diff --git a/packages/core/src/clients/middleware/retry/jitteredBackoff.ts b/packages/core/src/clients/middleware/retry/jitteredBackoff.ts
index d7c04dcf76e..e26c7eebe22 100644
--- a/packages/core/src/clients/middleware/retry/jitteredBackoff.ts
+++ b/packages/core/src/clients/middleware/retry/jitteredBackoff.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
import type { RetryOptions } from './middleware';
// TODO: remove this dependency in v6
import { jitteredBackoff as jitteredBackoffUtil } from '../../../Util/Retry';
diff --git a/packages/core/src/clients/middleware/retry/middleware.ts b/packages/core/src/clients/middleware/retry/middleware.ts
index d5bdb30d8f9..014b5b6d468 100644
--- a/packages/core/src/clients/middleware/retry/middleware.ts
+++ b/packages/core/src/clients/middleware/retry/middleware.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
import { MetadataBearer } from '@aws-sdk/types';
import {
MiddlewareContext,
diff --git a/packages/core/src/clients/middleware/userAgent/index.ts b/packages/core/src/clients/middleware/userAgent/index.ts
index db583db354b..020aec1776b 100644
--- a/packages/core/src/clients/middleware/userAgent/index.ts
+++ b/packages/core/src/clients/middleware/userAgent/index.ts
@@ -1 +1,4 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
export { userAgentMiddleware, UserAgentOptions } from './middleware';
diff --git a/packages/core/src/clients/middleware/userAgent/middleware.ts b/packages/core/src/clients/middleware/userAgent/middleware.ts
index 0e582f246cb..1e5adb16a5f 100644
--- a/packages/core/src/clients/middleware/userAgent/middleware.ts
+++ b/packages/core/src/clients/middleware/userAgent/middleware.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
import { HttpRequest, HttpResponse } from '../../types/http';
import { Middleware } from '../../types/core';
diff --git a/packages/core/src/clients/polyfills/fetch.node.ts b/packages/core/src/clients/polyfills/fetch.node.ts
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/packages/core/src/clients/serde/index.ts b/packages/core/src/clients/serde/index.ts
index b1fa64e5ab4..0f695e31278 100644
--- a/packages/core/src/clients/serde/index.ts
+++ b/packages/core/src/clients/serde/index.ts
@@ -1,2 +1,5 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
export { parseMetadata } from './responseInfo';
export { parseJsonBody, parseJsonError } from './json';
diff --git a/packages/core/src/clients/serde/json.ts b/packages/core/src/clients/serde/json.ts
index e4b0b860a55..cea8db0efe8 100644
--- a/packages/core/src/clients/serde/json.ts
+++ b/packages/core/src/clients/serde/json.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
import { ErrorParser, HttpResponse } from '../types';
import { parseMetadata } from './responseInfo';
diff --git a/packages/core/src/clients/serde/responseInfo.ts b/packages/core/src/clients/serde/responseInfo.ts
index 3298b1e584c..fa422873b5e 100644
--- a/packages/core/src/clients/serde/responseInfo.ts
+++ b/packages/core/src/clients/serde/responseInfo.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
import { ResponseMetadata } from '@aws-sdk/types';
import { isMetadataBearer } from '../middleware/retry/middleware';
import { HttpResponse } from '../types/http';
diff --git a/packages/core/src/clients/types/aws.ts b/packages/core/src/clients/types/aws.ts
index c01a33ca76e..b1d71635ce5 100644
--- a/packages/core/src/clients/types/aws.ts
+++ b/packages/core/src/clients/types/aws.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
import { Endpoint } from './core';
import { HttpResponse } from './http';
diff --git a/packages/core/src/clients/types/core.ts b/packages/core/src/clients/types/core.ts
index 31d94fea24c..7430bfc9edd 100644
--- a/packages/core/src/clients/types/core.ts
+++ b/packages/core/src/clients/types/core.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
/**
* General None HTTP-specific request interface
*/
diff --git a/packages/core/src/clients/types/http.ts b/packages/core/src/clients/types/http.ts
index eb23f2ce9f0..370ebf2134a 100644
--- a/packages/core/src/clients/types/http.ts
+++ b/packages/core/src/clients/types/http.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
import { Request, Response, TransferHandler } from './core';
/**
diff --git a/packages/core/src/clients/types/index.ts b/packages/core/src/clients/types/index.ts
index 007a461994b..9a1ed26b4ce 100644
--- a/packages/core/src/clients/types/index.ts
+++ b/packages/core/src/clients/types/index.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
export {
Middleware,
MiddlewareHandler,
From 41bb35aabac952006ad7ef5f8e60ab8782503317 Mon Sep 17 00:00:00 2001
From: Chris F <5827964+cshfang@users.noreply.github.com>
Date: Mon, 24 Apr 2023 14:09:26 -0700
Subject: [PATCH 11/41] feat(clients): Add custom signature v4 signer (#11273)
* feat(clients): Add custom signature v4 signer
* Updated docstrings
* Extracted common code
* Use == null instead of isNil
* Add unit tests
* Add missing licensing headers
* Use test case options in presign tests
* Fixed comment
* Add test for data hashing with SourceData keys
* Remove buffer dependency
* Remove internal sdk dependency
---
.../signer/signatureV4/presignUrl-test.ts | 30 +++
.../signer/signatureV4/signRequest-test.ts | 31 +++
.../signer/signatureV4/testUtils/data.ts | 47 +++++
.../signatureV4/testUtils/signingTestTable.ts | 187 ++++++++++++++++++
.../signatureV4/utils/dataHashHelpers-test.ts | 77 ++++++++
.../utils/getCanonicalHeaders-test.ts | 26 +++
.../utils/getCanonicalQueryString-test.ts | 20 ++
.../utils/getCanonicalRequest-test.ts | 17 ++
.../signatureV4/utils/getCanonicalUri-test.ts | 25 +++
.../utils/getCredentialScope-test.ts | 22 +++
.../utils/getFormattedDates-test.ts | 15 ++
.../utils/getHashedPayload-test.ts | 41 ++++
.../signatureV4/utils/getSignature-test.ts | 31 +++
.../utils/getSignedHeaders-test.ts | 16 ++
.../signatureV4/utils/getSigningKey-test.ts | 17 ++
.../utils/getSigningValues-test.ts | 33 ++++
.../signatureV4/utils/getStringToSign-test.ts | 19 ++
packages/core/package.json | 1 +
.../signing/signer/signatureV4/constants.ts | 28 +++
.../signing/signer/signatureV4/index.ts | 6 +
.../signing/signer/signatureV4/presignUrl.ts | 58 ++++++
.../signing/signer/signatureV4/signRequest.ts | 51 +++++
.../signing/signer/signatureV4/types/index.ts | 4 +
.../signer/signatureV4/types/signer.ts | 31 +++
.../signatureV4/utils/dataHashHelpers.ts | 39 ++++
.../signatureV4/utils/getCanonicalHeaders.ts | 23 +++
.../utils/getCanonicalQueryString.ts | 30 +++
.../signatureV4/utils/getCanonicalRequest.ts | 36 ++++
.../signatureV4/utils/getCanonicalUri.ts | 12 ++
.../signatureV4/utils/getCredentialScope.ts | 19 ++
.../signatureV4/utils/getFormattedDates.ts | 20 ++
.../signatureV4/utils/getHashedPayload.ts | 38 ++++
.../signer/signatureV4/utils/getSignature.ts | 55 ++++++
.../signatureV4/utils/getSignedHeaders.ts | 17 ++
.../signer/signatureV4/utils/getSigningKey.ts | 29 +++
.../signatureV4/utils/getSigningValues.ts | 41 ++++
.../signatureV4/utils/getStringToSign.ts | 26 +++
packages/tsconfig.base.json | 1 +
38 files changed, 1219 insertions(+)
create mode 100644 packages/core/__tests__/clients/middleware/signing/signer/signatureV4/presignUrl-test.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/signer/signatureV4/signRequest-test.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/signer/signatureV4/testUtils/data.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/signer/signatureV4/testUtils/signingTestTable.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers-test.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalHeaders-test.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalQueryString-test.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest-test.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalUri-test.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCredentialScope-test.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getFormattedDates-test.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload-test.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSignature-test.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSignedHeaders-test.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSigningKey-test.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSigningValues-test.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getStringToSign-test.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/constants.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/index.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/presignUrl.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/signRequest.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/types/index.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/types/signer.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalHeaders.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalQueryString.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalUri.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCredentialScope.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getFormattedDates.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignature.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignedHeaders.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningKey.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningValues.ts
create mode 100644 packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getStringToSign.ts
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/presignUrl-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/presignUrl-test.ts
new file mode 100644
index 00000000000..c040eedd9c1
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/presignUrl-test.ts
@@ -0,0 +1,30 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { presignUrl } from '../../../../../../src/clients/middleware/signing/signer/signatureV4/presignUrl';
+import { HttpRequest } from '../../../../../../src/clients/types';
+import { signingTestTable } from './testUtils/signingTestTable';
+import { getDefaultRequest, signingOptions } from './testUtils/data';
+
+describe('presignUrl', () => {
+ test.each(
+ signingTestTable.map(
+ ({ name, request, queryParams, options, expectedUrl }) => {
+ const updatedRequest: HttpRequest = {
+ ...getDefaultRequest(),
+ ...request,
+ };
+ queryParams?.forEach(([key, value]) => {
+ updatedRequest.url?.searchParams.append(key, value);
+ });
+ const updatedOptions = {
+ ...signingOptions,
+ ...options,
+ };
+ return [name, updatedRequest, updatedOptions, expectedUrl];
+ }
+ )
+ )('presigns url with %s', async (_, request, options, expected) => {
+ expect((await presignUrl(request, options)).toString()).toBe(expected);
+ });
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/signRequest-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/signRequest-test.ts
new file mode 100644
index 00000000000..4ec61e8ad1f
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/signRequest-test.ts
@@ -0,0 +1,31 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { signRequest } from '../../../../../../src/clients/middleware/signing/signer/signatureV4/signRequest';
+import { signingTestTable } from './testUtils/signingTestTable';
+import { getDefaultRequest, signingOptions } from './testUtils/data';
+
+describe('signRequest', () => {
+ test.each(
+ signingTestTable.map(
+ ({ name, request, queryParams, options, expectedAuthorization }) => {
+ const updatedRequest = {
+ ...getDefaultRequest(),
+ ...request,
+ };
+ queryParams?.forEach(([key, value]) => {
+ updatedRequest.url?.searchParams.append(key, value);
+ });
+ const updatedOptions = {
+ ...signingOptions,
+ ...options,
+ };
+ return [name, updatedRequest, updatedOptions, expectedAuthorization];
+ }
+ )
+ )('signs request with %s', async (_, request, options, expected) => {
+ expect((await signRequest(request, options)).headers.authorization).toBe(
+ expected
+ );
+ });
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/testUtils/data.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/testUtils/data.ts
new file mode 100644
index 00000000000..e436c433e87
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/testUtils/data.ts
@@ -0,0 +1,47 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import {
+ FormattedDates,
+ SignRequestOptions,
+} from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/types/signer';
+import { Credentials } from '../../../../../../../src/clients/types';
+
+export const credentials: Credentials = {
+ accessKeyId: 'access-key-id',
+ secretAccessKey: 'secret-access-key',
+};
+
+export const credentialsWithToken: Credentials = {
+ ...credentials,
+ sessionToken: 'session-token',
+};
+
+export const url = 'https://domain.fakeurl/';
+
+export const signingDate = new Date('2020-09-18T18:18:18.808Z');
+
+export const signingRegion = 'signing-region';
+
+export const signingService = 'signing-service';
+
+export const signingOptions: SignRequestOptions = {
+ credentials,
+ signingDate,
+ signingRegion,
+ signingService,
+};
+
+export const formattedDates: FormattedDates = {
+ longDate: '20200918T181818Z',
+ shortDate: '20200918',
+};
+
+export const credentialScope =
+ '20200918/signing-region/signing-service/aws4_request';
+
+export const getDefaultRequest = () => ({
+ headers: {},
+ method: 'GET',
+ url: new URL(url),
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/testUtils/signingTestTable.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/testUtils/signingTestTable.ts
new file mode 100644
index 00000000000..5a65d7c9717
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/testUtils/signingTestTable.ts
@@ -0,0 +1,187 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { SignRequestOptions } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/types';
+import { HttpRequest } from '../../../../../../../src/clients/types';
+import { credentialsWithToken, signingOptions, url } from './data';
+
+interface TestCase {
+ name: string;
+ request?: Partial;
+ queryParams?: [string, string][];
+ options?: SignRequestOptions;
+ expectedAuthorization: string;
+ expectedUrl: string;
+}
+
+export const signingTestTable: TestCase[] = [
+ {
+ name: 'basic values',
+ expectedAuthorization:
+ 'AWS4-HMAC-SHA256 Credential=access-key-id/20200918/signing-region/signing-service/aws4_request, SignedHeaders=host;x-amz-date, Signature=ed540d965ed71159f0cf2af27a410746f74e972fb8cf976e7dd2295e21bba370',
+ expectedUrl:
+ 'https://domain.fakeurl/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access-key-id%2F20200918%2Fsigning-region%2Fsigning-service%2Faws4_request&X-Amz-Date=20200918T181818Z&X-Amz-SignedHeaders=host&X-Amz-Signature=b2bdc8574c553b0652332bf38234b9916ba8f9f44d25635bb817e43a03b4bb75',
+ },
+ {
+ name: 'utf8 url',
+ request: { url: new URL(`${url}\u03A9`) },
+ expectedAuthorization:
+ 'AWS4-HMAC-SHA256 Credential=access-key-id/20200918/signing-region/signing-service/aws4_request, SignedHeaders=host;x-amz-date, Signature=0260705547c9edbf83887d1871daf92e4dd1cd14fe5aa6049b0262b692a42944',
+ expectedUrl:
+ 'https://domain.fakeurl/%CE%A9?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access-key-id%2F20200918%2Fsigning-region%2Fsigning-service%2Faws4_request&X-Amz-Date=20200918T181818Z&X-Amz-SignedHeaders=host&X-Amz-Signature=9fe1147bc2119afa6d453fb4d7905426b50c29d40279dd1c2d2204be0d068686',
+ },
+ {
+ name: 'basic query params',
+ queryParams: [['foo', 'bar']],
+ expectedAuthorization:
+ 'AWS4-HMAC-SHA256 Credential=access-key-id/20200918/signing-region/signing-service/aws4_request, SignedHeaders=host;x-amz-date, Signature=8867f03bdc06c350e3161f17c996fcb3d37d79a05ff219d70f89f8a29f56e784',
+ expectedUrl:
+ 'https://domain.fakeurl/?foo=bar&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access-key-id%2F20200918%2Fsigning-region%2Fsigning-service%2Faws4_request&X-Amz-Date=20200918T181818Z&X-Amz-SignedHeaders=host&X-Amz-Signature=60a370af8f549abf3e8d700ed92652c72a06e58dc495f7362df1b5f253d5c35d',
+ },
+ {
+ name: 'empty query params',
+ request: {
+ method: 'POST',
+ },
+ queryParams: [['foo', '']],
+ expectedAuthorization:
+ 'AWS4-HMAC-SHA256 Credential=access-key-id/20200918/signing-region/signing-service/aws4_request, SignedHeaders=host;x-amz-date, Signature=66645930d9d0841a65d3ad384205671bffc4f3c8f017318d7e40d1c2c5abbef9',
+ expectedUrl:
+ 'https://domain.fakeurl/?foo=&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access-key-id%2F20200918%2Fsigning-region%2Fsigning-service%2Faws4_request&X-Amz-Date=20200918T181818Z&X-Amz-SignedHeaders=host&X-Amz-Signature=8de709b2ef1ab770d62eb9ff2476e6979c22b15b651e3fcfc58a8abd50bec424',
+ },
+ {
+ name: 'query params of equal values',
+ request: {
+ method: 'POST',
+ },
+ queryParams: [
+ ['foo', 'b'],
+ ['foo', 'a'],
+ ['foo', 'Zoo'],
+ ['foo', 'bar'],
+ ],
+ expectedAuthorization:
+ 'AWS4-HMAC-SHA256 Credential=access-key-id/20200918/signing-region/signing-service/aws4_request, SignedHeaders=host;x-amz-date, Signature=f41abae910a13886267e000f63059c4e157bab50a6d07dba1a8bed8779f4e68b',
+ expectedUrl:
+ 'https://domain.fakeurl/?foo=b&foo=a&foo=Zoo&foo=bar&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access-key-id%2F20200918%2Fsigning-region%2Fsigning-service%2Faws4_request&X-Amz-Date=20200918T181818Z&X-Amz-SignedHeaders=host&X-Amz-Signature=97674b0a370aca53b1f66a60d1a7c31c7d80b843faf659cd57cb60b9b13ba7b2',
+ },
+ {
+ name: 'query params not in order',
+ request: {
+ method: 'POST',
+ },
+ queryParams: [
+ ['foo', 'a'],
+ ['bar', 'b'],
+ ['Zoo', 'c'],
+ ['baz', 'd'],
+ ],
+ expectedAuthorization:
+ 'AWS4-HMAC-SHA256 Credential=access-key-id/20200918/signing-region/signing-service/aws4_request, SignedHeaders=host;x-amz-date, Signature=dd137aa8dda85c6ce410e1ff783da63b8c6ad4ee00bf2b7c1c6dbe733a30eb80',
+ expectedUrl:
+ 'https://domain.fakeurl/?foo=a&bar=b&Zoo=c&baz=d&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access-key-id%2F20200918%2Fsigning-region%2Fsigning-service%2Faws4_request&X-Amz-Date=20200918T181818Z&X-Amz-SignedHeaders=host&X-Amz-Signature=5750204236a6fa87a59685a2378f94f0d2699b7b6b32a67b23f829db953957ce',
+ },
+ {
+ name: 'unreserved characters in query params',
+ request: {
+ method: 'POST',
+ },
+ queryParams: [
+ [
+ '-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
+ '-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
+ ],
+ ],
+ expectedAuthorization:
+ 'AWS4-HMAC-SHA256 Credential=access-key-id/20200918/signing-region/signing-service/aws4_request, SignedHeaders=host;x-amz-date, Signature=41bd9fff7326a22cf0126afc1d401f1cbdffa287eca4e806edb8cb73a9adc98e',
+ expectedUrl:
+ 'https://domain.fakeurl/?-._%7E0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=-._%7E0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access-key-id%2F20200918%2Fsigning-region%2Fsigning-service%2Faws4_request&X-Amz-Date=20200918T181818Z&X-Amz-SignedHeaders=host&X-Amz-Signature=828db109a0a3a6a840a522262088a272848e0121bf785c332511a0dfd3df04c6',
+ },
+ {
+ name: 'reserved characters in query params',
+ request: {
+ method: 'POST',
+ },
+ queryParams: [['@#$%^&+=/,?><`";:\\|][{} ', '@#$%^&+=/,?><`";:\\|][{} ']],
+ expectedAuthorization:
+ 'AWS4-HMAC-SHA256 Credential=access-key-id/20200918/signing-region/signing-service/aws4_request, SignedHeaders=host;x-amz-date, Signature=f327b38d72f04124c880907467b44e7338e9ff5c2bf01534af27174aaf929d63',
+ expectedUrl:
+ 'https://domain.fakeurl/?%40%23%24%25%5E%26%2B%3D%2F%2C%3F%3E%3C%60%22%3B%3A%5C%7C%5D%5B%7B%7D+=%40%23%24%25%5E%26%2B%3D%2F%2C%3F%3E%3C%60%22%3B%3A%5C%7C%5D%5B%7B%7D+&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access-key-id%2F20200918%2Fsigning-region%2Fsigning-service%2Faws4_request&X-Amz-Date=20200918T181818Z&X-Amz-SignedHeaders=host&X-Amz-Signature=deddbc9d8440a57bdc87d0b4e06b2859304e263af0896b7a4b0126b9d629a81a',
+ },
+ {
+ name: 'spaces in query params',
+ queryParams: [['f oo', 'b ar']],
+ expectedAuthorization:
+ 'AWS4-HMAC-SHA256 Credential=access-key-id/20200918/signing-region/signing-service/aws4_request, SignedHeaders=host;x-amz-date, Signature=37ce5dcb43c12849d256de4cc73dff4c89e333ba2604d8a8de24876f6fbc57d0',
+ expectedUrl:
+ 'https://domain.fakeurl/?f+oo=b+ar&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access-key-id%2F20200918%2Fsigning-region%2Fsigning-service%2Faws4_request&X-Amz-Date=20200918T181818Z&X-Amz-SignedHeaders=host&X-Amz-Signature=e7f19479e5275c7f5ba98b07cfb40ea02c6959f652e9aac3ff855b1c38426790',
+ },
+ {
+ name: 'mixed cased header keys',
+ request: {
+ headers: { fOo: 'foo', ZOO: 'zoobar' },
+ },
+ expectedAuthorization:
+ 'AWS4-HMAC-SHA256 Credential=access-key-id/20200918/signing-region/signing-service/aws4_request, SignedHeaders=foo;host;x-amz-date;zoo, Signature=184870aa03cf0f113eedc9f8796d460062bf25dc4c9b236430ba58cc56a9078b',
+ expectedUrl:
+ 'https://domain.fakeurl/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access-key-id%2F20200918%2Fsigning-region%2Fsigning-service%2Faws4_request&X-Amz-Date=20200918T181818Z&X-Amz-SignedHeaders=host&X-Amz-Signature=b2bdc8574c553b0652332bf38234b9916ba8f9f44d25635bb817e43a03b4bb75',
+ },
+ {
+ name: 'duplicate headers',
+ request: {
+ headers: { foo: 'a,z,p' },
+ },
+ expectedAuthorization:
+ 'AWS4-HMAC-SHA256 Credential=access-key-id/20200918/signing-region/signing-service/aws4_request, SignedHeaders=foo;host;x-amz-date, Signature=39b75b20e62d754be359ba39c4549b71cca2a1e139f9518ad741fd983126ba88',
+ expectedUrl:
+ 'https://domain.fakeurl/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access-key-id%2F20200918%2Fsigning-region%2Fsigning-service%2Faws4_request&X-Amz-Date=20200918T181818Z&X-Amz-SignedHeaders=host&X-Amz-Signature=b2bdc8574c553b0652332bf38234b9916ba8f9f44d25635bb817e43a03b4bb75',
+ },
+ {
+ name: 'duplicate out of order headers',
+ request: {
+ headers: { foo: 'z,a,p,a' },
+ },
+ expectedAuthorization:
+ 'AWS4-HMAC-SHA256 Credential=access-key-id/20200918/signing-region/signing-service/aws4_request, SignedHeaders=foo;host;x-amz-date, Signature=a3a7216abaa809ee23e04284f7424a174c9b41bc48b82f1bb8f2b338eb70fc37',
+ expectedUrl:
+ 'https://domain.fakeurl/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access-key-id%2F20200918%2Fsigning-region%2Fsigning-service%2Faws4_request&X-Amz-Date=20200918T181818Z&X-Amz-SignedHeaders=host&X-Amz-Signature=b2bdc8574c553b0652332bf38234b9916ba8f9f44d25635bb817e43a03b4bb75',
+ },
+ {
+ name: 'multiline values in header',
+ request: {
+ headers: { foo: 'a\n b\n\tc' },
+ },
+ expectedAuthorization:
+ 'AWS4-HMAC-SHA256 Credential=access-key-id/20200918/signing-region/signing-service/aws4_request, SignedHeaders=foo;host;x-amz-date, Signature=1963fb52fc54543c28cc3d79517c321a090a87772b5744ed46416f9a575f564b',
+ expectedUrl:
+ 'https://domain.fakeurl/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access-key-id%2F20200918%2Fsigning-region%2Fsigning-service%2Faws4_request&X-Amz-Date=20200918T181818Z&X-Amz-SignedHeaders=host&X-Amz-Signature=b2bdc8574c553b0652332bf38234b9916ba8f9f44d25635bb817e43a03b4bb75',
+ },
+ {
+ name: 'unreserved characters in url',
+ request: {
+ url: new URL(
+ `${url}-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`
+ ),
+ },
+ expectedAuthorization:
+ 'AWS4-HMAC-SHA256 Credential=access-key-id/20200918/signing-region/signing-service/aws4_request, SignedHeaders=host;x-amz-date, Signature=298d61b98e17ec3b2aa025787c586b073dd0b331f8f8f74ff5d5654810bc8d83',
+ expectedUrl:
+ 'https://domain.fakeurl/-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access-key-id%2F20200918%2Fsigning-region%2Fsigning-service%2Faws4_request&X-Amz-Date=20200918T181818Z&X-Amz-SignedHeaders=host&X-Amz-Signature=82af8b9467cf48bfd865ca4061c95080a4f7bb5ad839cd6d55542cc93e4e4e2f',
+ },
+ {
+ name: 'spaces in url',
+ request: { url: new URL(`${url} path`) },
+ expectedAuthorization:
+ 'AWS4-HMAC-SHA256 Credential=access-key-id/20200918/signing-region/signing-service/aws4_request, SignedHeaders=host;x-amz-date, Signature=a784d1fd673929c255d84e730d1445e44769d341ebea7284f6f58e2770f034e0',
+ expectedUrl:
+ 'https://domain.fakeurl/%20path?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access-key-id%2F20200918%2Fsigning-region%2Fsigning-service%2Faws4_request&X-Amz-Date=20200918T181818Z&X-Amz-SignedHeaders=host&X-Amz-Signature=0100d882befcf809a55d34bf1e6ffacf42accdbce255b04d3acf6175c878a875',
+ },
+ {
+ name: 'session token',
+ options: { ...signingOptions, credentials: credentialsWithToken },
+ expectedAuthorization:
+ 'AWS4-HMAC-SHA256 Credential=access-key-id/20200918/signing-region/signing-service/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=10fa345ac8828e6350dd55c784b1bfa85c4ae977c62abf522050cb9065d3e61e',
+ expectedUrl:
+ 'https://domain.fakeurl/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access-key-id%2F20200918%2Fsigning-region%2Fsigning-service%2Faws4_request&X-Amz-Date=20200918T181818Z&X-Amz-SignedHeaders=host&X-Amz-Security-Token=session-token&X-Amz-Signature=14018e6e5b599a4bf0340427309af1c541409409043e2b070725437cb16847b9',
+ },
+];
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers-test.ts
new file mode 100644
index 00000000000..59c2955ed4c
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers-test.ts
@@ -0,0 +1,77 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import {
+ getHashedData,
+ getHashedDataAsHex,
+} from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers';
+
+describe('dataHashHelpers', () => {
+ const key = 'hash-key';
+ const data = 'hash-data';
+ const arrayBuffer = new ArrayBuffer(8);
+ const arrayBufferView = new Uint8Array(new ArrayBuffer(8));
+
+ describe('getHashedData', () => {
+ test('returns hashed data from a key and data', async () => {
+ expect(await getHashedData(key, data)).toStrictEqual(
+ new Uint8Array([
+ 232, 228, 217, 152, 156, 118, 166, 42, 124, 217, 129, 99, 56, 163,
+ 190, 131, 62, 148, 54, 170, 101, 55, 158, 118, 42, 239, 245, 136, 153,
+ 115, 41, 156,
+ ])
+ );
+
+ expect(await getHashedData(arrayBuffer, arrayBuffer)).toStrictEqual(
+ new Uint8Array([
+ 243, 117, 24, 10, 186, 146, 136, 132, 1, 241, 145, 155, 228, 168, 113,
+ 90, 98, 118, 59, 101, 193, 193, 14, 29, 8, 88, 232, 29, 77, 111, 159,
+ 210,
+ ])
+ );
+
+ expect(
+ await getHashedData(arrayBufferView, arrayBufferView)
+ ).toStrictEqual(
+ new Uint8Array([
+ 243, 117, 24, 10, 186, 146, 136, 132, 1, 241, 145, 155, 228, 168, 113,
+ 90, 98, 118, 59, 101, 193, 193, 14, 29, 8, 88, 232, 29, 77, 111, 159,
+ 210,
+ ])
+ );
+ });
+
+ test('returns hashed data from just data', async () => {
+ expect(await getHashedData(null, data)).toStrictEqual(
+ new Uint8Array([
+ 113, 23, 15, 47, 24, 62, 16, 92, 220, 133, 238, 126, 50, 50, 172, 161,
+ 24, 87, 60, 247, 83, 37, 49, 75, 190, 87, 74, 159, 224, 95, 47, 233,
+ ])
+ );
+ });
+ });
+
+ describe('getHashedDataAsHex', () => {
+ test('returns hashed data from a key and data', async () => {
+ expect(await getHashedDataAsHex(key, data)).toStrictEqual(
+ 'e8e4d9989c76a62a7cd9816338a3be833e9436aa65379e762aeff5889973299c'
+ );
+
+ expect(await getHashedDataAsHex(arrayBuffer, arrayBuffer)).toStrictEqual(
+ 'f375180aba92888401f1919be4a8715a62763b65c1c10e1d0858e81d4d6f9fd2'
+ );
+
+ expect(
+ await getHashedDataAsHex(arrayBufferView, arrayBufferView)
+ ).toStrictEqual(
+ 'f375180aba92888401f1919be4a8715a62763b65c1c10e1d0858e81d4d6f9fd2'
+ );
+ });
+
+ test('returns hashed data from just data', async () => {
+ expect(await getHashedDataAsHex(null, data)).toStrictEqual(
+ '71170f2f183e105cdc85ee7e3232aca118573cf75325314bbe574a9fe05f2fe9'
+ );
+ });
+ });
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalHeaders-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalHeaders-test.ts
new file mode 100644
index 00000000000..cc1ec43a5da
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalHeaders-test.ts
@@ -0,0 +1,26 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { getCanonicalHeaders } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalHeaders';
+
+describe('getCanonicalHeaders', () => {
+ test('lowercases keys', () => {
+ const headers = { BAR: 'BAR', fOo: 'fOo' };
+ expect(getCanonicalHeaders(headers)).toBe('bar:BAR\nfoo:fOo\n');
+ });
+
+ test('trims values', () => {
+ const headers = { foobar: ' f o o b a r ' };
+ expect(getCanonicalHeaders(headers)).toBe('foobar:f o o b a r\n');
+ });
+
+ test('sorts by keys', () => {
+ const headers = { baz: 'baz', foo: 'foo', bar: 'bar' };
+ expect(getCanonicalHeaders(headers)).toBe('bar:bar\nbaz:baz\nfoo:foo\n');
+ });
+
+ test('handles empty headers', () => {
+ const headers = {};
+ expect(getCanonicalHeaders(headers)).toBe('');
+ });
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalQueryString-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalQueryString-test.ts
new file mode 100644
index 00000000000..2c85aad34bb
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalQueryString-test.ts
@@ -0,0 +1,20 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { getCanonicalQueryString } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalQueryString';
+
+describe('getCanonicalQueryString', () => {
+ test('sorts by keys and then by values', () => {
+ const url = new URL('https://sub.domain?baz=qux&foo=foo&bar=bar&baz=baz');
+ expect(getCanonicalQueryString(url.searchParams)).toBe(
+ 'bar=bar&baz=baz&baz=qux&foo=foo'
+ );
+ });
+
+ test('encodes both key and value', () => {
+ const url = new URL("https://sub.domain?(f!o'o)=*{f o$o}");
+ expect(getCanonicalQueryString(url.searchParams)).toBe(
+ '%28f%21o%27o%29=%2A%7Bf%20o%24o%7D'
+ );
+ });
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest-test.ts
new file mode 100644
index 00000000000..55aa404a65d
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest-test.ts
@@ -0,0 +1,17 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { getCanonicalRequest } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest';
+
+describe('getCanonicalRequest', () => {
+ test('returns a canonical request', async () => {
+ const request = {
+ headers: {},
+ method: 'POST',
+ url: new URL('https://sub.domain'),
+ };
+ expect(await getCanonicalRequest(request)).toBe(
+ 'POST\n/\n\n\n\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
+ );
+ });
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalUri-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalUri-test.ts
new file mode 100644
index 00000000000..bfb4c70e1ae
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalUri-test.ts
@@ -0,0 +1,25 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { getCanonicalUri } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalUri';
+
+describe('getCanonicalUri', () => {
+ test('returns a canonical uri', () => {
+ const path = '/foo/bar/baz';
+ expect(getCanonicalUri(path)).toBe(path);
+ });
+
+ test('handles empty path', () => {
+ expect(getCanonicalUri('')).toBe('/');
+ });
+
+ test('handles utf8', () => {
+ const path = '/\u03A9';
+ expect(getCanonicalUri(path)).toBe('/%CE%A9');
+ });
+
+ test('handles unicode', () => {
+ const path = '/👀';
+ expect(getCanonicalUri(path)).toBe('/%F0%9F%91%80');
+ });
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCredentialScope-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCredentialScope-test.ts
new file mode 100644
index 00000000000..146c9513d6f
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCredentialScope-test.ts
@@ -0,0 +1,22 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import {
+ credentialScope,
+ formattedDates,
+ signingRegion,
+ signingService,
+} from '../testUtils/data';
+import { getCredentialScope } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getCredentialScope';
+
+describe('getCredentialScope', () => {
+ test('returns a credential scope', () => {
+ expect(
+ getCredentialScope(
+ formattedDates.shortDate,
+ signingRegion,
+ signingService
+ )
+ ).toBe(credentialScope);
+ });
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getFormattedDates-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getFormattedDates-test.ts
new file mode 100644
index 00000000000..c67831de1e3
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getFormattedDates-test.ts
@@ -0,0 +1,15 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { formattedDates, signingDate } from '../testUtils/data';
+import { getFormattedDates } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getFormattedDates';
+
+describe('getFormattedDates', () => {
+ test('returns formatted dates', () => {
+ const { longDate, shortDate } = formattedDates;
+ expect(getFormattedDates(signingDate)).toStrictEqual({
+ longDate,
+ shortDate,
+ });
+ });
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload-test.ts
new file mode 100644
index 00000000000..aa6ce463794
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload-test.ts
@@ -0,0 +1,41 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { getHashedPayload } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload';
+
+describe('getHashedPayload', () => {
+ test('returns empty hash if nullish', async () => {
+ expect(await getHashedPayload(undefined)).toStrictEqual(
+ 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
+ );
+ expect(await getHashedPayload(null as any)).toStrictEqual(
+ 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
+ );
+ });
+
+ test('returns hashed payload', async () => {
+ expect(await getHashedPayload('string-body')).toStrictEqual(
+ 'f4241e27b103e1a1d7f88a1556541718250245fe31c8e695f3e068d3fe837572'
+ );
+ });
+
+ test('works with ArrayBuffer', async () => {
+ expect(await getHashedPayload(new ArrayBuffer(8))).toStrictEqual(
+ 'af5570f5a1810b7af78caf4bc70a660f0df51e42baf91d4de5b2328de0e83dfc'
+ );
+
+ expect(
+ await getHashedPayload(new Uint8Array(new ArrayBuffer(8)))
+ ).toStrictEqual(
+ 'af5570f5a1810b7af78caf4bc70a660f0df51e42baf91d4de5b2328de0e83dfc'
+ );
+ });
+
+ test('returns unsigned payload if not hashable', async () => {
+ for (const scalar of [123.234, true, new Blob()]) {
+ expect(await getHashedPayload(scalar as any)).toStrictEqual(
+ 'UNSIGNED-PAYLOAD'
+ );
+ }
+ });
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSignature-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSignature-test.ts
new file mode 100644
index 00000000000..3f08598c121
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSignature-test.ts
@@ -0,0 +1,31 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import {
+ credentials,
+ credentialScope,
+ formattedDates,
+ getDefaultRequest,
+ signingRegion,
+ signingService,
+} from '../testUtils/data';
+import { getSignature } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getSignature';
+
+describe('getSignature', () => {
+ test('returns signature', async () => {
+ const { longDate, shortDate } = formattedDates;
+ const signingValues = {
+ ...credentials,
+ credentialScope,
+ longDate,
+ shortDate,
+ signingRegion,
+ signingService,
+ };
+ expect(
+ await getSignature(getDefaultRequest(), signingValues)
+ ).toStrictEqual(
+ '145191af25230efbe34c7eb79d9d3ce881f7a945d02d0361719107147d0086b3'
+ );
+ });
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSignedHeaders-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSignedHeaders-test.ts
new file mode 100644
index 00000000000..226eefe2dd7
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSignedHeaders-test.ts
@@ -0,0 +1,16 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { getSignedHeaders } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getSignedHeaders';
+
+describe('getSignedHeaders', () => {
+ test('lowercases keys', () => {
+ const headers = { BAR: 'BAR', fOo: 'fOo' };
+ expect(getSignedHeaders(headers)).toBe('bar;foo');
+ });
+
+ test('sorts by keys', () => {
+ const headers = { baz: 'baz', foo: 'foo', bar: 'bar' };
+ expect(getSignedHeaders(headers)).toBe('bar;baz;foo');
+ });
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSigningKey-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSigningKey-test.ts
new file mode 100644
index 00000000000..4f91e76c8d2
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSigningKey-test.ts
@@ -0,0 +1,17 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { getSigningKey } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getSigningKey';
+
+describe('getSigningKey', () => {
+ test('returns a signing key', async () => {
+ expect(
+ await getSigningKey('secret-access-key', '20200918', 'region', 'service')
+ ).toStrictEqual(
+ new Uint8Array([
+ 79, 189, 20, 186, 57, 62, 187, 22, 80, 142, 29, 192, 182, 56, 183, 254,
+ 40, 157, 31, 233, 13, 76, 236, 41, 206, 90, 24, 22, 52, 165, 235, 99,
+ ])
+ );
+ });
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSigningValues-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSigningValues-test.ts
new file mode 100644
index 00000000000..7087af518e9
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSigningValues-test.ts
@@ -0,0 +1,33 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import {
+ credentialScope,
+ credentialsWithToken,
+ formattedDates,
+ signingDate,
+ signingRegion,
+ signingService,
+} from '../testUtils/data';
+import { getSigningValues } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getSigningValues';
+
+describe('getSigningValues', () => {
+ test('returns signing values', () => {
+ const { longDate, shortDate } = formattedDates;
+ expect(
+ getSigningValues({
+ credentials: credentialsWithToken,
+ signingDate,
+ signingRegion,
+ signingService,
+ })
+ ).toStrictEqual({
+ ...credentialsWithToken,
+ credentialScope,
+ longDate,
+ shortDate,
+ signingRegion,
+ signingService,
+ });
+ });
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getStringToSign-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getStringToSign-test.ts
new file mode 100644
index 00000000000..16228387b6b
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getStringToSign-test.ts
@@ -0,0 +1,19 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { credentialScope, formattedDates } from '../testUtils/data';
+import { getStringToSign } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getStringToSign';
+
+describe('getStringToSign', () => {
+ test('returns signature', async () => {
+ expect(
+ await getStringToSign(
+ formattedDates.longDate,
+ credentialScope,
+ 'hashed-request'
+ )
+ ).toStrictEqual(
+ 'AWS4-HMAC-SHA256\n20200918T181818Z\n20200918/signing-region/signing-service/aws4_request\nhashed-request'
+ );
+ });
+});
diff --git a/packages/core/package.json b/packages/core/package.json
index 36649702a8f..9542ff8a3e1 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -132,6 +132,7 @@
"^.+\\.(js|jsx|ts|tsx)$": "ts-jest"
},
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(tsx?|jsx?)$",
+ "testPathIgnorePatterns": ["/testUtils/"],
"moduleFileExtensions": [
"ts",
"tsx",
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/constants.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/constants.ts
new file mode 100644
index 00000000000..6d77e9964ee
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/constants.ts
@@ -0,0 +1,28 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+// query params
+export const ALGORITHM_QUERY_PARAM = 'X-Amz-Algorithm';
+export const AMZ_DATE_QUERY_PARAM = 'X-Amz-Date';
+export const CREDENTIAL_QUERY_PARAM = 'X-Amz-Credential';
+export const EXPIRES_QUERY_PARAM = 'X-Amz-Expires';
+export const REGION_SET_PARAM = 'X-Amz-Region-Set';
+export const SIGNATURE_QUERY_PARAM = 'X-Amz-Signature';
+export const SIGNED_HEADERS_QUERY_PARAM = 'X-Amz-SignedHeaders';
+export const TOKEN_QUERY_PARAM = 'X-Amz-Security-Token';
+
+// headers
+export const AUTH_HEADER = 'authorization';
+export const HOST_HEADER = 'host';
+export const AMZ_DATE_HEADER = AMZ_DATE_QUERY_PARAM.toLowerCase();
+export const TOKEN_HEADER = TOKEN_QUERY_PARAM.toLowerCase();
+
+// identifiers
+export const KEY_TYPE_IDENTIFIER = 'aws4_request';
+export const SHA256_ALGORITHM_IDENTIFIER = 'AWS4-HMAC-SHA256';
+export const SIGNATURE_IDENTIFIER = 'AWS4';
+
+// preset values
+export const EMPTY_HASH =
+ 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
+export const UNSIGNED_PAYLOAD = 'UNSIGNED-PAYLOAD';
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/index.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/index.ts
new file mode 100644
index 00000000000..7ba8a359f1e
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/index.ts
@@ -0,0 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+// TODO: V6 replace Signer
+export { signRequest } from './signRequest';
+export { presignUrl } from './presignUrl';
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/presignUrl.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/presignUrl.ts
new file mode 100644
index 00000000000..c7c5eb71c41
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/presignUrl.ts
@@ -0,0 +1,58 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { Presignable, PresignUrlOptions } from './types';
+import {
+ ALGORITHM_QUERY_PARAM,
+ AMZ_DATE_QUERY_PARAM,
+ CREDENTIAL_QUERY_PARAM,
+ EXPIRES_QUERY_PARAM,
+ HOST_HEADER,
+ SHA256_ALGORITHM_IDENTIFIER,
+ SIGNATURE_QUERY_PARAM,
+ SIGNED_HEADERS_QUERY_PARAM,
+ TOKEN_QUERY_PARAM,
+} from './constants';
+import { getSigningValues } from './utils/getSigningValues';
+import { getSignature } from './utils/getSignature';
+
+/**
+ * Given a `Presignable` object, returns a Signature Version 4 presigned `URL` object.
+ *
+ * @param presignable `Presignable` object containing at least a url to be presigned with authentication query params.
+ * @param presignUrlOptions `PresignUrlOptions` object containing values used to construct the signature.
+ * @returns A `URL` with authentication query params which can grant temporary access to AWS resources.
+ */
+export const presignUrl = async (
+ { body, method = 'GET', url }: Presignable,
+ { expiration, ...options }: PresignUrlOptions
+): Promise => {
+ const signingValues = getSigningValues(options);
+ const { accessKeyId, credentialScope, longDate, sessionToken } =
+ signingValues;
+
+ // create the request to sign
+ const presignedUrl = new URL(url);
+ Object.entries({
+ [ALGORITHM_QUERY_PARAM]: SHA256_ALGORITHM_IDENTIFIER,
+ [CREDENTIAL_QUERY_PARAM]: `${accessKeyId}/${credentialScope}`,
+ [AMZ_DATE_QUERY_PARAM]: longDate,
+ [SIGNED_HEADERS_QUERY_PARAM]: HOST_HEADER,
+ ...(expiration && { [EXPIRES_QUERY_PARAM]: expiration.toString() }),
+ ...(sessionToken && { [TOKEN_QUERY_PARAM]: sessionToken }),
+ }).forEach(([key, value]) => {
+ presignedUrl.searchParams.append(key, value);
+ });
+ const requestToSign = {
+ body,
+ headers: { [HOST_HEADER]: url.host },
+ method,
+ url: presignedUrl,
+ };
+
+ // calculate and add the signature to the url
+ const signature = await getSignature(requestToSign, signingValues);
+ presignedUrl.searchParams.append(SIGNATURE_QUERY_PARAM, signature);
+
+ return presignedUrl;
+};
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/signRequest.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/signRequest.ts
new file mode 100644
index 00000000000..da1ab9b9a1c
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/signRequest.ts
@@ -0,0 +1,51 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { HttpRequest } from '../../../../types';
+import { SignRequestOptions } from './types/signer';
+import { getSignedHeaders } from './utils/getSignedHeaders';
+import { getSigningValues } from './utils/getSigningValues';
+import {
+ AMZ_DATE_HEADER,
+ AUTH_HEADER,
+ HOST_HEADER,
+ SHA256_ALGORITHM_IDENTIFIER,
+ TOKEN_HEADER,
+} from './constants';
+import { getSignature } from './utils/getSignature';
+
+/**
+ * Given a `HttpRequest`, returns a Signature Version 4 signed `HttpRequest`.
+ *
+ * @param request `HttpRequest` to be signed.
+ * @param signRequestOptions `SignRequestOptions` object containing values used to construct the signature.
+ * @returns A `HttpRequest` with authentication headers which can grant temporary access to AWS resources.
+ */
+export const signRequest = async (
+ request: HttpRequest,
+ options: SignRequestOptions
+): Promise => {
+ const signingValues = getSigningValues(options);
+ const { accessKeyId, credentialScope, longDate, sessionToken } =
+ signingValues;
+
+ // create the request to sign
+ const headers = { ...request.headers };
+ headers[HOST_HEADER] = request.url.host;
+ headers[AMZ_DATE_HEADER] = longDate;
+ if (sessionToken) {
+ headers[TOKEN_HEADER] = sessionToken;
+ }
+ const requestToSign = { ...request, headers };
+
+ // calculate and add the signature to the request
+ const signature = await getSignature(requestToSign, signingValues);
+ const credentialEntry = `Credential=${accessKeyId}/${credentialScope}`;
+ const signedHeadersEntry = `SignedHeaders=${getSignedHeaders(headers)}`;
+ const signatureEntry = `Signature=${signature}`;
+ headers[
+ AUTH_HEADER
+ ] = `${SHA256_ALGORITHM_IDENTIFIER} ${credentialEntry}, ${signedHeadersEntry}, ${signatureEntry}`;
+
+ return requestToSign;
+};
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/types/index.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/types/index.ts
new file mode 100644
index 00000000000..0d40a13dffd
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/types/index.ts
@@ -0,0 +1,4 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+export { Presignable, PresignUrlOptions, SignRequestOptions } from './signer';
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/types/signer.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/types/signer.ts
new file mode 100644
index 00000000000..84d5c35f599
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/types/signer.ts
@@ -0,0 +1,31 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { Credentials, HttpRequest } from '../../../../../types';
+
+export interface SignRequestOptions {
+ credentials: Credentials;
+ signingDate?: Date;
+ signingRegion: string;
+ signingService: string;
+}
+
+export interface PresignUrlOptions extends SignRequestOptions {
+ expiration?: number;
+}
+
+export interface Presignable extends Pick {
+ method?: HttpRequest['method'];
+}
+
+export interface FormattedDates {
+ longDate: string;
+ shortDate: string;
+}
+
+export interface SigningValues
+ extends Credentials,
+ FormattedDates,
+ Pick {
+ credentialScope: string;
+}
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers.ts
new file mode 100644
index 00000000000..ed34d6ee84e
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers.ts
@@ -0,0 +1,39 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+// TODO: V6 update to different crypto dependency?
+import { Sha256 } from '@aws-crypto/sha256-js';
+import { SourceData } from '@aws-sdk/types';
+import { toHex } from '@aws-sdk/util-hex-encoding';
+
+/**
+ * Returns the hashed data a `Uint8Array`.
+ *
+ * @param key `SourceData` to be used as hashing key.
+ * @param data Hashable `SourceData`.
+ * @returns `Uint8Array` created from the data as input to a hash function.
+ */
+export const getHashedData = async (
+ key: SourceData | null,
+ data: SourceData
+): Promise => {
+ const sha256 = new Sha256(key);
+ sha256.update(data);
+ const hashedData = await sha256.digest();
+ return hashedData;
+};
+
+/**
+ * Returns the hashed data as a hex string.
+ *
+ * @param key `SourceData` to be used as hashing key.
+ * @param data Hashable `SourceData`.
+ * @returns String using lowercase hexadecimal characters created from the data as input to a hash function.
+ */
+export const getHashedDataAsHex = async (
+ key: SourceData | null,
+ data: SourceData
+): Promise => {
+ const hashedData = await getHashedData(key, data);
+ return toHex(hashedData);
+};
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalHeaders.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalHeaders.ts
new file mode 100644
index 00000000000..85cb3ba1148
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalHeaders.ts
@@ -0,0 +1,23 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { HttpRequest } from '../../../../../types';
+
+/**
+ * Returns canonical headers.
+ *
+ * @param headers Headers from the request.
+ * @returns Request headers that will be signed, and their values, separated by newline characters. Header names must
+ * use lowercase characters, must appear in alphabetical order, and must be followed by a colon (:). For the values,
+ * trim any leading or trailing spaces, convert sequential spaces to a single space, and separate the values
+ * for a multi-value header using commas.
+ */
+export const getCanonicalHeaders = (headers: HttpRequest['headers']): string =>
+ Object.entries(headers)
+ .map(([key, value]) => ({
+ key: key.toLowerCase(),
+ value: value?.trim().replace(/\s+/g, ' ') ?? '',
+ }))
+ .sort((a, b) => (a.key < b.key ? -1 : 1))
+ .map(entry => `${entry.key}:${entry.value}\n`)
+ .join('');
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalQueryString.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalQueryString.ts
new file mode 100644
index 00000000000..09fdec6cc6a
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalQueryString.ts
@@ -0,0 +1,30 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+/**
+ * Returns a canonical query string.
+ *
+ * @param searchParams `searchParams` from the request url.
+ * @returns URL-encoded query string parameters, separated by ampersands (&). Percent-encode reserved characters,
+ * including the space character. Encode names and values separately. If there are empty parameters, append the equals
+ * sign to the parameter name before encoding. After encoding, sort the parameters alphabetically by key name. If there
+ * is no query string, use an empty string ("").
+ */
+export const getCanonicalQueryString = (
+ searchParams: URLSearchParams
+): string =>
+ Array.from(searchParams)
+ .sort(([keyA, valA], [keyB, valB]) => {
+ if (keyA === keyB) {
+ return valA < valB ? -1 : 1;
+ }
+ return keyA < keyB ? -1 : 1;
+ })
+ .map(([key, val]) => `${escapeUri(key)}=${escapeUri(val)}`)
+ .join('&');
+
+const escapeUri = (uri: string): string =>
+ encodeURIComponent(uri).replace(/[!'()*]/g, hexEncode);
+
+const hexEncode = (c: string) =>
+ `%${c.charCodeAt(0).toString(16).toUpperCase()}`;
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest.ts
new file mode 100644
index 00000000000..674e68c6bbd
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest.ts
@@ -0,0 +1,36 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { HttpRequest } from '../../../../../types';
+import { getCanonicalHeaders } from './getCanonicalHeaders';
+import { getCanonicalQueryString } from './getCanonicalQueryString';
+import { getCanonicalUri } from './getCanonicalUri';
+import { getHashedPayload } from './getHashedPayload';
+import { getSignedHeaders } from './getSignedHeaders';
+
+/**
+ * Returns a canonical request.
+ *
+ * @param request `HttpRequest` from which to create the canonical request from.
+ * @returns String created by by concatenating the following strings, separated by newline characters:
+ * - HTTPMethod
+ * - CanonicalUri
+ * - CanonicalQueryString
+ * - CanonicalHeaders
+ * - SignedHeaders
+ * - HashedPayload
+ */
+export const getCanonicalRequest = async ({
+ body,
+ headers,
+ method,
+ url,
+}: HttpRequest): Promise =>
+ [
+ method,
+ getCanonicalUri(url.pathname),
+ getCanonicalQueryString(url.searchParams),
+ getCanonicalHeaders(headers),
+ getSignedHeaders(headers),
+ await getHashedPayload(body),
+ ].join('\n');
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalUri.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalUri.ts
new file mode 100644
index 00000000000..b16975077a0
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalUri.ts
@@ -0,0 +1,12 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+/**
+ * Returns a canonical uri.
+ *
+ * @param pathname `pathname` from request url.
+ * @returns URI-encoded version of the absolute path component URL (everything between the host and the question mark
+ * character (?) that starts the query string parameters). If the absolute path is empty, a forward slash character (/).
+ */
+export const getCanonicalUri = (pathname: string): string =>
+ pathname ? encodeURIComponent(pathname).replace(/%2F/g, '/') : '/';
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCredentialScope.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCredentialScope.ts
new file mode 100644
index 00000000000..5a9ddf43079
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCredentialScope.ts
@@ -0,0 +1,19 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { KEY_TYPE_IDENTIFIER } from '../constants';
+
+/**
+ * Returns the credential scope which restricts the resulting signature to the specified region and service.
+ *
+ * @param date Current date in the format 'YYYYMMDD'.
+ * @param region AWS region in which the service resides.
+ * @param service Service to which the signed request is being sent.
+ *
+ * @returns A string representing the credential scope with format 'YYYYMMDD/region/service/aws4_request'.
+ */
+export const getCredentialScope = (
+ date: string,
+ region: string,
+ service: string
+): string => `${date}/${region}/${service}/${KEY_TYPE_IDENTIFIER}`;
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getFormattedDates.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getFormattedDates.ts
new file mode 100644
index 00000000000..72387fa8948
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getFormattedDates.ts
@@ -0,0 +1,20 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { FormattedDates } from '../types/signer';
+
+/**
+ * Returns expected date strings to be used in signing.
+ *
+ * @param date JavaScript `Date` object.
+ * @returns `FormattedDates` object containing the following:
+ * - longDate: A date string in 'YYYYMMDDThhmmssZ' format
+ * - shortDate: A date string in 'YYYYMMDD' format
+ */
+export const getFormattedDates = (date: Date): FormattedDates => {
+ const longDate = date.toISOString().replace(/[:\-]|\.\d{3}/g, '');
+ return {
+ longDate,
+ shortDate: longDate.slice(0, 8),
+ };
+};
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.ts
new file mode 100644
index 00000000000..84d9cd09b60
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.ts
@@ -0,0 +1,38 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { SourceData } from '@aws-sdk/types';
+import { HttpRequest } from '../../../../../types';
+import { EMPTY_HASH, UNSIGNED_PAYLOAD } from '../constants';
+import { getHashedDataAsHex } from './dataHashHelpers';
+
+/**
+ * Returns the hashed payload.
+ *
+ * @param body `body` (payload) from the request.
+ * @returns String created using the payload in the body of the HTTP request as input to a hash function. This string
+ * uses lowercase hexadecimal characters. If the payload is empty, return precalculated result of an empty hash.
+ */
+export const getHashedPayload = async (
+ body: HttpRequest['body']
+): Promise => {
+ // return precalculated empty hash if body is undefined or null
+ if (body == null) {
+ return EMPTY_HASH;
+ }
+
+ if (isSourceData(body)) {
+ const hashedData = await getHashedDataAsHex(null, body);
+ return hashedData;
+ }
+
+ // Defined body is not signable. Return unsigned payload which may or may not be accepted by the service.
+ return UNSIGNED_PAYLOAD;
+};
+
+const isSourceData = (body: HttpRequest['body']): body is SourceData =>
+ typeof body === 'string' || ArrayBuffer.isView(body) || isArrayBuffer(body);
+
+const isArrayBuffer = (arg: any): arg is ArrayBuffer =>
+ (typeof ArrayBuffer === 'function' && arg instanceof ArrayBuffer) ||
+ Object.prototype.toString.call(arg) === '[object ArrayBuffer]';
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignature.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignature.ts
new file mode 100644
index 00000000000..864505a157c
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignature.ts
@@ -0,0 +1,55 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { HttpRequest } from '../../../../../types';
+import { SigningValues } from '../types/signer';
+import { getHashedDataAsHex } from './dataHashHelpers';
+import { getCanonicalRequest } from './getCanonicalRequest';
+import { getSigningKey } from './getSigningKey';
+import { getStringToSign } from './getStringToSign';
+
+/**
+ * Calculates and returns an AWS API Signature.
+ * https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html
+ *
+ * @param request `HttpRequest` to be signed.
+ * @param signRequestOptions `SignRequestOptions` object containing values used to construct the signature.
+ * @returns AWS API Signature to sign a request or url with.
+ */
+export const getSignature = async (
+ request: HttpRequest,
+ {
+ credentialScope,
+ longDate,
+ secretAccessKey,
+ shortDate,
+ signingRegion,
+ signingService,
+ }: SigningValues
+): Promise => {
+ // step 1: create a canonical request
+ const canonicalRequest = await getCanonicalRequest(request);
+
+ // step 2: create a hash of the canonical request
+ const hashedRequest = await getHashedDataAsHex(null, canonicalRequest);
+
+ // step 3: create a string to sign
+ const stringToSign = await getStringToSign(
+ longDate,
+ credentialScope,
+ hashedRequest
+ );
+
+ // step 4: calculate the signature
+ const signature = await getHashedDataAsHex(
+ await getSigningKey(
+ secretAccessKey,
+ shortDate,
+ signingRegion,
+ signingService
+ ),
+ stringToSign
+ );
+
+ return signature;
+};
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignedHeaders.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignedHeaders.ts
new file mode 100644
index 00000000000..fbf480d1ffb
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignedHeaders.ts
@@ -0,0 +1,17 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { HttpRequest } from '../../../../../types';
+
+/**
+ * Returns signed headers.
+ *
+ * @param headers `headers` from the request.
+ * @returns List of headers included in canonical headers, separated by semicolons (;). This indicates which headers
+ * are part of the signing process. Header names must use lowercase characters and must appear in alphabetical order.
+ */
+export const getSignedHeaders = (headers: HttpRequest['headers']): string =>
+ Object.keys(headers)
+ .map(key => key.toLowerCase())
+ .sort()
+ .join(';');
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningKey.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningKey.ts
new file mode 100644
index 00000000000..f061a72fc59
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningKey.ts
@@ -0,0 +1,29 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { KEY_TYPE_IDENTIFIER, SIGNATURE_IDENTIFIER } from '../constants';
+import { getHashedData } from './dataHashHelpers';
+
+/**
+ * Returns a signing key to be used for signing requests.
+ *
+ * @param secretAccessKey AWS secret access key from credentials.
+ * @param date Current date in the format 'YYYYMMDD'.
+ * @param region AWS region in which the service resides.
+ * @param service Service to which the signed request is being sent.
+ *
+ * @returns `Uint8Array` calculated from its composite parts.
+ */
+export const getSigningKey = async (
+ secretAccessKey: string,
+ date: string,
+ region: string,
+ service: string
+): Promise => {
+ const key = `${SIGNATURE_IDENTIFIER}${secretAccessKey}`;
+ const dateKey = await getHashedData(key, date);
+ const regionKey = await getHashedData(dateKey, region);
+ const serviceKey = await getHashedData(regionKey, service);
+ const signingKey = await getHashedData(serviceKey, KEY_TYPE_IDENTIFIER);
+ return signingKey;
+};
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningValues.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningValues.ts
new file mode 100644
index 00000000000..594a0fcf3e2
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningValues.ts
@@ -0,0 +1,41 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { SignRequestOptions } from '../types';
+import { SigningValues } from '../types/signer';
+import { getCredentialScope } from './getCredentialScope';
+import { getFormattedDates } from './getFormattedDates';
+
+/**
+ * Extracts common values used for signing both requests and urls.
+ *
+ * @param options `SignRequestOptions` object containing values used to construct the signature.
+ * @returns Common `SigningValues` used for signing.
+ */
+export const getSigningValues = ({
+ credentials,
+ signingDate = new Date(),
+ signingRegion,
+ signingService,
+}: SignRequestOptions): SigningValues => {
+ // get properties from credentials
+ const { accessKeyId, secretAccessKey, sessionToken } = credentials;
+ // get formatted dates for signing
+ const { longDate, shortDate } = getFormattedDates(signingDate);
+ // copy header and set signing properties
+ const credentialScope = getCredentialScope(
+ shortDate,
+ signingRegion,
+ signingService
+ );
+ return {
+ accessKeyId,
+ credentialScope,
+ longDate,
+ secretAccessKey,
+ sessionToken,
+ shortDate,
+ signingRegion,
+ signingService,
+ };
+};
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getStringToSign.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getStringToSign.ts
new file mode 100644
index 00000000000..8a522b9c94a
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getStringToSign.ts
@@ -0,0 +1,26 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { SHA256_ALGORITHM_IDENTIFIER } from '../constants';
+
+/**
+ * Returns a string to be signed.
+ *
+ * @param date Current date in the format 'YYYYMMDDThhmmssZ'.
+ * @param credentialScope String representing the credential scope with format 'YYYYMMDD/region/service/aws4_request'.
+ * @param hashedRequest Hashed canonical request.
+ *
+ * @returns A string created by by concatenating the following strings, separated by newline characters:
+ * - Algorithm
+ * - RequestDateTime
+ * - CredentialScope
+ * - HashedCanonicalRequest
+ */
+export const getStringToSign = (
+ date: string,
+ credentialScope: string,
+ hashedRequest: string
+): string =>
+ [SHA256_ALGORITHM_IDENTIFIER, date, credentialScope, hashedRequest].join(
+ '\n'
+ );
diff --git a/packages/tsconfig.base.json b/packages/tsconfig.base.json
index 4df845e444b..1160e2ac980 100644
--- a/packages/tsconfig.base.json
+++ b/packages/tsconfig.base.json
@@ -4,6 +4,7 @@
"noImplicitAny": false,
"lib": [
"dom",
+ "dom.iterable",
"es2017",
"esnext.asynciterable",
"es2018.asyncgenerator",
From 56dc9e086aec4d9c6c7ef511198930d355a627a9 Mon Sep 17 00:00:00 2001
From: Chris F <5827964+cshfang@users.noreply.github.com>
Date: Wed, 26 Apr 2023 10:18:25 -0700
Subject: [PATCH 12/41] chore(clients): Make signing functions synchronous
(#11307)
---
.../signer/signatureV4/presignUrl-test.ts | 4 +--
.../signer/signatureV4/signRequest-test.ts | 6 ++---
.../signatureV4/utils/dataHashHelpers-test.ts | 26 +++++++++----------
.../utils/getCanonicalRequest-test.ts | 4 +--
.../utils/getHashedPayload-test.ts | 24 +++++++----------
.../signatureV4/utils/getSignature-test.ts | 6 ++---
.../signatureV4/utils/getSigningKey-test.ts | 4 +--
.../signatureV4/utils/getStringToSign-test.ts | 4 +--
.../signing/signer/signatureV4/presignUrl.ts | 6 ++---
.../signing/signer/signatureV4/signRequest.ts | 6 ++---
.../signatureV4/utils/dataHashHelpers.ts | 13 +++++-----
.../signatureV4/utils/getCanonicalRequest.ts | 6 ++---
.../signatureV4/utils/getHashedPayload.ts | 6 ++---
.../signer/signatureV4/utils/getSignature.ts | 19 +++++---------
.../signer/signatureV4/utils/getSigningKey.ts | 12 ++++-----
15 files changed, 65 insertions(+), 81 deletions(-)
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/presignUrl-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/presignUrl-test.ts
index c040eedd9c1..f5ca9b11795 100644
--- a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/presignUrl-test.ts
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/presignUrl-test.ts
@@ -24,7 +24,7 @@ describe('presignUrl', () => {
return [name, updatedRequest, updatedOptions, expectedUrl];
}
)
- )('presigns url with %s', async (_, request, options, expected) => {
- expect((await presignUrl(request, options)).toString()).toBe(expected);
+ )('presigns url with %s', (_, request, options, expected) => {
+ expect(presignUrl(request, options).toString()).toBe(expected);
});
});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/signRequest-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/signRequest-test.ts
index 4ec61e8ad1f..5cd4c2a4ed8 100644
--- a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/signRequest-test.ts
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/signRequest-test.ts
@@ -23,9 +23,7 @@ describe('signRequest', () => {
return [name, updatedRequest, updatedOptions, expectedAuthorization];
}
)
- )('signs request with %s', async (_, request, options, expected) => {
- expect((await signRequest(request, options)).headers.authorization).toBe(
- expected
- );
+ )('signs request with %s', (_, request, options, expected) => {
+ expect(signRequest(request, options).headers.authorization).toBe(expected);
});
});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers-test.ts
index 59c2955ed4c..b14eb7e9d74 100644
--- a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers-test.ts
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers-test.ts
@@ -13,8 +13,8 @@ describe('dataHashHelpers', () => {
const arrayBufferView = new Uint8Array(new ArrayBuffer(8));
describe('getHashedData', () => {
- test('returns hashed data from a key and data', async () => {
- expect(await getHashedData(key, data)).toStrictEqual(
+ test('returns hashed data from a key and data', () => {
+ expect(getHashedData(key, data)).toStrictEqual(
new Uint8Array([
232, 228, 217, 152, 156, 118, 166, 42, 124, 217, 129, 99, 56, 163,
190, 131, 62, 148, 54, 170, 101, 55, 158, 118, 42, 239, 245, 136, 153,
@@ -22,7 +22,7 @@ describe('dataHashHelpers', () => {
])
);
- expect(await getHashedData(arrayBuffer, arrayBuffer)).toStrictEqual(
+ expect(getHashedData(arrayBuffer, arrayBuffer)).toStrictEqual(
new Uint8Array([
243, 117, 24, 10, 186, 146, 136, 132, 1, 241, 145, 155, 228, 168, 113,
90, 98, 118, 59, 101, 193, 193, 14, 29, 8, 88, 232, 29, 77, 111, 159,
@@ -30,9 +30,7 @@ describe('dataHashHelpers', () => {
])
);
- expect(
- await getHashedData(arrayBufferView, arrayBufferView)
- ).toStrictEqual(
+ expect(getHashedData(arrayBufferView, arrayBufferView)).toStrictEqual(
new Uint8Array([
243, 117, 24, 10, 186, 146, 136, 132, 1, 241, 145, 155, 228, 168, 113,
90, 98, 118, 59, 101, 193, 193, 14, 29, 8, 88, 232, 29, 77, 111, 159,
@@ -41,8 +39,8 @@ describe('dataHashHelpers', () => {
);
});
- test('returns hashed data from just data', async () => {
- expect(await getHashedData(null, data)).toStrictEqual(
+ test('returns hashed data from just data', () => {
+ expect(getHashedData(null, data)).toStrictEqual(
new Uint8Array([
113, 23, 15, 47, 24, 62, 16, 92, 220, 133, 238, 126, 50, 50, 172, 161,
24, 87, 60, 247, 83, 37, 49, 75, 190, 87, 74, 159, 224, 95, 47, 233,
@@ -52,24 +50,24 @@ describe('dataHashHelpers', () => {
});
describe('getHashedDataAsHex', () => {
- test('returns hashed data from a key and data', async () => {
- expect(await getHashedDataAsHex(key, data)).toStrictEqual(
+ test('returns hashed data from a key and data', () => {
+ expect(getHashedDataAsHex(key, data)).toStrictEqual(
'e8e4d9989c76a62a7cd9816338a3be833e9436aa65379e762aeff5889973299c'
);
- expect(await getHashedDataAsHex(arrayBuffer, arrayBuffer)).toStrictEqual(
+ expect(getHashedDataAsHex(arrayBuffer, arrayBuffer)).toStrictEqual(
'f375180aba92888401f1919be4a8715a62763b65c1c10e1d0858e81d4d6f9fd2'
);
expect(
- await getHashedDataAsHex(arrayBufferView, arrayBufferView)
+ getHashedDataAsHex(arrayBufferView, arrayBufferView)
).toStrictEqual(
'f375180aba92888401f1919be4a8715a62763b65c1c10e1d0858e81d4d6f9fd2'
);
});
- test('returns hashed data from just data', async () => {
- expect(await getHashedDataAsHex(null, data)).toStrictEqual(
+ test('returns hashed data from just data', () => {
+ expect(getHashedDataAsHex(null, data)).toStrictEqual(
'71170f2f183e105cdc85ee7e3232aca118573cf75325314bbe574a9fe05f2fe9'
);
});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest-test.ts
index 55aa404a65d..1e8c27ce460 100644
--- a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest-test.ts
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest-test.ts
@@ -4,13 +4,13 @@
import { getCanonicalRequest } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest';
describe('getCanonicalRequest', () => {
- test('returns a canonical request', async () => {
+ test('returns a canonical request', () => {
const request = {
headers: {},
method: 'POST',
url: new URL('https://sub.domain'),
};
- expect(await getCanonicalRequest(request)).toBe(
+ expect(getCanonicalRequest(request)).toBe(
'POST\n/\n\n\n\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
);
});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload-test.ts
index aa6ce463794..87c35b68bf3 100644
--- a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload-test.ts
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload-test.ts
@@ -4,38 +4,34 @@
import { getHashedPayload } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload';
describe('getHashedPayload', () => {
- test('returns empty hash if nullish', async () => {
- expect(await getHashedPayload(undefined)).toStrictEqual(
+ test('returns empty hash if nullish', () => {
+ expect(getHashedPayload(undefined)).toStrictEqual(
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
);
- expect(await getHashedPayload(null as any)).toStrictEqual(
+ expect(getHashedPayload(null as any)).toStrictEqual(
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
);
});
- test('returns hashed payload', async () => {
- expect(await getHashedPayload('string-body')).toStrictEqual(
+ test('returns hashed payload', () => {
+ expect(getHashedPayload('string-body')).toStrictEqual(
'f4241e27b103e1a1d7f88a1556541718250245fe31c8e695f3e068d3fe837572'
);
});
- test('works with ArrayBuffer', async () => {
- expect(await getHashedPayload(new ArrayBuffer(8))).toStrictEqual(
+ test('works with ArrayBuffer', () => {
+ expect(getHashedPayload(new ArrayBuffer(8))).toStrictEqual(
'af5570f5a1810b7af78caf4bc70a660f0df51e42baf91d4de5b2328de0e83dfc'
);
- expect(
- await getHashedPayload(new Uint8Array(new ArrayBuffer(8)))
- ).toStrictEqual(
+ expect(getHashedPayload(new Uint8Array(new ArrayBuffer(8)))).toStrictEqual(
'af5570f5a1810b7af78caf4bc70a660f0df51e42baf91d4de5b2328de0e83dfc'
);
});
- test('returns unsigned payload if not hashable', async () => {
+ test('returns unsigned payload if not hashable', () => {
for (const scalar of [123.234, true, new Blob()]) {
- expect(await getHashedPayload(scalar as any)).toStrictEqual(
- 'UNSIGNED-PAYLOAD'
- );
+ expect(getHashedPayload(scalar as any)).toStrictEqual('UNSIGNED-PAYLOAD');
}
});
});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSignature-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSignature-test.ts
index 3f08598c121..1cfe4c3a14c 100644
--- a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSignature-test.ts
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSignature-test.ts
@@ -12,7 +12,7 @@ import {
import { getSignature } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getSignature';
describe('getSignature', () => {
- test('returns signature', async () => {
+ test('returns signature', () => {
const { longDate, shortDate } = formattedDates;
const signingValues = {
...credentials,
@@ -22,9 +22,7 @@ describe('getSignature', () => {
signingRegion,
signingService,
};
- expect(
- await getSignature(getDefaultRequest(), signingValues)
- ).toStrictEqual(
+ expect(getSignature(getDefaultRequest(), signingValues)).toStrictEqual(
'145191af25230efbe34c7eb79d9d3ce881f7a945d02d0361719107147d0086b3'
);
});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSigningKey-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSigningKey-test.ts
index 4f91e76c8d2..45f6a2aedb0 100644
--- a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSigningKey-test.ts
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getSigningKey-test.ts
@@ -4,9 +4,9 @@
import { getSigningKey } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getSigningKey';
describe('getSigningKey', () => {
- test('returns a signing key', async () => {
+ test('returns a signing key', () => {
expect(
- await getSigningKey('secret-access-key', '20200918', 'region', 'service')
+ getSigningKey('secret-access-key', '20200918', 'region', 'service')
).toStrictEqual(
new Uint8Array([
79, 189, 20, 186, 57, 62, 187, 22, 80, 142, 29, 192, 182, 56, 183, 254,
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getStringToSign-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getStringToSign-test.ts
index 16228387b6b..605143f1a67 100644
--- a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getStringToSign-test.ts
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getStringToSign-test.ts
@@ -5,9 +5,9 @@ import { credentialScope, formattedDates } from '../testUtils/data';
import { getStringToSign } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getStringToSign';
describe('getStringToSign', () => {
- test('returns signature', async () => {
+ test('returns signature', () => {
expect(
- await getStringToSign(
+ getStringToSign(
formattedDates.longDate,
credentialScope,
'hashed-request'
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/presignUrl.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/presignUrl.ts
index c7c5eb71c41..b023029ffe2 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/presignUrl.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/presignUrl.ts
@@ -23,10 +23,10 @@ import { getSignature } from './utils/getSignature';
* @param presignUrlOptions `PresignUrlOptions` object containing values used to construct the signature.
* @returns A `URL` with authentication query params which can grant temporary access to AWS resources.
*/
-export const presignUrl = async (
+export const presignUrl = (
{ body, method = 'GET', url }: Presignable,
{ expiration, ...options }: PresignUrlOptions
-): Promise => {
+): URL => {
const signingValues = getSigningValues(options);
const { accessKeyId, credentialScope, longDate, sessionToken } =
signingValues;
@@ -51,7 +51,7 @@ export const presignUrl = async (
};
// calculate and add the signature to the url
- const signature = await getSignature(requestToSign, signingValues);
+ const signature = getSignature(requestToSign, signingValues);
presignedUrl.searchParams.append(SIGNATURE_QUERY_PARAM, signature);
return presignedUrl;
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/signRequest.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/signRequest.ts
index da1ab9b9a1c..132236fe4b4 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/signRequest.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/signRequest.ts
@@ -21,10 +21,10 @@ import { getSignature } from './utils/getSignature';
* @param signRequestOptions `SignRequestOptions` object containing values used to construct the signature.
* @returns A `HttpRequest` with authentication headers which can grant temporary access to AWS resources.
*/
-export const signRequest = async (
+export const signRequest = (
request: HttpRequest,
options: SignRequestOptions
-): Promise => {
+): HttpRequest => {
const signingValues = getSigningValues(options);
const { accessKeyId, credentialScope, longDate, sessionToken } =
signingValues;
@@ -39,7 +39,7 @@ export const signRequest = async (
const requestToSign = { ...request, headers };
// calculate and add the signature to the request
- const signature = await getSignature(requestToSign, signingValues);
+ const signature = getSignature(requestToSign, signingValues);
const credentialEntry = `Credential=${accessKeyId}/${credentialScope}`;
const signedHeadersEntry = `SignedHeaders=${getSignedHeaders(headers)}`;
const signatureEntry = `Signature=${signature}`;
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers.ts
index ed34d6ee84e..3742d135842 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers.ts
@@ -13,13 +13,14 @@ import { toHex } from '@aws-sdk/util-hex-encoding';
* @param data Hashable `SourceData`.
* @returns `Uint8Array` created from the data as input to a hash function.
*/
-export const getHashedData = async (
+export const getHashedData = (
key: SourceData | null,
data: SourceData
-): Promise => {
+): Uint8Array => {
const sha256 = new Sha256(key);
sha256.update(data);
- const hashedData = await sha256.digest();
+ // TODO: V6 flip to async digest
+ const hashedData = sha256.digestSync();
return hashedData;
};
@@ -30,10 +31,10 @@ export const getHashedData = async (
* @param data Hashable `SourceData`.
* @returns String using lowercase hexadecimal characters created from the data as input to a hash function.
*/
-export const getHashedDataAsHex = async (
+export const getHashedDataAsHex = (
key: SourceData | null,
data: SourceData
-): Promise => {
- const hashedData = await getHashedData(key, data);
+): string => {
+ const hashedData = getHashedData(key, data);
return toHex(hashedData);
};
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest.ts
index 674e68c6bbd..42618bb8f7b 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest.ts
@@ -20,17 +20,17 @@ import { getSignedHeaders } from './getSignedHeaders';
* - SignedHeaders
* - HashedPayload
*/
-export const getCanonicalRequest = async ({
+export const getCanonicalRequest = ({
body,
headers,
method,
url,
-}: HttpRequest): Promise =>
+}: HttpRequest): string =>
[
method,
getCanonicalUri(url.pathname),
getCanonicalQueryString(url.searchParams),
getCanonicalHeaders(headers),
getSignedHeaders(headers),
- await getHashedPayload(body),
+ getHashedPayload(body),
].join('\n');
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.ts
index 84d9cd09b60..2445d5ac32f 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.ts
@@ -13,16 +13,14 @@ import { getHashedDataAsHex } from './dataHashHelpers';
* @returns String created using the payload in the body of the HTTP request as input to a hash function. This string
* uses lowercase hexadecimal characters. If the payload is empty, return precalculated result of an empty hash.
*/
-export const getHashedPayload = async (
- body: HttpRequest['body']
-): Promise => {
+export const getHashedPayload = (body: HttpRequest['body']): string => {
// return precalculated empty hash if body is undefined or null
if (body == null) {
return EMPTY_HASH;
}
if (isSourceData(body)) {
- const hashedData = await getHashedDataAsHex(null, body);
+ const hashedData = getHashedDataAsHex(null, body);
return hashedData;
}
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignature.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignature.ts
index 864505a157c..6316b664fcd 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignature.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignature.ts
@@ -16,7 +16,7 @@ import { getStringToSign } from './getStringToSign';
* @param signRequestOptions `SignRequestOptions` object containing values used to construct the signature.
* @returns AWS API Signature to sign a request or url with.
*/
-export const getSignature = async (
+export const getSignature = (
request: HttpRequest,
{
credentialScope,
@@ -26,28 +26,23 @@ export const getSignature = async (
signingRegion,
signingService,
}: SigningValues
-): Promise => {
+): string => {
// step 1: create a canonical request
- const canonicalRequest = await getCanonicalRequest(request);
+ const canonicalRequest = getCanonicalRequest(request);
// step 2: create a hash of the canonical request
- const hashedRequest = await getHashedDataAsHex(null, canonicalRequest);
+ const hashedRequest = getHashedDataAsHex(null, canonicalRequest);
// step 3: create a string to sign
- const stringToSign = await getStringToSign(
+ const stringToSign = getStringToSign(
longDate,
credentialScope,
hashedRequest
);
// step 4: calculate the signature
- const signature = await getHashedDataAsHex(
- await getSigningKey(
- secretAccessKey,
- shortDate,
- signingRegion,
- signingService
- ),
+ const signature = getHashedDataAsHex(
+ getSigningKey(secretAccessKey, shortDate, signingRegion, signingService),
stringToSign
);
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningKey.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningKey.ts
index f061a72fc59..9a096eb4cb2 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningKey.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningKey.ts
@@ -14,16 +14,16 @@ import { getHashedData } from './dataHashHelpers';
*
* @returns `Uint8Array` calculated from its composite parts.
*/
-export const getSigningKey = async (
+export const getSigningKey = (
secretAccessKey: string,
date: string,
region: string,
service: string
-): Promise => {
+): Uint8Array => {
const key = `${SIGNATURE_IDENTIFIER}${secretAccessKey}`;
- const dateKey = await getHashedData(key, date);
- const regionKey = await getHashedData(dateKey, region);
- const serviceKey = await getHashedData(regionKey, service);
- const signingKey = await getHashedData(serviceKey, KEY_TYPE_IDENTIFIER);
+ const dateKey = getHashedData(key, date);
+ const regionKey = getHashedData(dateKey, region);
+ const serviceKey = getHashedData(regionKey, service);
+ const signingKey = getHashedData(serviceKey, KEY_TYPE_IDENTIFIER);
return signingKey;
};
From 1755800fb6cd593fce33101173216f13eeac7f77 Mon Sep 17 00:00:00 2001
From: Chris F <5827964+cshfang@users.noreply.github.com>
Date: Fri, 28 Apr 2023 13:50:07 -0700
Subject: [PATCH 13/41] chore(clients): Replace existing Signer implementation
(#11310)
---
.../Providers/AWSPinpointProvider.test.ts | 2 +-
packages/api-graphql/package.json | 2 +-
packages/api-rest/__tests__/RestAPI-test.ts | 2 +-
packages/api-rest/package.json | 2 +-
packages/api/package.json | 2 +-
packages/core/__tests__/Signer-test.ts | 251 ++++++-----
.../signer/signatureV4/presignUrl-test.ts | 3 +-
.../signer/signatureV4/signRequest-test.ts | 6 +-
.../signatureV4/testUtils/signingTestTable.ts | 8 -
packages/core/package.json | 18 +
packages/core/src/Signer.ts | 394 ++----------------
packages/datastore/package.json | 2 +-
packages/pubsub/package.json | 4 +-
13 files changed, 215 insertions(+), 481 deletions(-)
diff --git a/packages/analytics/__tests__/Providers/AWSPinpointProvider.test.ts b/packages/analytics/__tests__/Providers/AWSPinpointProvider.test.ts
index 260b8211bb3..082feb9f699 100644
--- a/packages/analytics/__tests__/Providers/AWSPinpointProvider.test.ts
+++ b/packages/analytics/__tests__/Providers/AWSPinpointProvider.test.ts
@@ -430,7 +430,7 @@ describe('AnalyticsProvider test', () => {
await analytics.record(params, { resolve, reject });
const expectedUrl =
- 'https://pinpoint.region.amazonaws.com/v1/apps/appId/events/legacy?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=accessKeyId%2FisoStrin%2Fregion%2Fmobiletargeting%2Faws4_request&X-Amz-Date=isoString&X-Amz-Security-Token=sessionToken&X-Amz-SignedHeaders=host&X-Amz-Signature=9dfa2a29782d344c56a9ab99fe58db6d1748e097ae418c398b26ab372a23f22f';
+ 'https://pinpoint.region.amazonaws.com/v1/apps/appId/events/legacy?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=accessKeyId%2FisoStrin%2Fregion%2Fmobiletargeting%2Faws4_request&X-Amz-Date=isoString&X-Amz-SignedHeaders=host&X-Amz-Security-Token=sessionToken&X-Amz-Signature=9dfa2a29782d344c56a9ab99fe58db6d1748e097ae418c398b26ab372a23f22f';
const expectedData = JSON.stringify({
BatchItem: {
diff --git a/packages/api-graphql/package.json b/packages/api-graphql/package.json
index ab9094e0a29..6a2845b38bd 100644
--- a/packages/api-graphql/package.json
+++ b/packages/api-graphql/package.json
@@ -64,7 +64,7 @@
"name": "API (GraphQL client)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, GraphQLAPI }",
- "limit": "86.2 kB"
+ "limit": "86.49 kB"
}
],
"jest": {
diff --git a/packages/api-rest/__tests__/RestAPI-test.ts b/packages/api-rest/__tests__/RestAPI-test.ts
index 36659bd8545..adaaa041296 100644
--- a/packages/api-rest/__tests__/RestAPI-test.ts
+++ b/packages/api-rest/__tests__/RestAPI-test.ts
@@ -514,7 +514,7 @@ describe('Rest API test', () => {
endpoints: [
{
name: 'url',
- endpoint: 'endpoint',
+ endpoint: 'https://domain.fakeurl/',
},
],
});
diff --git a/packages/api-rest/package.json b/packages/api-rest/package.json
index 4df69cf3de8..30009f86999 100644
--- a/packages/api-rest/package.json
+++ b/packages/api-rest/package.json
@@ -55,7 +55,7 @@
"name": "API (rest client)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, RestAPI }",
- "limit": "28.8 kB"
+ "limit": "29.22 kB"
}
],
"jest": {
diff --git a/packages/api/package.json b/packages/api/package.json
index 46acc34ab3d..6d8d3c0c2d0 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -66,7 +66,7 @@
"name": "API (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, API }",
- "limit": "87.5 kB"
+ "limit": "87.85 kB"
}
],
"jest": {
diff --git a/packages/core/__tests__/Signer-test.ts b/packages/core/__tests__/Signer-test.ts
index c862ded4139..6428f8ad3d2 100644
--- a/packages/core/__tests__/Signer-test.ts
+++ b/packages/core/__tests__/Signer-test.ts
@@ -1,132 +1,155 @@
-import { Signer } from '../src/Signer';
-import { DateUtils } from '../src';
-
-jest.mock('@aws-sdk/util-hex-encoding', () => ({
- ...jest.requireActual('@aws-sdk/util-hex-encoding'),
- toHex: () => {
- return 'encrypt';
- },
-}));
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
-describe('Signer test', () => {
- describe('sign test', () => {
- test('happy case', () => {
- const url = 'https://host/some/path';
+import { SignRequestOptions } from '../src/clients/middleware/signing/signer/signatureV4/types';
+import { Signer } from '../src/Signer';
+import { DateUtils } from '../src/Util/DateUtils';
+import {
+ credentials,
+ getDefaultRequest,
+ signingDate,
+ signingOptions,
+ signingRegion,
+ signingService,
+ url,
+} from './clients/middleware/signing/signer/signatureV4/testUtils/data';
+import { signingTestTable } from './clients/middleware/signing/signer/signatureV4/testUtils/signingTestTable';
+
+const getDateSpy = jest.spyOn(DateUtils, 'getDateWithClockOffset');
+
+describe('Signer.sign', () => {
+ beforeAll(() => {
+ getDateSpy.mockReturnValue(signingDate);
+ });
- const request = {
- url,
- headers: {},
+ test.each(
+ signingTestTable.map(
+ ({ name, request, queryParams, options, expectedAuthorization }) => {
+ const updatedRequest = {
+ ...getDefaultRequest(),
+ ...request,
+ };
+ queryParams?.forEach(([key, value]) => {
+ updatedRequest.url?.searchParams.append(key, value);
+ });
+ const updatedOptions: SignRequestOptions = {
+ ...signingOptions,
+ ...options,
+ };
+ return [name, updatedRequest, updatedOptions, expectedAuthorization];
+ }
+ )
+ )(
+ 'signs request with %s',
+ (
+ _,
+ { url, ...request },
+ { credentials, signingRegion, signingService },
+ expected
+ ) => {
+ const { accessKeyId, secretAccessKey, sessionToken } = credentials;
+ const accessInfo = {
+ access_key: accessKeyId,
+ secret_key: secretAccessKey,
+ session_token: sessionToken,
};
- const access_info = {
- session_token: 'session_token',
+ const serviceInfo = {
+ region: signingRegion,
+ service: signingService,
};
+ const signedRequest = Signer.sign(
+ { ...request, url: url.toString() },
+ accessInfo,
+ serviceInfo as any
+ );
+ expect(signedRequest.headers?.Authorization).toBe(expected);
+ }
+ );
+
+ describe('Error handling', () => {
+ const { accessKeyId, secretAccessKey, sessionToken } = credentials;
+ const accessInfo = {
+ access_key: accessKeyId,
+ secret_key: secretAccessKey,
+ session_token: sessionToken,
+ };
+ const serviceInfo = {
+ region: signingRegion,
+ service: signingService,
+ };
- const spyon = jest
- .spyOn(Date.prototype, 'toISOString')
- .mockReturnValueOnce('0');
-
- const getDateSpy = jest.spyOn(DateUtils, 'getDateWithClockOffset');
-
- const res = {
- headers: {
- Authorization:
- 'AWS4-HMAC-SHA256 Credential=undefined/0/aregion/aservice/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=encrypt',
- 'X-Amz-Security-Token': 'session_token',
- host: 'host',
- 'x-amz-date': '0',
- },
- url: url,
+ test('should throw an Error if body attribute is passed to sign method', () => {
+ const request = {
+ ...getDefaultRequest(),
+ body: 'foo',
+ url,
};
- expect(
- Signer.sign(request, access_info, {
- service: 'aservice',
- region: 'aregion',
- })
- ).toEqual(res);
- expect(getDateSpy).toHaveBeenCalledTimes(1);
- spyon.mockClear();
+ expect(() => {
+ Signer.sign(request, accessInfo, serviceInfo as any);
+ }).toThrow();
});
- test('happy case signUrl', () => {
- const url = 'https://example.com:1234/some/path';
-
- const access_info = {
- session_token: 'session_token',
+ test('should not throw an Error if data attribute is passed to sign method', () => {
+ const request = {
+ ...getDefaultRequest(),
+ data: 'foo',
+ url,
};
- const spyon = jest
- .spyOn(Date.prototype, 'toISOString')
- .mockReturnValueOnce('0');
-
- const expectedUrl =
- 'https://example.com:1234/some/path?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=%2F0%2Faregion%2Faservice%2Faws4_request&X-Amz-Date=0&X-Amz-Security-Token=session_token&X-Amz-SignedHeaders=host&X-Amz-Signature=encrypt';
-
- const signedUrl = Signer.signUrl(url, access_info, {
- service: 'aservice',
- region: 'aregion',
- });
-
- expect(signedUrl).toEqual(expectedUrl);
-
- spyon.mockClear();
+ expect(() => {
+ Signer.sign(request, accessInfo, serviceInfo as any);
+ }).not.toThrow();
});
});
});
-describe('Sign method error', () => {
- test('Should throw an Error if body attribute is passed to sign method', () => {
- const url = 'https://host/some/path';
-
- const request_body = {
- url,
- headers: {},
- body: {},
- };
-
- const access_info = {
- session_token: 'session_token',
- };
- const spyon = jest
- .spyOn(Date.prototype, 'toISOString')
- .mockReturnValueOnce('0');
-
- expect(() => {
- Signer.sign(request_body, access_info, {
- service: 'aservice',
- region: 'aregion',
- });
- }).toThrowError(
- 'The attribute "body" was found on the request object. Please use the attribute "data" instead.'
- );
-
- spyon.mockClear();
+describe('Signer.signUrl', () => {
+ beforeAll(() => {
+ getDateSpy.mockReturnValue(signingDate);
});
- test('Should NOT throw an Error if data attribute is passed to sign method', () => {
- const url = 'https://host/some/path';
-
- const request_data = {
- url,
- headers: {},
- data: {},
- };
-
- const access_info = {
- session_token: 'session_token',
- };
-
- const spyon = jest
- .spyOn(Date.prototype, 'toISOString')
- .mockReturnValueOnce('0');
-
- expect(() => {
- Signer.sign(request_data, access_info, {
- service: 'aservice',
- region: 'aregion',
- });
- }).not.toThrowError();
-
- spyon.mockClear();
- });
+ test.each(
+ signingTestTable.map(
+ ({ name, request, queryParams, options, expectedUrl }) => {
+ const updatedRequest = {
+ ...getDefaultRequest(),
+ ...request,
+ };
+ queryParams?.forEach(([key, value]) => {
+ updatedRequest.url?.searchParams.append(key, value);
+ });
+ const updatedOptions: SignRequestOptions = {
+ ...signingOptions,
+ ...options,
+ };
+ return [name, updatedRequest, updatedOptions, expectedUrl];
+ }
+ )
+ )(
+ 'signs url with %s',
+ (
+ _,
+ { url, ...request },
+ { credentials, signingRegion, signingService },
+ expected
+ ) => {
+ const { accessKeyId, secretAccessKey, sessionToken } = credentials;
+ const accessInfo = {
+ access_key: accessKeyId,
+ secret_key: secretAccessKey,
+ session_token: sessionToken,
+ };
+ const serviceInfo = {
+ region: signingRegion,
+ service: signingService,
+ };
+ const signedUrl = Signer.signUrl(
+ { ...request, url: url.toString() },
+ accessInfo,
+ serviceInfo as any
+ );
+ expect(signedUrl).toBe(expected);
+ }
+ );
});
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/presignUrl-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/presignUrl-test.ts
index f5ca9b11795..659934c1229 100644
--- a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/presignUrl-test.ts
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/presignUrl-test.ts
@@ -5,6 +5,7 @@ import { presignUrl } from '../../../../../../src/clients/middleware/signing/sig
import { HttpRequest } from '../../../../../../src/clients/types';
import { signingTestTable } from './testUtils/signingTestTable';
import { getDefaultRequest, signingOptions } from './testUtils/data';
+import { PresignUrlOptions } from '../../../../../../src/clients/middleware/signing/signer/signatureV4/types';
describe('presignUrl', () => {
test.each(
@@ -17,7 +18,7 @@ describe('presignUrl', () => {
queryParams?.forEach(([key, value]) => {
updatedRequest.url?.searchParams.append(key, value);
});
- const updatedOptions = {
+ const updatedOptions: PresignUrlOptions = {
...signingOptions,
...options,
};
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/signRequest-test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/signRequest-test.ts
index 5cd4c2a4ed8..e1c3c8ad131 100644
--- a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/signRequest-test.ts
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/signRequest-test.ts
@@ -2,21 +2,23 @@
// SPDX-License-Identifier: Apache-2.0
import { signRequest } from '../../../../../../src/clients/middleware/signing/signer/signatureV4/signRequest';
+import { HttpRequest } from '../../../../../../src/clients/types';
import { signingTestTable } from './testUtils/signingTestTable';
import { getDefaultRequest, signingOptions } from './testUtils/data';
+import { SignRequestOptions } from '../../../../../../src/clients/middleware/signing/signer/signatureV4/types';
describe('signRequest', () => {
test.each(
signingTestTable.map(
({ name, request, queryParams, options, expectedAuthorization }) => {
- const updatedRequest = {
+ const updatedRequest: HttpRequest = {
...getDefaultRequest(),
...request,
};
queryParams?.forEach(([key, value]) => {
updatedRequest.url?.searchParams.append(key, value);
});
- const updatedOptions = {
+ const updatedOptions: SignRequestOptions = {
...signingOptions,
...options,
};
diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/testUtils/signingTestTable.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/testUtils/signingTestTable.ts
index 5a65d7c9717..2a219223add 100644
--- a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/testUtils/signingTestTable.ts
+++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/testUtils/signingTestTable.ts
@@ -108,14 +108,6 @@ export const signingTestTable: TestCase[] = [
expectedUrl:
'https://domain.fakeurl/?%40%23%24%25%5E%26%2B%3D%2F%2C%3F%3E%3C%60%22%3B%3A%5C%7C%5D%5B%7B%7D+=%40%23%24%25%5E%26%2B%3D%2F%2C%3F%3E%3C%60%22%3B%3A%5C%7C%5D%5B%7B%7D+&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access-key-id%2F20200918%2Fsigning-region%2Fsigning-service%2Faws4_request&X-Amz-Date=20200918T181818Z&X-Amz-SignedHeaders=host&X-Amz-Signature=deddbc9d8440a57bdc87d0b4e06b2859304e263af0896b7a4b0126b9d629a81a',
},
- {
- name: 'spaces in query params',
- queryParams: [['f oo', 'b ar']],
- expectedAuthorization:
- 'AWS4-HMAC-SHA256 Credential=access-key-id/20200918/signing-region/signing-service/aws4_request, SignedHeaders=host;x-amz-date, Signature=37ce5dcb43c12849d256de4cc73dff4c89e333ba2604d8a8de24876f6fbc57d0',
- expectedUrl:
- 'https://domain.fakeurl/?f+oo=b+ar&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access-key-id%2F20200918%2Fsigning-region%2Fsigning-service%2Faws4_request&X-Amz-Date=20200918T181818Z&X-Amz-SignedHeaders=host&X-Amz-Signature=e7f19479e5275c7f5ba98b07cfb40ea02c6959f652e9aac3ff855b1c38426790',
- },
{
name: 'mixed cased header keys',
request: {
diff --git a/packages/core/package.json b/packages/core/package.json
index 9542ff8a3e1..797b5fcf8c0 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -102,6 +102,12 @@
"import": "{ Credentials }",
"limit": "11.7 kB"
},
+ {
+ "name": "Core (Signer)",
+ "path": "./lib-esm/index.js",
+ "import": "{ Signer }",
+ "limit": "7.08 kB"
+ },
{
"name": "Custom clients (retry middleware)",
"path": "./lib-esm/clients/middleware/retry/middleware.js",
@@ -119,6 +125,18 @@
"path": "./lib-esm/clients/handlers/unauthenticated.js",
"import": "{ unauthenticatedHandler }",
"limit": "2.75 kB"
+ },
+ {
+ "name": "Custom clients (request signer)",
+ "path": "./lib-esm/clients/middleware/signing/signer/signatureV4/index.js",
+ "import": "{ signRequest }",
+ "limit": "6.13 kB"
+ },
+ {
+ "name": "Custom clients (url presigner)",
+ "path": "./lib-esm/clients/middleware/signing/signer/signatureV4/index.js",
+ "import": "{ presignUrl }",
+ "limit": "6.25 kB"
}
],
"jest": {
diff --git a/packages/core/src/Signer.ts b/packages/core/src/Signer.ts
index 8bf8be9d781..fc62bdeeabd 100644
--- a/packages/core/src/Signer.ts
+++ b/packages/core/src/Signer.ts
@@ -1,238 +1,11 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ConsoleLogger as Logger } from './Logger';
-import { Sha256 as jsSha256 } from '@aws-crypto/sha256-js';
-import { toHex } from '@aws-sdk/util-hex-encoding';
-import { parse, format } from 'url';
import { DateUtils } from './Util';
-
-const logger = new Logger('Signer');
-
-const DEFAULT_ALGORITHM = 'AWS4-HMAC-SHA256';
-const IOT_SERVICE_NAME = 'iotdevicegateway';
-
-const encrypt = function(key, src) {
- const hash = new jsSha256(key);
- hash.update(src);
- return hash.digestSync();
-};
-
-const hash = function(src) {
- const arg = src || '';
- const hash = new jsSha256();
- hash.update(arg);
- return toHex(hash.digestSync());
-};
-
-/**
- * @private
- * RFC 3986 compliant version of encodeURIComponent
- */
-const escape_RFC3986 = function(component) {
- return component.replace(/[!'()*]/g, function(c) {
- return '%' + c.charCodeAt(0).toString(16).toUpperCase();
- });
-};
-
-/**
- * @private
- * Create canonical query string
- *
- */
-const canonical_query = function(query) {
- if (!query || query.length === 0) {
- return '';
- }
-
- return query
- .split('&')
- .map(e => {
- const key_val = e.split('=');
-
- if (key_val.length === 1) {
- return e;
- } else {
- const reencoded_val = escape_RFC3986(key_val[1]);
- return key_val[0] + '=' + reencoded_val;
- }
- })
- .sort((a, b) => {
- const key_a = a.split('=')[0];
- const key_b = b.split('=')[0];
- if (key_a === key_b) {
- return a < b ? -1 : 1;
- } else {
- return key_a < key_b ? -1 : 1;
- }
- })
- .join('&');
-};
-
-/**
-* @private
-* Create canonical headers
-*
-
-CanonicalHeaders =
- CanonicalHeadersEntry0 + CanonicalHeadersEntry1 + ... + CanonicalHeadersEntryN
-CanonicalHeadersEntry =
- Lowercase(HeaderName) + ':' + Trimall(HeaderValue) + '\n'
-
-*/
-const canonical_headers = function(headers) {
- if (!headers || Object.keys(headers).length === 0) {
- return '';
- }
-
- return (
- Object.keys(headers)
- .map(function(key) {
- return {
- key: key.toLowerCase(),
- value: headers[key] ? headers[key].trim().replace(/\s+/g, ' ') : '',
- };
- })
- .sort(function(a, b) {
- return a.key < b.key ? -1 : 1;
- })
- .map(function(item) {
- return item.key + ':' + item.value;
- })
- .join('\n') + '\n'
- );
-};
-
-/**
- * List of header keys included in the canonical headers.
- * @access private
- */
-const signed_headers = function(headers) {
- return Object.keys(headers)
- .map(function(key) {
- return key.toLowerCase();
- })
- .sort()
- .join(';');
-};
-
-/**
-* @private
-* Create canonical request
-* Refer to
-* {@link http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html|Create a Canonical Request}
-*
-
-CanonicalRequest =
- HTTPRequestMethod + '\n' +
- CanonicalURI + '\n' +
- CanonicalQueryString + '\n' +
- CanonicalHeaders + '\n' +
- SignedHeaders + '\n' +
- HexEncode(Hash(RequestPayload))
-
-*/
-const canonical_request = function(request) {
- const url_info = parse(request.url);
-
- return [
- request.method || '/',
- encodeURIComponent(url_info.pathname).replace(/%2F/gi, '/'),
- canonical_query(url_info.query),
- canonical_headers(request.headers),
- signed_headers(request.headers),
- hash(request.data),
- ].join('\n');
-};
-
-const parse_service_info = function(request) {
- const url_info = parse(request.url),
- host = url_info.host;
-
- const matched = host.match(/([^\.]+)\.(?:([^\.]*)\.)?amazonaws\.com$/);
- let parsed = (matched || []).slice(1, 3);
-
- if (parsed[1] === 'es') {
- // Elastic Search
- parsed = parsed.reverse();
- }
-
- return {
- service: request.service || parsed[0],
- region: request.region || parsed[1],
- };
-};
-
-const credential_scope = function(d_str, region, service) {
- return [d_str, region, service, 'aws4_request'].join('/');
-};
-
-/**
-* @private
-* Create a string to sign
-* Refer to
-* {@link http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html|Create String to Sign}
-*
-
-StringToSign =
- Algorithm + \n +
- RequestDateTime + \n +
- CredentialScope + \n +
- HashedCanonicalRequest
-
-*/
-const string_to_sign = function(algorithm, canonical_request, dt_str, scope) {
- return [algorithm, dt_str, scope, hash(canonical_request)].join('\n');
-};
-
-/**
-* @private
-* Create signing key
-* Refer to
-* {@link http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html|Calculate Signature}
-*
-
-kSecret = your secret access key
-kDate = HMAC("AWS4" + kSecret, Date)
-kRegion = HMAC(kDate, Region)
-kService = HMAC(kRegion, Service)
-kSigning = HMAC(kService, "aws4_request")
-
-*/
-const get_signing_key = function(secret_key, d_str, service_info) {
- logger.debug(service_info);
- const k = 'AWS4' + secret_key,
- k_date = encrypt(k, d_str),
- k_region = encrypt(k_date, service_info.region),
- k_service = encrypt(k_region, service_info.service),
- k_signing = encrypt(k_service, 'aws4_request');
-
- return k_signing;
-};
-
-const get_signature = function(signing_key, str_to_sign) {
- return toHex(encrypt(signing_key, str_to_sign));
-};
-
-/**
- * @private
- * Create authorization header
- * Refer to
- * {@link http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html|Add the Signing Information}
- */
-const get_authorization_header = function(
- algorithm,
- access_key,
- scope,
- signed_headers,
- signature
-) {
- return [
- algorithm + ' ' + 'Credential=' + access_key + '/' + scope,
- 'SignedHeaders=' + signed_headers,
- 'Signature=' + signature,
- ].join(', ');
-};
+import {
+ presignUrl,
+ signRequest,
+} from './clients/middleware/signing/signer/signatureV4';
export class Signer {
/**
@@ -271,7 +44,7 @@ export class Signer {
*
* @returns {object} Signed HTTP request
*/
- static sign(request, access_info, service_info = null) {
+ static sign(request, accessInfo, serviceInfo) {
request.headers = request.headers || {};
if (request.body && !request.data) {
@@ -280,51 +53,25 @@ export class Signer {
);
}
- // datetime string and date string
- const dt = DateUtils.getDateWithClockOffset(),
- dt_str = dt.toISOString().replace(/[:\-]|\.\d{3}/g, ''),
- d_str = dt_str.substr(0, 8);
-
- const url_info = parse(request.url);
- request.headers['host'] = url_info.host;
- request.headers['x-amz-date'] = dt_str;
- if (access_info.session_token) {
- request.headers['X-Amz-Security-Token'] = access_info.session_token;
- }
-
- // Task 1: Create a Canonical Request
- const request_str = canonical_request(request);
- logger.debug(request_str);
-
- // Task 2: Create a String to Sign
- const serviceInfo = service_info || parse_service_info(request),
- scope = credential_scope(d_str, serviceInfo.region, serviceInfo.service),
- str_to_sign = string_to_sign(
- DEFAULT_ALGORITHM,
- request_str,
- dt_str,
- scope
- );
-
- // Task 3: Calculate the Signature
- const signing_key = get_signing_key(
- access_info.secret_key,
- d_str,
- serviceInfo
- ),
- signature = get_signature(signing_key, str_to_sign);
-
- // Task 4: Adding the Signing information to the Request
- const authorization_header = get_authorization_header(
- DEFAULT_ALGORITHM,
- access_info.access_key,
- scope,
- signed_headers(request.headers),
- signature
- );
- request.headers['Authorization'] = authorization_header;
+ const requestToSign = {
+ ...request,
+ body: request.data,
+ url: new URL(request.url as string),
+ };
- return request;
+ const options = getOptions(accessInfo, serviceInfo);
+ const signedRequest: any = signRequest(requestToSign, options);
+ // Prior to using `signRequest`, Signer accepted urls as strings and outputted urls as string. Coerce the property
+ // back to a string so as not to disrupt consumers of Signer.
+ signedRequest.url = signedRequest.url.toString();
+ // HTTP headers should be case insensitive but, to maintain parity with the previous Signer implementation and
+ // limit the impact of this implementation swap, replace lowercased headers with title cased ones.
+ signedRequest.headers.Authorization = signedRequest.headers.authorization;
+ signedRequest.headers['X-Amz-Security-Token'] =
+ signedRequest.headers['x-amz-security-token'];
+ delete signedRequest.headers.authorization;
+ delete signedRequest.headers['x-amz-security-token'];
+ return signedRequest;
}
static signUrl(
@@ -352,80 +99,31 @@ export class Signer {
const body: any =
typeof urlOrRequest === 'object' ? urlOrRequest.body : undefined;
- const now = DateUtils.getDateWithClockOffset()
- .toISOString()
- .replace(/[:\-]|\.\d{3}/g, '');
- const today = now.substr(0, 8);
- // Intentionally discarding search
- const { search, ...parsedUrl } = parse(urlToSign, true, true);
- const { host } = parsedUrl;
- const signedHeaders = { host };
-
- const { region, service } =
- serviceInfo || parse_service_info({ url: format(parsedUrl) });
- const credentialScope = credential_scope(today, region, service);
-
- // IoT service does not allow the session token in the canonical request
- // https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
- const sessionTokenRequired =
- accessInfo.session_token && service !== IOT_SERVICE_NAME;
- const queryParams = {
- 'X-Amz-Algorithm': DEFAULT_ALGORITHM,
- 'X-Amz-Credential': [accessInfo.access_key, credentialScope].join('/'),
- 'X-Amz-Date': now.substr(0, 16),
- ...(sessionTokenRequired
- ? { 'X-Amz-Security-Token': `${accessInfo.session_token}` }
- : {}),
- ...(expiration ? { 'X-Amz-Expires': `${expiration}` } : {}),
- 'X-Amz-SignedHeaders': Object.keys(signedHeaders).join(','),
- };
-
- const canonicalRequest = canonical_request({
+ const presignable = {
+ body,
method,
- url: format({
- ...parsedUrl,
- query: {
- ...parsedUrl.query,
- ...queryParams,
- },
- }),
- headers: signedHeaders,
- data: body,
- });
-
- const stringToSign = string_to_sign(
- DEFAULT_ALGORITHM,
- canonicalRequest,
- now,
- credentialScope
- );
-
- const signing_key = get_signing_key(accessInfo.secret_key, today, {
- region,
- service,
- });
- const signature = get_signature(signing_key, stringToSign);
-
- const additionalQueryParams = {
- 'X-Amz-Signature': signature,
- ...(accessInfo.session_token && {
- 'X-Amz-Security-Token': accessInfo.session_token,
- }),
+ url: new URL(urlToSign),
};
- const result = format({
- protocol: parsedUrl.protocol,
- slashes: true,
- hostname: parsedUrl.hostname,
- port: parsedUrl.port,
- pathname: parsedUrl.pathname,
- query: {
- ...parsedUrl.query,
- ...queryParams,
- ...additionalQueryParams,
- },
- });
-
- return result;
+ const options = getOptions(accessInfo, serviceInfo, expiration);
+ const signedUrl = presignUrl(presignable, options);
+ return signedUrl.toString();
}
}
+
+const getOptions = (accessInfo, serviceInfo, expiration?) => {
+ const { access_key, secret_key, session_token } = accessInfo ?? {};
+ const { region, service } = serviceInfo ?? {};
+ const credentials = {
+ accessKeyId: access_key,
+ secretAccessKey: secret_key,
+ sessionToken: session_token,
+ };
+ return {
+ credentials,
+ signingDate: DateUtils.getDateWithClockOffset(),
+ signingRegion: region,
+ signingService: service,
+ ...(expiration && { expiration }),
+ };
+};
diff --git a/packages/datastore/package.json b/packages/datastore/package.json
index 16978cff0c3..89449c56e91 100644
--- a/packages/datastore/package.json
+++ b/packages/datastore/package.json
@@ -72,7 +72,7 @@
"name": "DataStore (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, DataStore }",
- "limit": "135.5 kB"
+ "limit": "135.85 kB"
}
],
"jest": {
diff --git a/packages/pubsub/package.json b/packages/pubsub/package.json
index 4b2528959a7..2f1635224c7 100644
--- a/packages/pubsub/package.json
+++ b/packages/pubsub/package.json
@@ -63,13 +63,13 @@
"name": "PubSub (IoT provider)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, PubSub, AWSIoTProvider }",
- "limit": "78.5 kB"
+ "limit": "78.76 kB"
},
{
"name": "PubSub (Mqtt provider)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, PubSub, MqttOverWSProvider }",
- "limit": "78.4 kB"
+ "limit": "78.61 kB"
}
],
"jest": {
From 3d607ffa26cbaddd4d659ddd3e68704e0b3d280b Mon Sep 17 00:00:00 2001
From: Chris F <5827964+cshfang@users.noreply.github.com>
Date: Fri, 28 Apr 2023 16:28:24 -0700
Subject: [PATCH 14/41] chore(clients): Add @internal annotation (#11320)
---
.../signing/signer/signatureV4/utils/dataHashHelpers.ts | 2 ++
.../signing/signer/signatureV4/utils/getCanonicalHeaders.ts | 2 ++
.../signing/signer/signatureV4/utils/getCanonicalQueryString.ts | 2 ++
.../signing/signer/signatureV4/utils/getCanonicalRequest.ts | 2 ++
.../signing/signer/signatureV4/utils/getCanonicalUri.ts | 2 ++
.../signing/signer/signatureV4/utils/getCredentialScope.ts | 2 ++
.../signing/signer/signatureV4/utils/getFormattedDates.ts | 2 ++
.../signing/signer/signatureV4/utils/getHashedPayload.ts | 2 ++
.../middleware/signing/signer/signatureV4/utils/getSignature.ts | 2 ++
.../signing/signer/signatureV4/utils/getSignedHeaders.ts | 2 ++
.../signing/signer/signatureV4/utils/getSigningKey.ts | 2 ++
.../signing/signer/signatureV4/utils/getSigningValues.ts | 2 ++
.../signing/signer/signatureV4/utils/getStringToSign.ts | 2 ++
13 files changed, 26 insertions(+)
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers.ts
index 3742d135842..adf37473ccc 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/dataHashHelpers.ts
@@ -30,6 +30,8 @@ export const getHashedData = (
* @param key `SourceData` to be used as hashing key.
* @param data Hashable `SourceData`.
* @returns String using lowercase hexadecimal characters created from the data as input to a hash function.
+ *
+ * @internal
*/
export const getHashedDataAsHex = (
key: SourceData | null,
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalHeaders.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalHeaders.ts
index 85cb3ba1148..1f9980208cf 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalHeaders.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalHeaders.ts
@@ -11,6 +11,8 @@ import { HttpRequest } from '../../../../../types';
* use lowercase characters, must appear in alphabetical order, and must be followed by a colon (:). For the values,
* trim any leading or trailing spaces, convert sequential spaces to a single space, and separate the values
* for a multi-value header using commas.
+ *
+ * @internal
*/
export const getCanonicalHeaders = (headers: HttpRequest['headers']): string =>
Object.entries(headers)
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalQueryString.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalQueryString.ts
index 09fdec6cc6a..1686eaf537c 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalQueryString.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalQueryString.ts
@@ -9,6 +9,8 @@
* including the space character. Encode names and values separately. If there are empty parameters, append the equals
* sign to the parameter name before encoding. After encoding, sort the parameters alphabetically by key name. If there
* is no query string, use an empty string ("").
+ *
+ * @internal
*/
export const getCanonicalQueryString = (
searchParams: URLSearchParams
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest.ts
index 42618bb8f7b..1f4f5c7c3d3 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalRequest.ts
@@ -19,6 +19,8 @@ import { getSignedHeaders } from './getSignedHeaders';
* - CanonicalHeaders
* - SignedHeaders
* - HashedPayload
+ *
+ * @internal
*/
export const getCanonicalRequest = ({
body,
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalUri.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalUri.ts
index b16975077a0..f082b0dc75e 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalUri.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalUri.ts
@@ -7,6 +7,8 @@
* @param pathname `pathname` from request url.
* @returns URI-encoded version of the absolute path component URL (everything between the host and the question mark
* character (?) that starts the query string parameters). If the absolute path is empty, a forward slash character (/).
+ *
+ * @internal
*/
export const getCanonicalUri = (pathname: string): string =>
pathname ? encodeURIComponent(pathname).replace(/%2F/g, '/') : '/';
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCredentialScope.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCredentialScope.ts
index 5a9ddf43079..7e7f94f2b45 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCredentialScope.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCredentialScope.ts
@@ -11,6 +11,8 @@ import { KEY_TYPE_IDENTIFIER } from '../constants';
* @param service Service to which the signed request is being sent.
*
* @returns A string representing the credential scope with format 'YYYYMMDD/region/service/aws4_request'.
+ *
+ * @internal
*/
export const getCredentialScope = (
date: string,
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getFormattedDates.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getFormattedDates.ts
index 72387fa8948..2b742910c83 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getFormattedDates.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getFormattedDates.ts
@@ -10,6 +10,8 @@ import { FormattedDates } from '../types/signer';
* @returns `FormattedDates` object containing the following:
* - longDate: A date string in 'YYYYMMDDThhmmssZ' format
* - shortDate: A date string in 'YYYYMMDD' format
+ *
+ * @internal
*/
export const getFormattedDates = (date: Date): FormattedDates => {
const longDate = date.toISOString().replace(/[:\-]|\.\d{3}/g, '');
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.ts
index 2445d5ac32f..f2543e78272 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.ts
@@ -12,6 +12,8 @@ import { getHashedDataAsHex } from './dataHashHelpers';
* @param body `body` (payload) from the request.
* @returns String created using the payload in the body of the HTTP request as input to a hash function. This string
* uses lowercase hexadecimal characters. If the payload is empty, return precalculated result of an empty hash.
+ *
+ * @internal
*/
export const getHashedPayload = (body: HttpRequest['body']): string => {
// return precalculated empty hash if body is undefined or null
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignature.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignature.ts
index 6316b664fcd..90231c3f6fd 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignature.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignature.ts
@@ -15,6 +15,8 @@ import { getStringToSign } from './getStringToSign';
* @param request `HttpRequest` to be signed.
* @param signRequestOptions `SignRequestOptions` object containing values used to construct the signature.
* @returns AWS API Signature to sign a request or url with.
+ *
+ * @internal
*/
export const getSignature = (
request: HttpRequest,
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignedHeaders.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignedHeaders.ts
index fbf480d1ffb..a96bbe57868 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignedHeaders.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSignedHeaders.ts
@@ -9,6 +9,8 @@ import { HttpRequest } from '../../../../../types';
* @param headers `headers` from the request.
* @returns List of headers included in canonical headers, separated by semicolons (;). This indicates which headers
* are part of the signing process. Header names must use lowercase characters and must appear in alphabetical order.
+ *
+ * @internal
*/
export const getSignedHeaders = (headers: HttpRequest['headers']): string =>
Object.keys(headers)
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningKey.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningKey.ts
index 9a096eb4cb2..f744db91300 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningKey.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningKey.ts
@@ -13,6 +13,8 @@ import { getHashedData } from './dataHashHelpers';
* @param service Service to which the signed request is being sent.
*
* @returns `Uint8Array` calculated from its composite parts.
+ *
+ * @internal
*/
export const getSigningKey = (
secretAccessKey: string,
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningValues.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningValues.ts
index 594a0fcf3e2..3a9e2709618 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningValues.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getSigningValues.ts
@@ -11,6 +11,8 @@ import { getFormattedDates } from './getFormattedDates';
*
* @param options `SignRequestOptions` object containing values used to construct the signature.
* @returns Common `SigningValues` used for signing.
+ *
+ * @internal
*/
export const getSigningValues = ({
credentials,
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getStringToSign.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getStringToSign.ts
index 8a522b9c94a..095b79f7065 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getStringToSign.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getStringToSign.ts
@@ -15,6 +15,8 @@ import { SHA256_ALGORITHM_IDENTIFIER } from '../constants';
* - RequestDateTime
* - CredentialScope
* - HashedCanonicalRequest
+ *
+ * @internal
*/
export const getStringToSign = (
date: string,
From de1ce7b1d3fcc9b0fe042e08cfa58f705d8751da Mon Sep 17 00:00:00 2001
From: Chris F <5827964+cshfang@users.noreply.github.com>
Date: Wed, 3 May 2023 13:11:24 -0700
Subject: [PATCH 15/41] feat(clients): Add signing middleware (#11323)
* feat(clients): Add signing middleware
* Use closure for clock offset
* Add bundle size test entry
* Address comments
---
.../clients/composeApiHandler-test.ts | 5 +-
.../clients/composeTransferHandler-test.ts | 3 +
.../retry/middleware-test.ts} | 9 +-
.../middleware/signing/middleware-test.ts | 141 ++++++++++++++++++
.../utils/extendedEncodeURIComponent-test.ts | 10 ++
.../utils/getSkewCorrectedDate-test.ts | 16 ++
.../utils/getUpdatedSystemClockOffset-test.ts | 43 ++++++
.../signing/utils/isClockSkewed-test.ts | 45 ++++++
.../clients/utils/isClockSkewedError-test.ts | 15 ++
packages/core/package.json | 18 ++-
.../getCredentialsForIdentity.ts | 2 +-
.../src/AwsClients/CognitoIdentity/getId.ts | 2 +-
...poseApiHandler.ts => composeServiceApi.ts} | 0
.../middleware/retry/defaultRetryDecider.ts | 14 +-
.../src/clients/middleware/signing/index.ts | 4 +
.../clients/middleware/signing/middleware.ts | 65 ++++++++
.../utils/getCanonicalQueryString.ts | 13 +-
.../utils/extendedEncodeURIComponent.ts | 16 ++
.../signing/utils/getSkewCorrectedDate.ts | 14 ++
.../utils/getUpdatedSystemClockOffset.ts | 22 +++
.../middleware/signing/utils/isClockSkewed.ts | 26 ++++
packages/core/src/clients/types/core.ts | 3 +-
.../src/clients/utils/isClockSkewError.ts | 24 +++
23 files changed, 476 insertions(+), 34 deletions(-)
rename packages/core/__tests__/clients/{retry-middleware-test.ts => middleware/retry/middleware-test.ts} (94%)
create mode 100644 packages/core/__tests__/clients/middleware/signing/middleware-test.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/utils/extendedEncodeURIComponent-test.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/utils/getSkewCorrectedDate-test.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/utils/getUpdatedSystemClockOffset-test.ts
create mode 100644 packages/core/__tests__/clients/middleware/signing/utils/isClockSkewed-test.ts
create mode 100644 packages/core/__tests__/clients/utils/isClockSkewedError-test.ts
rename packages/core/src/clients/internal/{composeApiHandler.ts => composeServiceApi.ts} (100%)
create mode 100644 packages/core/src/clients/middleware/signing/index.ts
create mode 100644 packages/core/src/clients/middleware/signing/middleware.ts
create mode 100644 packages/core/src/clients/middleware/signing/utils/extendedEncodeURIComponent.ts
create mode 100644 packages/core/src/clients/middleware/signing/utils/getSkewCorrectedDate.ts
create mode 100644 packages/core/src/clients/middleware/signing/utils/getUpdatedSystemClockOffset.ts
create mode 100644 packages/core/src/clients/middleware/signing/utils/isClockSkewed.ts
create mode 100644 packages/core/src/clients/utils/isClockSkewError.ts
diff --git a/packages/core/__tests__/clients/composeApiHandler-test.ts b/packages/core/__tests__/clients/composeApiHandler-test.ts
index 792d33b47bb..529f6413e9b 100644
--- a/packages/core/__tests__/clients/composeApiHandler-test.ts
+++ b/packages/core/__tests__/clients/composeApiHandler-test.ts
@@ -1,4 +1,7 @@
-import { composeServiceApi } from '../../src/clients/internal/composeApiHandler';
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { composeServiceApi } from '../../src/clients/internal/composeServiceApi';
describe(composeServiceApi.name, () => {
const defaultRequest = {
diff --git a/packages/core/__tests__/clients/composeTransferHandler-test.ts b/packages/core/__tests__/clients/composeTransferHandler-test.ts
index 875a8dcd395..fb0cad674de 100644
--- a/packages/core/__tests__/clients/composeTransferHandler-test.ts
+++ b/packages/core/__tests__/clients/composeTransferHandler-test.ts
@@ -1,3 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
import { composeTransferHandler } from '../../src/clients/internal/composeTransferHandler';
import {
Middleware,
diff --git a/packages/core/__tests__/clients/retry-middleware-test.ts b/packages/core/__tests__/clients/middleware/retry/middleware-test.ts
similarity index 94%
rename from packages/core/__tests__/clients/retry-middleware-test.ts
rename to packages/core/__tests__/clients/middleware/retry/middleware-test.ts
index 3bbfe5f7b8b..d37ec0f4eee 100644
--- a/packages/core/__tests__/clients/retry-middleware-test.ts
+++ b/packages/core/__tests__/clients/middleware/retry/middleware-test.ts
@@ -1,9 +1,12 @@
-import { HttpResponse, MiddlewareHandler } from '../../src/clients/types';
-import { composeTransferHandler } from '../../src/clients/internal/composeTransferHandler';
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { HttpResponse, MiddlewareHandler } from '../../../../src/clients/types';
+import { composeTransferHandler } from '../../../../src/clients/internal/composeTransferHandler';
import {
retryMiddleware,
RetryOptions,
-} from '../../src/clients/middleware/retry';
+} from '../../../../src/clients/middleware/retry';
jest.spyOn(global, 'setTimeout');
jest.spyOn(global, 'clearTimeout');
diff --git a/packages/core/__tests__/clients/middleware/signing/middleware-test.ts b/packages/core/__tests__/clients/middleware/signing/middleware-test.ts
new file mode 100644
index 00000000000..ae5171f89b5
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/middleware-test.ts
@@ -0,0 +1,141 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { composeTransferHandler } from '../../../../src/clients/internal/composeTransferHandler';
+import {
+ signingMiddleware,
+ SigningOptions,
+} from '../../../../src/clients/middleware/signing';
+import { getSkewCorrectedDate } from '../../../../src/clients/middleware/signing/utils/getSkewCorrectedDate';
+import { getUpdatedSystemClockOffset } from '../../../../src/clients/middleware/signing/utils/getUpdatedSystemClockOffset';
+import {
+ HttpRequest,
+ HttpResponse,
+ MiddlewareHandler,
+} from '../../../../src/clients/types';
+import { isClockSkewError } from '../../../../src/clients/utils/isClockSkewError';
+import {
+ credentials,
+ signingDate,
+ signingRegion,
+ signingService,
+ url,
+} from './signer/signatureV4/testUtils/data';
+import { signingTestTable } from './signer/signatureV4/testUtils/signingTestTable';
+
+jest.mock('../../../../src/clients/utils/isClockSkewError');
+jest.mock(
+ '../../../../src/clients/middleware/signing/utils/getSkewCorrectedDate'
+);
+jest.mock(
+ '../../../../src/clients/middleware/signing/utils/getUpdatedSystemClockOffset'
+);
+
+const mockisClockSkewError = isClockSkewError as jest.Mock;
+const mockGetSkewCorrectedDate = getSkewCorrectedDate as jest.Mock;
+const mockGetUpdatedSystemClockOffset =
+ getUpdatedSystemClockOffset as jest.Mock;
+
+describe('Signing middleware', () => {
+ const defaultSigningOptions = {
+ credentials,
+ region: signingRegion,
+ service: signingService,
+ };
+ const defaultRequest = { method: 'GET', headers: {}, url: new URL(url) };
+ const defaultResponse: HttpResponse = {
+ body: 'foo' as any,
+ statusCode: 200,
+ headers: {},
+ };
+ const updatedOffset = 10000;
+ const [basicTestCase] = signingTestTable;
+ const getSignableHandler = (nextHandler: MiddlewareHandler) =>
+ composeTransferHandler<[SigningOptions], HttpRequest, HttpResponse>(
+ nextHandler,
+ [signingMiddleware]
+ );
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockGetSkewCorrectedDate.mockReturnValue(signingDate);
+ mockGetUpdatedSystemClockOffset.mockReturnValue(updatedOffset);
+ });
+
+ test('should sign request', async () => {
+ const nextHandler = jest.fn().mockResolvedValue(defaultResponse);
+ const signableHandler = getSignableHandler(nextHandler);
+ const config = { ...defaultSigningOptions };
+ await signableHandler(defaultRequest, config);
+ expect(nextHandler).toBeCalledWith(
+ expect.objectContaining({
+ headers: expect.objectContaining({
+ authorization: basicTestCase.expectedAuthorization,
+ }),
+ }),
+ expect.objectContaining(defaultSigningOptions)
+ );
+ });
+
+ test('can be configured with clock offset', async () => {
+ const systemClockOffset = 100;
+ const nextHandler = jest.fn().mockResolvedValue(defaultResponse);
+ const signableHandler = getSignableHandler(nextHandler);
+ const config = { ...defaultSigningOptions, systemClockOffset };
+ await signableHandler(defaultRequest, config);
+ expect(nextHandler).toBeCalledWith(
+ expect.objectContaining({
+ headers: expect.objectContaining({
+ authorization: basicTestCase.expectedAuthorization,
+ }),
+ }),
+ expect.objectContaining({
+ ...defaultSigningOptions,
+ systemClockOffset,
+ })
+ );
+ });
+
+ test.each([
+ ['skew error', null],
+ ['error with Date header', 'Date'],
+ ['error with date header', 'date'],
+ ])('should adjust clock offset if server returns %s', async (_, key) => {
+ mockisClockSkewError.mockReturnValue(true);
+ const serverTime = signingDate.toISOString();
+ const parsedServerTime = Date.parse(serverTime);
+ const nextHandler = key
+ ? jest.fn().mockRejectedValue({
+ $response: {
+ headers: {
+ [key]: serverTime,
+ },
+ },
+ })
+ : jest.fn().mockRejectedValue({ ServerTime: serverTime });
+
+ const middlewareFunction = signingMiddleware(defaultSigningOptions)(
+ nextHandler
+ );
+
+ try {
+ await middlewareFunction(defaultRequest);
+ } catch (error) {
+ expect(mockGetSkewCorrectedDate).toBeCalledWith(0);
+ expect(mockGetUpdatedSystemClockOffset).toBeCalledWith(
+ parsedServerTime,
+ 0
+ );
+ jest.clearAllMocks();
+ try {
+ await middlewareFunction(defaultRequest);
+ } catch (error) {
+ expect(mockGetSkewCorrectedDate).toBeCalledWith(updatedOffset);
+ expect(mockGetUpdatedSystemClockOffset).toBeCalledWith(
+ parsedServerTime,
+ updatedOffset
+ );
+ }
+ }
+ expect.assertions(4);
+ });
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/utils/extendedEncodeURIComponent-test.ts b/packages/core/__tests__/clients/middleware/signing/utils/extendedEncodeURIComponent-test.ts
new file mode 100644
index 00000000000..eb90e62658e
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/utils/extendedEncodeURIComponent-test.ts
@@ -0,0 +1,10 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { extendedEncodeURIComponent } from '../../../../../src/clients/middleware/signing/utils/extendedEncodeURIComponent';
+
+describe('extendedEncodeURIComponent', () => {
+ test('encodes additional characters', () => {
+ expect(extendedEncodeURIComponent("!'()*")).toBe('%21%27%28%29%2A');
+ });
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/utils/getSkewCorrectedDate-test.ts b/packages/core/__tests__/clients/middleware/signing/utils/getSkewCorrectedDate-test.ts
new file mode 100644
index 00000000000..fbee25ccc29
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/utils/getSkewCorrectedDate-test.ts
@@ -0,0 +1,16 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { getSkewCorrectedDate } from '../../../../../src/clients/middleware/signing/utils/getSkewCorrectedDate';
+import { signingDate } from '../signer/signatureV4/testUtils/data';
+
+describe('getSkewCorrectedDate', () => {
+ test('gets current time but with offset', () => {
+ Date.now = jest.fn(() => signingDate.valueOf());
+ expect(getSkewCorrectedDate(0)).toStrictEqual(signingDate);
+
+ const expectedDate = new Date(signingDate);
+ expectedDate.setSeconds(signingDate.getSeconds() + 30);
+ expect(getSkewCorrectedDate(30 * 1000)).toStrictEqual(expectedDate);
+ });
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/utils/getUpdatedSystemClockOffset-test.ts b/packages/core/__tests__/clients/middleware/signing/utils/getUpdatedSystemClockOffset-test.ts
new file mode 100644
index 00000000000..a7bf498885e
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/utils/getUpdatedSystemClockOffset-test.ts
@@ -0,0 +1,43 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { getUpdatedSystemClockOffset } from '../../../../../src/clients/middleware/signing/utils/getUpdatedSystemClockOffset';
+import { isClockSkewed } from '../../../../../src/clients/middleware/signing/utils/isClockSkewed';
+import { signingDate } from '../signer/signatureV4/testUtils/data';
+
+jest.mock('../../../../../src/clients/middleware/signing/utils/isClockSkewed');
+
+const mockIsClockSkewed = isClockSkewed as jest.Mock;
+
+describe('getUpdatedSystemClockOffset', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ Date.now = jest.fn(() => signingDate.valueOf());
+ });
+
+ test('returns the current offset if not skewed', () => {
+ mockIsClockSkewed.mockReturnValue(false);
+ const offset = 1500;
+ expect(getUpdatedSystemClockOffset(signingDate.getTime(), offset)).toBe(
+ offset
+ );
+ });
+
+ test('returns the updated offset if system clock is behind', () => {
+ mockIsClockSkewed.mockReturnValue(true);
+ const clockTime = new Date(signingDate);
+ clockTime.setMinutes(signingDate.getMinutes() + 15);
+ expect(getUpdatedSystemClockOffset(clockTime.getTime(), 0)).toBe(
+ 15 * 60 * 1000
+ );
+ });
+
+ test('returns the updated offset if system clock is ahead', () => {
+ mockIsClockSkewed.mockReturnValue(true);
+ const clockTime = new Date(signingDate);
+ clockTime.setMinutes(signingDate.getMinutes() - 15);
+ expect(getUpdatedSystemClockOffset(clockTime.getTime(), 0)).toBe(
+ -15 * 60 * 1000
+ );
+ });
+});
diff --git a/packages/core/__tests__/clients/middleware/signing/utils/isClockSkewed-test.ts b/packages/core/__tests__/clients/middleware/signing/utils/isClockSkewed-test.ts
new file mode 100644
index 00000000000..38df2218091
--- /dev/null
+++ b/packages/core/__tests__/clients/middleware/signing/utils/isClockSkewed-test.ts
@@ -0,0 +1,45 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { getSkewCorrectedDate } from '../../../../../src/clients/middleware/signing/utils/getSkewCorrectedDate';
+import { isClockSkewed } from '../../../../../src/clients/middleware/signing/utils/isClockSkewed';
+import { signingDate } from '../signer/signatureV4/testUtils/data';
+
+jest.mock(
+ '../../../../../src/clients/middleware/signing/utils/getSkewCorrectedDate'
+);
+
+const mockGetSkewCorrectedDate = getSkewCorrectedDate as jest.Mock;
+
+describe('isClockSkewed', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+
+ test('returns true if system clock is too far behind', () => {
+ const clockTime = new Date(signingDate);
+ mockGetSkewCorrectedDate.mockReturnValue(signingDate);
+
+ clockTime.setMinutes(signingDate.getMinutes() + 5);
+ expect(isClockSkewed(clockTime.getTime(), 0)).toBe(true);
+ });
+
+ test('returns true if system clock is too far ahead', () => {
+ const clockTime = new Date(signingDate);
+ mockGetSkewCorrectedDate.mockReturnValue(signingDate);
+
+ clockTime.setMinutes(signingDate.getMinutes() - 5);
+ expect(isClockSkewed(clockTime.getTime(), 0)).toBe(true);
+ });
+
+ test('returns false if clock skew is within tolerance', () => {
+ const clockTime = new Date(signingDate);
+ mockGetSkewCorrectedDate.mockReturnValue(signingDate);
+
+ clockTime.setMinutes(signingDate.getMinutes() + 4);
+ expect(isClockSkewed(clockTime.getTime(), 0)).toBe(false);
+
+ clockTime.setMinutes(signingDate.getMinutes() - 4);
+ expect(isClockSkewed(clockTime.getTime(), 0)).toBe(false);
+ });
+});
diff --git a/packages/core/__tests__/clients/utils/isClockSkewedError-test.ts b/packages/core/__tests__/clients/utils/isClockSkewedError-test.ts
new file mode 100644
index 00000000000..d711e4bc5b0
--- /dev/null
+++ b/packages/core/__tests__/clients/utils/isClockSkewedError-test.ts
@@ -0,0 +1,15 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { isClockSkewError } from '../../../src/clients/utils/isClockSkewError';
+
+describe('isClockSkewError', () => {
+ test('returns true if error code is a clock skew error', () => {
+ expect(isClockSkewError('RequestInTheFuture')).toBe(true);
+ expect(isClockSkewError('RequestTimeTooSkewed')).toBe(true);
+ });
+
+ test('returns false if error code is not a clock skew error', () => {
+ expect(isClockSkewError('Foo')).toBe(false);
+ });
+});
diff --git a/packages/core/package.json b/packages/core/package.json
index 797b5fcf8c0..e15874c78b1 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -108,12 +108,6 @@
"import": "{ Signer }",
"limit": "7.08 kB"
},
- {
- "name": "Custom clients (retry middleware)",
- "path": "./lib-esm/clients/middleware/retry/middleware.js",
- "import": "{ retryMiddleware }",
- "limit": "1.24 kB"
- },
{
"name": "Custom clients (fetch handler)",
"path": "./lib-esm/clients/handlers/fetch.js",
@@ -126,6 +120,18 @@
"import": "{ unauthenticatedHandler }",
"limit": "2.75 kB"
},
+ {
+ "name": "Custom clients (retry middleware)",
+ "path": "./lib-esm/clients/middleware/retry/middleware.js",
+ "import": "{ retryMiddleware }",
+ "limit": "1.24 kB"
+ },
+ {
+ "name": "Custom clients (signing middleware)",
+ "path": "./lib-esm/clients/middleware/signing/middleware.js",
+ "import": "{ signingMiddleware }",
+ "limit": "6.77 kB"
+ },
{
"name": "Custom clients (request signer)",
"path": "./lib-esm/clients/middleware/signing/signer/signatureV4/index.js",
diff --git a/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts b/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
index 38991c96914..913b30c23b5 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
@@ -12,7 +12,7 @@ import {
defaultConfigs,
sharedHeaders,
} from './base';
-import { composeServiceApi } from '../../clients/internal/composeApiHandler';
+import { composeServiceApi } from '../../clients/internal/composeServiceApi';
import { Endpoint, HttpRequest, HttpResponse } from '../../clients/types';
import {
parseJsonBody,
diff --git a/packages/core/src/AwsClients/CognitoIdentity/getId.ts b/packages/core/src/AwsClients/CognitoIdentity/getId.ts
index 0c914ec510e..80ad71d752b 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/getId.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/getId.ts
@@ -11,7 +11,7 @@ import {
defaultConfigs,
sharedHeaders,
} from './base';
-import { composeServiceApi } from '../../clients/internal/composeApiHandler';
+import { composeServiceApi } from '../../clients/internal/composeServiceApi';
import { Endpoint, HttpRequest, HttpResponse } from '../../clients/types';
import {
parseJsonBody,
diff --git a/packages/core/src/clients/internal/composeApiHandler.ts b/packages/core/src/clients/internal/composeServiceApi.ts
similarity index 100%
rename from packages/core/src/clients/internal/composeApiHandler.ts
rename to packages/core/src/clients/internal/composeServiceApi.ts
diff --git a/packages/core/src/clients/middleware/retry/defaultRetryDecider.ts b/packages/core/src/clients/middleware/retry/defaultRetryDecider.ts
index 9ea345e1bd4..94a71ea64b4 100644
--- a/packages/core/src/clients/middleware/retry/defaultRetryDecider.ts
+++ b/packages/core/src/clients/middleware/retry/defaultRetryDecider.ts
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { HttpResponse, ErrorParser } from '../../types';
+import { isClockSkewError } from '../../utils/isClockSkewError';
/**
* Get retry decider function
@@ -37,16 +38,6 @@ const THROTTLING_ERROR_CODES = [
'TooManyRequestsException',
];
-const CLOCK_SKEW_ERROR_CODES = [
- 'AuthFailure',
- 'InvalidSignatureException',
- 'RequestExpired',
- 'RequestInTheFuture',
- 'RequestTimeTooSkewed',
- 'SignatureDoesNotMatch',
- 'BadRequestException', // API Gateway
-];
-
const TIMEOUT_ERROR_CODES = [
'TimeoutError',
'RequestTimeout',
@@ -58,9 +49,6 @@ const isThrottlingError = (statusCode?: number, errorCode?: string) =>
const isConnectionError = (error?: Error) => error?.name === 'Network error';
-const isClockSkewError = (errorCode?: string) =>
- CLOCK_SKEW_ERROR_CODES.includes(errorCode);
-
const isServerSideError = (statusCode?: number, errorCode?: string) =>
[500, 502, 503, 504].includes(statusCode) ||
TIMEOUT_ERROR_CODES.includes(errorCode);
diff --git a/packages/core/src/clients/middleware/signing/index.ts b/packages/core/src/clients/middleware/signing/index.ts
new file mode 100644
index 00000000000..de2f2566dcb
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/index.ts
@@ -0,0 +1,4 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+export { signingMiddleware, SigningOptions } from './middleware';
diff --git a/packages/core/src/clients/middleware/signing/middleware.ts b/packages/core/src/clients/middleware/signing/middleware.ts
new file mode 100644
index 00000000000..3499a2c2a39
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/middleware.ts
@@ -0,0 +1,65 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import {
+ Credentials,
+ HttpRequest,
+ HttpResponse,
+ MiddlewareHandler,
+} from '../../types';
+import { isClockSkewError } from '../../utils/isClockSkewError';
+import { signRequest } from './signer/signatureV4';
+import { getSkewCorrectedDate } from './utils/getSkewCorrectedDate';
+import { getUpdatedSystemClockOffset } from './utils/getUpdatedSystemClockOffset';
+
+/**
+ * Configuration of the signing middleware
+ */
+export interface SigningOptions {
+ credentials: Credentials;
+ region: string;
+ service: string;
+}
+
+/**
+ * Signing middleware
+ */
+export const signingMiddleware = ({
+ credentials,
+ region,
+ service,
+}: SigningOptions) => {
+ let currentSystemClockOffset;
+ return (next: MiddlewareHandler) =>
+ async function signingMiddleware(request: HttpRequest) {
+ currentSystemClockOffset = currentSystemClockOffset ?? 0;
+ const signRequestOptions = {
+ credentials,
+ signingDate: getSkewCorrectedDate(currentSystemClockOffset),
+ signingRegion: region,
+ signingService: service,
+ };
+ const signedRequest = await signRequest(request, signRequestOptions);
+ try {
+ const response = await next(signedRequest);
+ return response;
+ } catch (error) {
+ const dateString = shouldUseServerTime(error)
+ ? error.ServerTime
+ : getDateHeader(error.$response);
+ if (dateString) {
+ currentSystemClockOffset = getUpdatedSystemClockOffset(
+ Date.parse(dateString),
+ currentSystemClockOffset
+ );
+ }
+ throw error;
+ }
+ };
+};
+
+const shouldUseServerTime = ({ ServerTime, Code }: any): boolean =>
+ ServerTime && isClockSkewError(Code);
+
+const getDateHeader = ({ headers }: any = {}): string | undefined =>
+ headers?.date ?? headers?.Date;
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalQueryString.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalQueryString.ts
index 1686eaf537c..dea68420faa 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalQueryString.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getCanonicalQueryString.ts
@@ -1,6 +1,8 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
+import { extendedEncodeURIComponent } from '../../../utils/extendedEncodeURIComponent';
+
/**
* Returns a canonical query string.
*
@@ -22,11 +24,8 @@ export const getCanonicalQueryString = (
}
return keyA < keyB ? -1 : 1;
})
- .map(([key, val]) => `${escapeUri(key)}=${escapeUri(val)}`)
+ .map(
+ ([key, val]) =>
+ `${extendedEncodeURIComponent(key)}=${extendedEncodeURIComponent(val)}`
+ )
.join('&');
-
-const escapeUri = (uri: string): string =>
- encodeURIComponent(uri).replace(/[!'()*]/g, hexEncode);
-
-const hexEncode = (c: string) =>
- `%${c.charCodeAt(0).toString(16).toUpperCase()}`;
diff --git a/packages/core/src/clients/middleware/signing/utils/extendedEncodeURIComponent.ts b/packages/core/src/clients/middleware/signing/utils/extendedEncodeURIComponent.ts
new file mode 100644
index 00000000000..9eb94ea9614
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/utils/extendedEncodeURIComponent.ts
@@ -0,0 +1,16 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+/**
+ * Wraps encodeURIComponent to encode additional characters to fully adhere to RFC 3986.
+ *
+ * @param uri URI string to encode
+ * @returns RFC 3986 encoded string
+ *
+ * @internal
+ */
+export const extendedEncodeURIComponent = (uri: string): string =>
+ encodeURIComponent(uri).replace(/[!'()*]/g, hexEncode);
+
+const hexEncode = (c: string) =>
+ `%${c.charCodeAt(0).toString(16).toUpperCase()}`;
diff --git a/packages/core/src/clients/middleware/signing/utils/getSkewCorrectedDate.ts b/packages/core/src/clients/middleware/signing/utils/getSkewCorrectedDate.ts
new file mode 100644
index 00000000000..f08c3b66a46
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/utils/getSkewCorrectedDate.ts
@@ -0,0 +1,14 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+/**
+ * Returns a `Date` that is corrected for clock skew.
+ *
+ * @param systemClockOffset The offset of the system clock in milliseconds.
+ *
+ * @returns `Date` representing the current time adjusted by the system clock offset.
+ *
+ * @internal
+ */
+export const getSkewCorrectedDate = (systemClockOffset: number): Date =>
+ new Date(Date.now() + systemClockOffset);
diff --git a/packages/core/src/clients/middleware/signing/utils/getUpdatedSystemClockOffset.ts b/packages/core/src/clients/middleware/signing/utils/getUpdatedSystemClockOffset.ts
new file mode 100644
index 00000000000..d6e24d45474
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/utils/getUpdatedSystemClockOffset.ts
@@ -0,0 +1,22 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { isClockSkewed } from './isClockSkewed';
+
+/**
+ * Returns the difference between clock time and the current system time if clock is skewed.
+ *
+ * @param clockTimeInMilliseconds Clock time in milliseconds.
+ * @param currentSystemClockOffset Current system clock offset in milliseconds.
+ *
+ * @internal
+ */
+export const getUpdatedSystemClockOffset = (
+ clockTimeInMilliseconds: number,
+ currentSystemClockOffset: number
+): number => {
+ if (isClockSkewed(clockTimeInMilliseconds, currentSystemClockOffset)) {
+ return clockTimeInMilliseconds - Date.now();
+ }
+ return currentSystemClockOffset;
+};
diff --git a/packages/core/src/clients/middleware/signing/utils/isClockSkewed.ts b/packages/core/src/clients/middleware/signing/utils/isClockSkewed.ts
new file mode 100644
index 00000000000..4e9c5ab93a7
--- /dev/null
+++ b/packages/core/src/clients/middleware/signing/utils/isClockSkewed.ts
@@ -0,0 +1,26 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { getSkewCorrectedDate } from './getSkewCorrectedDate';
+
+const SKEW_WINDOW = 5 * 60 * 1000; // 5 minutes
+
+/**
+ * Checks if the provided date is within the skew window of 5 minutes.
+ *
+ * @param clockTimeInMilliseconds Time to check for skew in milliseconds.
+ * @param clockOffsetInMilliseconds Offset to check clock against in milliseconds.
+ *
+ * @returns True if the difference in time between the checked time and offset is greater than the 5 minute skew window
+ * and false otherwise.
+ *
+ * @internal
+ */
+export const isClockSkewed = (
+ clockTimeInMilliseconds: number,
+ clockOffsetInMilliseconds: number
+): boolean =>
+ Math.abs(
+ getSkewCorrectedDate(clockOffsetInMilliseconds).getTime() -
+ clockTimeInMilliseconds
+ ) >= SKEW_WINDOW;
diff --git a/packages/core/src/clients/types/core.ts b/packages/core/src/clients/types/core.ts
index 7430bfc9edd..d369312cd40 100644
--- a/packages/core/src/clients/types/core.ts
+++ b/packages/core/src/clients/types/core.ts
@@ -29,8 +29,7 @@ export type MiddlewareHandler = (
) => Promise;
/**
- * The context object to store states across the middleware chain and retry
- * attempts if retry middleware exists.
+ * The context object to store states across the middleware chain.
*/
export type MiddlewareContext = {
/**
diff --git a/packages/core/src/clients/utils/isClockSkewError.ts b/packages/core/src/clients/utils/isClockSkewError.ts
new file mode 100644
index 00000000000..6e111557098
--- /dev/null
+++ b/packages/core/src/clients/utils/isClockSkewError.ts
@@ -0,0 +1,24 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+// via https://github.com/aws/aws-sdk-js-v3/blob/ab0e7be36e7e7f8a0c04834357aaad643c7912c3/packages/service-error-classification/src/constants.ts#L8
+const CLOCK_SKEW_ERROR_CODES = [
+ 'AuthFailure',
+ 'InvalidSignatureException',
+ 'RequestExpired',
+ 'RequestInTheFuture',
+ 'RequestTimeTooSkewed',
+ 'SignatureDoesNotMatch',
+ 'BadRequestException', // API Gateway
+];
+
+/**
+ * Given an error code, returns true if it is related to a clock skew error.
+ *
+ * @param errorCode String representation of some error.
+ * @returns True if given error is present in `CLOCK_SKEW_ERROR_CODES`, false otherwise.
+ *
+ * @internal
+ */
+export const isClockSkewError = (errorCode?: string) =>
+ CLOCK_SKEW_ERROR_CODES.includes(errorCode);
From 9de297519fdbaaf1e9b4ae98f12aed4137400222 Mon Sep 17 00:00:00 2001
From: AllanZhengYP
Date: Wed, 3 May 2023 22:54:49 -0700
Subject: [PATCH 16/41] feat(clients): support CN partition by adding DNS
suffix resolver (#11311)
* feat(clients): support CN partition by adding DNS suffix resolver
* chore(clients): update bundle size test limit
* fix(clients): address feedbacks
* chore: update bundle size limit
---
packages/analytics/package.json | 2 +-
packages/api-graphql/package.json | 2 +-
packages/api-rest/package.json | 2 +-
packages/api/package.json | 2 +-
packages/auth/package.json | 2 +-
packages/cache/package.json | 2 +-
.../core/__tests__/clients/endpoints-test.ts | 22 ++++++++++
packages/core/package.json | 2 +-
.../src/AwsClients/CognitoIdentity/base.ts | 8 ++--
.../src/clients/endpoints/getDnsSuffix.ts | 25 ++++++++++++
packages/core/src/clients/endpoints/index.ts | 4 ++
.../core/src/clients/endpoints/partitions.ts | 40 +++++++++++++++++++
packages/core/src/clients/types/aws.ts | 7 +++-
packages/core/src/clients/types/index.ts | 7 +++-
packages/datastore/package.json | 2 +-
packages/geo/package.json | 2 +-
packages/interactions/package.json | 2 +-
packages/notifications/package.json | 4 +-
packages/pubsub/package.json | 4 +-
packages/storage/package.json | 2 +-
20 files changed, 122 insertions(+), 21 deletions(-)
create mode 100644 packages/core/__tests__/clients/endpoints-test.ts
create mode 100644 packages/core/src/clients/endpoints/getDnsSuffix.ts
create mode 100644 packages/core/src/clients/endpoints/index.ts
create mode 100644 packages/core/src/clients/endpoints/partitions.ts
diff --git a/packages/analytics/package.json b/packages/analytics/package.json
index 6046065486e..dcc71fa6440 100644
--- a/packages/analytics/package.json
+++ b/packages/analytics/package.json
@@ -64,7 +64,7 @@
"name": "Analytics (Pinpoint)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Analytics, AWSPinpointProvider }",
- "limit": "56.5 kB"
+ "limit": "54.7 kB"
},
{
"name": "Analytics (Kinesis)",
diff --git a/packages/api-graphql/package.json b/packages/api-graphql/package.json
index 6a2845b38bd..946464a57c5 100644
--- a/packages/api-graphql/package.json
+++ b/packages/api-graphql/package.json
@@ -64,7 +64,7 @@
"name": "API (GraphQL client)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, GraphQLAPI }",
- "limit": "86.49 kB"
+ "limit": "86.85 kB"
}
],
"jest": {
diff --git a/packages/api-rest/package.json b/packages/api-rest/package.json
index 30009f86999..f8741b07b57 100644
--- a/packages/api-rest/package.json
+++ b/packages/api-rest/package.json
@@ -55,7 +55,7 @@
"name": "API (rest client)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, RestAPI }",
- "limit": "29.22 kB"
+ "limit": "29.5 kB"
}
],
"jest": {
diff --git a/packages/api/package.json b/packages/api/package.json
index 6d8d3c0c2d0..3cf83251809 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -66,7 +66,7 @@
"name": "API (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, API }",
- "limit": "87.85 kB"
+ "limit": "88.1 kB"
}
],
"jest": {
diff --git a/packages/auth/package.json b/packages/auth/package.json
index e9c911eeab5..f8b4bc7f8dc 100644
--- a/packages/auth/package.json
+++ b/packages/auth/package.json
@@ -58,7 +58,7 @@
"name": "Auth (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Auth }",
- "limit": "53.5 kB"
+ "limit": "53.7 kB"
}
],
"jest": {
diff --git a/packages/cache/package.json b/packages/cache/package.json
index 5d8f2d72b44..4060c93bb93 100644
--- a/packages/cache/package.json
+++ b/packages/cache/package.json
@@ -62,7 +62,7 @@
"name": "Cache (in-memory)",
"path": "./lib-esm/index.js",
"import": "{ InMemoryCache }",
- "limit": "3.75 kB"
+ "limit": "3.7 kB"
}
],
"jest": {
diff --git a/packages/core/__tests__/clients/endpoints-test.ts b/packages/core/__tests__/clients/endpoints-test.ts
new file mode 100644
index 00000000000..8802a8cdc25
--- /dev/null
+++ b/packages/core/__tests__/clients/endpoints-test.ts
@@ -0,0 +1,22 @@
+import { getDnsSuffix } from '../../src/clients/endpoints';
+
+describe(getDnsSuffix.name, () => {
+ test.each([
+ ['happy case aws partition', 'af-south-1', 'amazonaws.com'],
+ ['happy case aws partition', 'us-east-1', 'amazonaws.com'],
+ ['infer aws partition', 'us-unspecified-99', 'amazonaws.com'],
+ ['infer aws partition', 'eu-unspecified-99', 'amazonaws.com'],
+ ['infer aws partition', 'ap-unspecified-99', 'amazonaws.com'],
+ ['infer aws partition', 'sa-unspecified-99', 'amazonaws.com'],
+ ['infer aws partition', 'ca-unspecified-99', 'amazonaws.com'],
+ ['infer aws partition', 'me-unspecified-99', 'amazonaws.com'],
+ ['infer aws partition', 'af-unspecified-99', 'amazonaws.com'],
+ ['aws partition global', 'aws-global', 'amazonaws.com'],
+ ['happy case cn partition', 'cn-north-1', 'amazonaws.com.cn'],
+ ['infer cn partition', 'cn-unspecified-99', 'amazonaws.com.cn'],
+ ['cn partition global', 'aws-cn-global', 'amazonaws.com.cn'],
+ ['fallback to default partition', 'unrecognized-region', 'amazonaws.com'],
+ ])(`%s -- resolve region %s to dnsSuffix %s`, (_, region, dnsSuffix) => {
+ expect(getDnsSuffix(region)).toEqual(dnsSuffix);
+ });
+});
diff --git a/packages/core/package.json b/packages/core/package.json
index e15874c78b1..efe9f76e591 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -100,7 +100,7 @@
"name": "Core (Credentials)",
"path": "./lib-esm/index.js",
"import": "{ Credentials }",
- "limit": "11.7 kB"
+ "limit": "12 kB"
},
{
"name": "Core (Signer)",
diff --git a/packages/core/src/AwsClients/CognitoIdentity/base.ts b/packages/core/src/AwsClients/CognitoIdentity/base.ts
index ea597ca9388..d9f368b498f 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/base.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/base.ts
@@ -3,11 +3,13 @@
import type {
Endpoint,
+ EndpointResolverOptions,
Headers,
HttpRequest,
HttpResponse,
Middleware,
} from '../../clients/types';
+import { getDnsSuffix } from '../../clients/endpoints';
import { composeTransferHandler } from '../../clients/internal/composeTransferHandler';
import { unauthenticatedHandler } from '../../clients/handlers/unauthenticated';
import {
@@ -25,10 +27,8 @@ const SERVICE_NAME = 'cognito-identity';
/**
* The endpoint resolver function that returns the endpoint URL for a given region.
*/
-const endpointResolver = (endpointOptions: { region: string }) => ({
- url: new URL(
- `https://cognito-identity.${endpointOptions.region}.amazonaws.com`
- ),
+const endpointResolver = ({ region }: EndpointResolverOptions) => ({
+ url: new URL(`https://cognito-identity.${region}.${getDnsSuffix(region)}`),
});
/**
diff --git a/packages/core/src/clients/endpoints/getDnsSuffix.ts b/packages/core/src/clients/endpoints/getDnsSuffix.ts
new file mode 100644
index 00000000000..dd5e8ee9729
--- /dev/null
+++ b/packages/core/src/clients/endpoints/getDnsSuffix.ts
@@ -0,0 +1,25 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { defaultPartition, partitionsInfo } from './partitions';
+
+/**
+ * Get the AWS Services endpoint URL's DNS suffix for a given region. A typical AWS regional service endpoint URL will
+ * follow this pattern: {endpointPrefix}.{region}.{dnsSuffix}. For example, the endpoint URL for Cognito Identity in
+ * us-east-1 will be cognito-identity.us-east-1.amazonaws.com. Here the DnsSuffix is `amazonaws.com`.
+ *
+ * @param region
+ * @returns The DNS suffix
+ *
+ * @internal
+ */
+export const getDnsSuffix = (region: string): string => {
+ const { partitions } = partitionsInfo;
+ for (const { regions, outputs, regionRegex } of partitions) {
+ const regex = new RegExp(regionRegex);
+ if (regions.includes(region) || regex.test(region)) {
+ return outputs.dnsSuffix;
+ }
+ }
+ return defaultPartition.outputs.dnsSuffix;
+};
diff --git a/packages/core/src/clients/endpoints/index.ts b/packages/core/src/clients/endpoints/index.ts
new file mode 100644
index 00000000000..e84c073a8af
--- /dev/null
+++ b/packages/core/src/clients/endpoints/index.ts
@@ -0,0 +1,4 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+export { getDnsSuffix } from './getDnsSuffix';
diff --git a/packages/core/src/clients/endpoints/partitions.ts b/packages/core/src/clients/endpoints/partitions.ts
new file mode 100644
index 00000000000..192a32f3bcc
--- /dev/null
+++ b/packages/core/src/clients/endpoints/partitions.ts
@@ -0,0 +1,40 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+/**
+ * Default partition for AWS services. This is used when the region is not provided or the region is not recognized.
+ *
+ * @internal
+ */
+export const defaultPartition = {
+ id: 'aws',
+ outputs: {
+ dnsSuffix: 'amazonaws.com',
+ },
+ regionRegex: '^(us|eu|ap|sa|ca|me|af)\\-\\w+\\-\\d+$',
+ regions: ['aws-global'],
+};
+
+/**
+ * This data is adapted from the partition file from AWS SDK shared utilities but remove some contents for bundle size
+ * concern. Information removed are `dualStackDnsSuffix`, `supportDualStack`, `supportFIPS`, restricted partitions, and
+ * list of regions for each partition other than global regions.
+ *
+ * * Ref: https://docs.aws.amazon.com/general/latest/gr/rande.html#regional-endpoints
+ * * Ref: https://github.com/aws/aws-sdk-js-v3/blob/0201baef03c2379f1f6f7150b9d401d4b230d488/packages/util-endpoints/src/lib/aws/partitions.json#L1
+ *
+ * @internal
+ */
+export const partitionsInfo = {
+ partitions: [
+ defaultPartition,
+ {
+ id: 'aws-cn',
+ outputs: {
+ dnsSuffix: 'amazonaws.com.cn',
+ },
+ regionRegex: '^cn\\-\\w+\\-\\d+$',
+ regions: ['aws-cn-global'],
+ },
+ ],
+};
diff --git a/packages/core/src/clients/types/aws.ts b/packages/core/src/clients/types/aws.ts
index b1d71635ce5..44dfcf16d7e 100644
--- a/packages/core/src/clients/types/aws.ts
+++ b/packages/core/src/clients/types/aws.ts
@@ -8,9 +8,14 @@ export type { Credentials } from '@aws-sdk/types';
export type SourceData = string | ArrayBuffer | ArrayBufferView;
+/**
+ * Basic option type for endpoint resolvers. It contains region only.
+ */
+export type EndpointResolverOptions = { region: string };
+
export interface ServiceClientOptions {
region: string;
- endpointResolver: (input: { region: string }) => Endpoint;
+ endpointResolver: (options: EndpointResolverOptions) => Endpoint;
}
/**
diff --git a/packages/core/src/clients/types/index.ts b/packages/core/src/clients/types/index.ts
index 9a1ed26b4ce..e2b8953a4d2 100644
--- a/packages/core/src/clients/types/index.ts
+++ b/packages/core/src/clients/types/index.ts
@@ -17,4 +17,9 @@ export {
HttpTransferOptions,
ResponseBodyMixin,
} from './http';
-export { Credentials, ServiceClientOptions, ErrorParser } from './aws';
+export {
+ Credentials,
+ EndpointResolverOptions,
+ ErrorParser,
+ ServiceClientOptions,
+} from './aws';
diff --git a/packages/datastore/package.json b/packages/datastore/package.json
index 89449c56e91..65b424a2823 100644
--- a/packages/datastore/package.json
+++ b/packages/datastore/package.json
@@ -72,7 +72,7 @@
"name": "DataStore (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, DataStore }",
- "limit": "135.85 kB"
+ "limit": "136.1 kB"
}
],
"jest": {
diff --git a/packages/geo/package.json b/packages/geo/package.json
index e31bbd73e72..d1725371337 100644
--- a/packages/geo/package.json
+++ b/packages/geo/package.json
@@ -57,7 +57,7 @@
"name": "Geo (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Geo }",
- "limit": "50.2 kB"
+ "limit": "50.5 kB"
}
],
"jest": {
diff --git a/packages/interactions/package.json b/packages/interactions/package.json
index 9ecece35138..cfe3ee9387c 100644
--- a/packages/interactions/package.json
+++ b/packages/interactions/package.json
@@ -59,7 +59,7 @@
"name": "Interactions (top-level class with Lex v2)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Interactions, AWSLexV2Provider }",
- "limit": "74.1 kB"
+ "limit": "74.5 kB"
}
],
"jest": {
diff --git a/packages/notifications/package.json b/packages/notifications/package.json
index 0f10696f4aa..ca0e389e3fb 100644
--- a/packages/notifications/package.json
+++ b/packages/notifications/package.json
@@ -60,13 +60,13 @@
"name": "Notifications (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Notifications }",
- "limit": "59 kB"
+ "limit": "59.3 kB"
},
{
"name": "Notifications (with Analytics)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Notifications, Analytics }",
- "limit": "59 kB"
+ "limit": "59.3 kB"
}
],
"devDependencies": {
diff --git a/packages/pubsub/package.json b/packages/pubsub/package.json
index 2f1635224c7..029be206ba6 100644
--- a/packages/pubsub/package.json
+++ b/packages/pubsub/package.json
@@ -63,13 +63,13 @@
"name": "PubSub (IoT provider)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, PubSub, AWSIoTProvider }",
- "limit": "78.76 kB"
+ "limit": "79.1 kB"
},
{
"name": "PubSub (Mqtt provider)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, PubSub, MqttOverWSProvider }",
- "limit": "78.61 kB"
+ "limit": "78.9 kB"
}
],
"jest": {
diff --git a/packages/storage/package.json b/packages/storage/package.json
index ffb196a7260..62b49f7c2fa 100644
--- a/packages/storage/package.json
+++ b/packages/storage/package.json
@@ -60,7 +60,7 @@
"name": "Storage (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Storage }",
- "limit": "87 kB"
+ "limit": "84.8 kB"
}
],
"jest": {
From 497a105bb7e3d7ad10cab049e88df564139034fe Mon Sep 17 00:00:00 2001
From: Chris F <5827964+cshfang@users.noreply.github.com>
Date: Thu, 4 May 2023 09:20:10 -0700
Subject: [PATCH 17/41] chore(clients): Add context to some regex (#11334)
---
.../signing/signer/signatureV4/utils/getFormattedDates.ts | 5 ++++-
.../signing/utils/extendedEncodeURIComponent.ts | 8 ++++++--
2 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getFormattedDates.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getFormattedDates.ts
index 2b742910c83..fd04ee6cd46 100644
--- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getFormattedDates.ts
+++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getFormattedDates.ts
@@ -14,7 +14,10 @@ import { FormattedDates } from '../types/signer';
* @internal
*/
export const getFormattedDates = (date: Date): FormattedDates => {
- const longDate = date.toISOString().replace(/[:\-]|\.\d{3}/g, '');
+ // Match unneeded ISO 8601 characters and the sub-second digits
+ const unneededCharacters = /[:\-]|\.\d{3}/g;
+
+ const longDate = date.toISOString().replace(unneededCharacters, '');
return {
longDate,
shortDate: longDate.slice(0, 8),
diff --git a/packages/core/src/clients/middleware/signing/utils/extendedEncodeURIComponent.ts b/packages/core/src/clients/middleware/signing/utils/extendedEncodeURIComponent.ts
index 9eb94ea9614..a90499e7936 100644
--- a/packages/core/src/clients/middleware/signing/utils/extendedEncodeURIComponent.ts
+++ b/packages/core/src/clients/middleware/signing/utils/extendedEncodeURIComponent.ts
@@ -9,8 +9,12 @@
*
* @internal
*/
-export const extendedEncodeURIComponent = (uri: string): string =>
- encodeURIComponent(uri).replace(/[!'()*]/g, hexEncode);
+export const extendedEncodeURIComponent = (uri: string): string => {
+ // Match characters normally not encoded by `encodeURIComponent`
+ const extendedCharacters = /[!'()*]/g;
+
+ return encodeURIComponent(uri).replace(extendedCharacters, hexEncode);
+};
const hexEncode = (c: string) =>
`%${c.charCodeAt(0).toString(16).toUpperCase()}`;
From 36ca0bdac67e18de67d054cf64cb9d3a3556bfd9 Mon Sep 17 00:00:00 2001
From: Chris F <5827964+cshfang@users.noreply.github.com>
Date: Thu, 4 May 2023 14:09:34 -0700
Subject: [PATCH 18/41] feat(clients) Add updateEndpoint API (#11330)
* feat(clients) Add updateEndpoint API
* Rename handler
---
.../AwsClients/CognitoIdentity-test.ts | 212 ------------------
.../getCredentialsForIdentity-test.ts | 105 +++++++++
.../AwsClients/CognitoIdentity/getId-test.ts | 99 ++++++++
.../Pinpoint/updateEndpoint-test.ts | 109 +++++++++
.../__tests__/AwsClients/testUtils/data.ts | 53 +++++
packages/core/package.json | 1 +
.../src/AwsClients/CognitoIdentity/base.ts | 4 +-
.../getCredentialsForIdentity.ts | 13 +-
.../src/AwsClients/CognitoIdentity/getId.ts | 13 +-
packages/core/src/AwsClients/Pinpoint/base.ts | 40 ++++
.../core/src/AwsClients/Pinpoint/index.ts | 8 +
.../src/AwsClients/Pinpoint/updateEndpoint.ts | 57 +++++
.../src/clients/handlers/authenticated.ts | 19 ++
13 files changed, 503 insertions(+), 230 deletions(-)
delete mode 100644 packages/core/__tests__/AwsClients/CognitoIdentity-test.ts
create mode 100644 packages/core/__tests__/AwsClients/CognitoIdentity/getCredentialsForIdentity-test.ts
create mode 100644 packages/core/__tests__/AwsClients/CognitoIdentity/getId-test.ts
create mode 100644 packages/core/__tests__/AwsClients/Pinpoint/updateEndpoint-test.ts
create mode 100644 packages/core/__tests__/AwsClients/testUtils/data.ts
create mode 100644 packages/core/src/AwsClients/Pinpoint/base.ts
create mode 100644 packages/core/src/AwsClients/Pinpoint/index.ts
create mode 100644 packages/core/src/AwsClients/Pinpoint/updateEndpoint.ts
create mode 100644 packages/core/src/clients/handlers/authenticated.ts
diff --git a/packages/core/__tests__/AwsClients/CognitoIdentity-test.ts b/packages/core/__tests__/AwsClients/CognitoIdentity-test.ts
deleted file mode 100644
index cb7e6d8234a..00000000000
--- a/packages/core/__tests__/AwsClients/CognitoIdentity-test.ts
+++ /dev/null
@@ -1,212 +0,0 @@
-/**
- * @jest-environment jsdom
- */
-
-import { fetchTransferHandler } from '../../src/clients/handlers/fetch';
-import {
- getId,
- GetIdInput,
- getCredentialsForIdentity,
- GetIdOutput,
- GetCredentialsForIdentityInput,
- GetCredentialsForIdentityOutput,
-} from '../../src/AwsClients/CognitoIdentity';
-import { HttpResponse } from '../../src/clients/types';
-jest.mock('../../src/clients/handlers/fetch');
-
-const mockJsonResponse = ({
- status,
- headers,
- body,
-}: {
- status: number;
- headers: Record;
- body: any;
-}): HttpResponse => {
- const responseBody = {
- json: async () => body,
- blob: async () => fail('blob() should not be called'),
- text: async () => fail('text() should not be called'),
- } as HttpResponse['body'];
- return {
- statusCode: status,
- headers,
- body: responseBody,
- };
-};
-
-describe('cognito-identity service client', () => {
- const REQUEST_ID = 'ff1ca798-b930-4b81-9ef3-c02e770188af';
- const IDENTITY_ID = 'us-east-1:88859bc9-0149-4183-bf10-39e36EXAMPLE';
- const handlerOptions = {
- region: 'us-east-1',
- };
-
- // API reference: https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetId.html
- describe('getId', () => {
- const IDENTITY_POOL_ID = 'us-east-1:177a950c-2c08-43f0-9983-28727EXAMPLE';
- const ACCOUNT_ID = '123456789012';
- const params: GetIdInput = {
- IdentityPoolId: IDENTITY_POOL_ID,
- AccountId: ACCOUNT_ID,
- };
-
- test('happy case', async () => {
- const expectedRequest = {
- url: new URL('https://cognito-identity.us-east-1.amazonaws.com/'),
- method: 'POST',
- headers: expect.objectContaining({
- 'cache-control': 'no-store',
- 'content-type': 'application/x-amz-json-1.1',
- 'x-amz-target': 'AWSCognitoIdentityService.GetId',
- 'x-amz-user-agent': expect.stringContaining('aws-amplify'),
- }),
- body: JSON.stringify({
- IdentityPoolId: IDENTITY_POOL_ID,
- AccountId: ACCOUNT_ID,
- }),
- };
- const succeedResponse = {
- status: 200,
- headers: {
- 'x-amzn-requestid': REQUEST_ID,
- },
- body: {
- IdentityId: IDENTITY_ID,
- },
- };
- const expectedOutput: GetIdOutput = {
- IdentityId: IDENTITY_ID,
- $metadata: expect.objectContaining({
- attempts: 1,
- requestId: REQUEST_ID,
- httpStatusCode: 200,
- }),
- };
- (fetchTransferHandler as jest.Mock).mockResolvedValue(
- mockJsonResponse(succeedResponse)
- );
- const response = await getId(handlerOptions, params);
- expect(response).toEqual(expectedOutput);
- expect(fetchTransferHandler).toBeCalledWith(
- expectedRequest,
- expect.anything()
- );
- });
-
- test('error case', async () => {
- const failureResponse = {
- status: 400,
- headers: {
- 'x-amzn-requestid': REQUEST_ID,
- 'x-amzn-errortype': 'NotAuthorizedException',
- },
- body: {
- __type: 'NotAuthorizedException',
- message: `Identity pool ${IDENTITY_POOL_ID} does not exist.`,
- },
- };
- const expectedError = {
- name: 'NotAuthorizedException',
- message: failureResponse.body.message,
- };
- (fetchTransferHandler as jest.Mock).mockResolvedValue(
- mockJsonResponse(failureResponse)
- );
- expect.assertions(1);
- try {
- await getId(handlerOptions, params);
- fail('test should fail');
- } catch (e) {
- expect(e).toEqual(expect.objectContaining(expectedError));
- }
- });
- });
-
- // API reference: https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetCredentialsForIdentity.html
- describe('getCredentialsForIdentity', () => {
- const CREDENTIALS = {
- SecretKey: 'secretKey',
- SessionToken: 'sessionToken',
- Expiration: 1442877512.0,
- AccessKeyId: 'accessKeyId',
- };
- const params: GetCredentialsForIdentityInput = { IdentityId: IDENTITY_ID };
-
- test('happy case', async () => {
- const succeedResponse = {
- status: 200,
- headers: {
- 'x-amzn-requestid': REQUEST_ID,
- },
- body: {
- Credentials: CREDENTIALS,
- IdentityId: IDENTITY_ID,
- },
- };
- const expectedOutput: GetCredentialsForIdentityOutput = {
- Credentials: {
- ...CREDENTIALS,
- Expiration: new Date(CREDENTIALS.Expiration * 1000),
- },
- IdentityId: IDENTITY_ID,
- $metadata: expect.objectContaining({
- attempts: 1,
- requestId: REQUEST_ID,
- httpStatusCode: 200,
- }),
- };
- const expectedRequest = {
- url: new URL('https://cognito-identity.us-east-1.amazonaws.com/'),
- method: 'POST',
- headers: expect.objectContaining({
- 'cache-control': 'no-store',
- 'content-type': 'application/x-amz-json-1.1',
- 'x-amz-target': 'AWSCognitoIdentityService.GetCredentialsForIdentity',
- 'x-amz-user-agent': expect.stringContaining('aws-amplify'),
- }),
- body: JSON.stringify({
- IdentityId: IDENTITY_ID,
- }),
- };
-
- (fetchTransferHandler as jest.Mock).mockResolvedValue(
- mockJsonResponse(succeedResponse)
- );
- const response = await getCredentialsForIdentity(handlerOptions, params);
- expect(response).toEqual(expectedOutput);
- expect(fetchTransferHandler).toBeCalledWith(
- expectedRequest,
- expect.anything()
- );
- });
-
- test('error case', async () => {
- const failureResponse = {
- status: 400,
- headers: {
- 'x-amzn-requestid': REQUEST_ID,
- 'x-amzn-errortype': 'NotAuthorizedException',
- },
- body: {
- __type: 'NotAuthorizedException',
- message: `Identity ${IDENTITY_ID} does not exist.`,
- },
- };
- const expectedError = {
- name: 'NotAuthorizedException',
- message: failureResponse.body.message,
- };
- (fetchTransferHandler as jest.Mock).mockResolvedValue(
- mockJsonResponse(failureResponse)
- );
- expect.assertions(1);
- try {
- await getCredentialsForIdentity(handlerOptions, params);
- fail('test should fail');
- } catch (e) {
- expect(e).toEqual(expect.objectContaining(expectedError));
- }
- });
- });
-});
diff --git a/packages/core/__tests__/AwsClients/CognitoIdentity/getCredentialsForIdentity-test.ts b/packages/core/__tests__/AwsClients/CognitoIdentity/getCredentialsForIdentity-test.ts
new file mode 100644
index 00000000000..3841edf63b8
--- /dev/null
+++ b/packages/core/__tests__/AwsClients/CognitoIdentity/getCredentialsForIdentity-test.ts
@@ -0,0 +1,105 @@
+/**
+ * @jest-environment jsdom
+ */
+
+import { fetchTransferHandler } from '../../../src/clients/handlers/fetch';
+import {
+ getCredentialsForIdentity,
+ GetCredentialsForIdentityInput,
+ GetCredentialsForIdentityOutput,
+} from '../../../src/AwsClients/CognitoIdentity';
+import {
+ cognitoIdentityHandlerOptions,
+ mockCredentials,
+ mockIdentityId,
+ mockJsonResponse,
+ mockRequestId,
+} from '../testUtils/data';
+
+jest.mock('../../../src/clients/handlers/fetch');
+
+// API reference: https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetCredentialsForIdentity.html
+describe('CognitoIdentity - getCredentialsForIdentity', () => {
+ const params: GetCredentialsForIdentityInput = {
+ IdentityId: mockIdentityId,
+ };
+
+ test('happy case', async () => {
+ const succeedResponse = {
+ status: 200,
+ headers: {
+ 'x-amzn-requestid': mockRequestId,
+ },
+ body: {
+ Credentials: mockCredentials,
+ IdentityId: mockIdentityId,
+ },
+ };
+ const expectedOutput: GetCredentialsForIdentityOutput = {
+ Credentials: {
+ ...mockCredentials,
+ Expiration: new Date(mockCredentials.Expiration * 1000),
+ },
+ IdentityId: mockIdentityId,
+ $metadata: expect.objectContaining({
+ attempts: 1,
+ requestId: mockRequestId,
+ httpStatusCode: 200,
+ }),
+ };
+ const expectedRequest = {
+ url: new URL('https://cognito-identity.us-east-1.amazonaws.com/'),
+ method: 'POST',
+ headers: expect.objectContaining({
+ 'cache-control': 'no-store',
+ 'content-type': 'application/x-amz-json-1.1',
+ 'x-amz-target': 'AWSCognitoIdentityService.GetCredentialsForIdentity',
+ 'x-amz-user-agent': expect.stringContaining('aws-amplify'),
+ }),
+ body: JSON.stringify({
+ IdentityId: mockIdentityId,
+ }),
+ };
+
+ (fetchTransferHandler as jest.Mock).mockResolvedValue(
+ mockJsonResponse(succeedResponse)
+ );
+ const response = await getCredentialsForIdentity(
+ cognitoIdentityHandlerOptions,
+ params
+ );
+ expect(response).toEqual(expectedOutput);
+ expect(fetchTransferHandler).toBeCalledWith(
+ expectedRequest,
+ expect.anything()
+ );
+ });
+
+ test('error case', async () => {
+ const failureResponse = {
+ status: 400,
+ headers: {
+ 'x-amzn-requestid': mockRequestId,
+ 'x-amzn-errortype': 'NotAuthorizedException',
+ },
+ body: {
+ __type: 'NotAuthorizedException',
+ message: `Identity ${mockIdentityId} does not exist.`,
+ },
+ };
+ const expectedError = {
+ name: 'NotAuthorizedException',
+ message: failureResponse.body.message,
+ };
+ (fetchTransferHandler as jest.Mock).mockResolvedValue(
+ mockJsonResponse(failureResponse)
+ );
+ expect.assertions(1);
+ try {
+ await getCredentialsForIdentity(cognitoIdentityHandlerOptions, params);
+ fail('test should fail');
+ } catch (e) {
+ expect(e).toEqual(expect.objectContaining(expectedError));
+ }
+ });
+});
diff --git a/packages/core/__tests__/AwsClients/CognitoIdentity/getId-test.ts b/packages/core/__tests__/AwsClients/CognitoIdentity/getId-test.ts
new file mode 100644
index 00000000000..736b5d8f47d
--- /dev/null
+++ b/packages/core/__tests__/AwsClients/CognitoIdentity/getId-test.ts
@@ -0,0 +1,99 @@
+/**
+ * @jest-environment jsdom
+ */
+
+import { fetchTransferHandler } from '../../../src/clients/handlers/fetch';
+import {
+ getId,
+ GetIdInput,
+ GetIdOutput,
+} from '../../../src/AwsClients/CognitoIdentity';
+import {
+ cognitoIdentityHandlerOptions,
+ mockIdentityId,
+ mockJsonResponse,
+ mockRequestId,
+} from '../testUtils/data';
+
+jest.mock('../../../src/clients/handlers/fetch');
+
+// API reference: https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetId.html
+describe('CognitoIdentity - getId', () => {
+ const IDENTITY_POOL_ID = 'us-east-1:177a950c-2c08-43f0-9983-28727EXAMPLE';
+ const ACCOUNT_ID = '123456789012';
+ const params: GetIdInput = {
+ IdentityPoolId: IDENTITY_POOL_ID,
+ AccountId: ACCOUNT_ID,
+ };
+
+ test('happy case', async () => {
+ const expectedRequest = {
+ url: new URL('https://cognito-identity.us-east-1.amazonaws.com/'),
+ method: 'POST',
+ headers: expect.objectContaining({
+ 'cache-control': 'no-store',
+ 'content-type': 'application/x-amz-json-1.1',
+ 'x-amz-target': 'AWSCognitoIdentityService.GetId',
+ 'x-amz-user-agent': expect.stringContaining('aws-amplify'),
+ }),
+ body: JSON.stringify({
+ IdentityPoolId: IDENTITY_POOL_ID,
+ AccountId: ACCOUNT_ID,
+ }),
+ };
+ const succeedResponse = {
+ status: 200,
+ headers: {
+ 'x-amzn-requestid': mockRequestId,
+ },
+ body: {
+ IdentityId: mockIdentityId,
+ },
+ };
+ const expectedOutput: GetIdOutput = {
+ IdentityId: mockIdentityId,
+ $metadata: expect.objectContaining({
+ attempts: 1,
+ requestId: mockRequestId,
+ httpStatusCode: 200,
+ }),
+ };
+ (fetchTransferHandler as jest.Mock).mockResolvedValue(
+ mockJsonResponse(succeedResponse)
+ );
+ const response = await getId(cognitoIdentityHandlerOptions, params);
+ expect(response).toEqual(expectedOutput);
+ expect(fetchTransferHandler).toBeCalledWith(
+ expectedRequest,
+ expect.anything()
+ );
+ });
+
+ test('error case', async () => {
+ const failureResponse = {
+ status: 400,
+ headers: {
+ 'x-amzn-requestid': mockRequestId,
+ 'x-amzn-errortype': 'NotAuthorizedException',
+ },
+ body: {
+ __type: 'NotAuthorizedException',
+ message: `Identity pool ${IDENTITY_POOL_ID} does not exist.`,
+ },
+ };
+ const expectedError = {
+ name: 'NotAuthorizedException',
+ message: failureResponse.body.message,
+ };
+ (fetchTransferHandler as jest.Mock).mockResolvedValue(
+ mockJsonResponse(failureResponse)
+ );
+ expect.assertions(1);
+ try {
+ await getId(cognitoIdentityHandlerOptions, params);
+ fail('test should fail');
+ } catch (e) {
+ expect(e).toEqual(expect.objectContaining(expectedError));
+ }
+ });
+});
diff --git a/packages/core/__tests__/AwsClients/Pinpoint/updateEndpoint-test.ts b/packages/core/__tests__/AwsClients/Pinpoint/updateEndpoint-test.ts
new file mode 100644
index 00000000000..b8d15bb8f6f
--- /dev/null
+++ b/packages/core/__tests__/AwsClients/Pinpoint/updateEndpoint-test.ts
@@ -0,0 +1,109 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { fetchTransferHandler } from '../../../src/clients/handlers/fetch';
+import {
+ updateEndpoint,
+ UpdateEndpointInput,
+ UpdateEndpointOutput,
+} from '../../../src/AwsClients/Pinpoint';
+import {
+ mockJsonResponse,
+ mockRequestId,
+ pinpointHandlerOptions,
+} from '../testUtils/data';
+
+jest.mock('../../../src/clients/handlers/fetch');
+
+// API reference: https://docs.aws.amazon.com/pinpoint/latest/apireference/apps-application-id-endpoints-endpoint-id.html#UpdateEndpoint
+describe('Pinpoint - updateEndpoint', () => {
+ const ApplicationId = 'fls189ysample154app128idpdsadk31';
+ const EndpointId = '41232bb8-1c62-448f-a295-89019bbdce5a';
+ const EndpointRequest = {
+ Attributes: {
+ hobbies: ['cooking', 'knitting'],
+ },
+ Demographic: {
+ AppVersion: '1.0',
+ },
+ RequestId: mockRequestId,
+ };
+ const MessageBody = {
+ Message: 'success',
+ RequestID: mockRequestId,
+ };
+ const params: UpdateEndpointInput = {
+ ApplicationId,
+ EndpointId,
+ EndpointRequest,
+ };
+
+ test('happy case', async () => {
+ const expectedRequest = expect.objectContaining({
+ url: expect.objectContaining({
+ href: `https://pinpoint.us-east-1.amazonaws.com/v1/apps/${ApplicationId}/endpoints/${EndpointId}`,
+ }),
+ method: 'PUT',
+ headers: expect.objectContaining({
+ authorization: expect.stringContaining('Signature'),
+ 'content-type': 'application/json',
+ host: 'pinpoint.us-east-1.amazonaws.com',
+ 'x-amz-date': expect.anything(),
+ 'x-amz-user-agent': expect.stringContaining('aws-amplify'),
+ }),
+ body: JSON.stringify(EndpointRequest),
+ });
+ const successfulResponse = {
+ status: 200,
+ headers: {
+ 'x-amzn-requestid': mockRequestId,
+ },
+ body: { ...MessageBody },
+ };
+ const expectedOutput: UpdateEndpointOutput = {
+ MessageBody,
+ $metadata: expect.objectContaining({
+ attempts: 1,
+ requestId: mockRequestId,
+ httpStatusCode: 200,
+ }),
+ };
+ (fetchTransferHandler as jest.Mock).mockResolvedValue(
+ mockJsonResponse(successfulResponse)
+ );
+ const response = await updateEndpoint(pinpointHandlerOptions, params);
+ expect(response).toEqual(expectedOutput);
+ expect(fetchTransferHandler).toBeCalledWith(
+ expectedRequest,
+ expect.anything()
+ );
+ });
+
+ test('error case', async () => {
+ const failureResponse = {
+ status: 400,
+ headers: {
+ 'x-amzn-requestid': mockRequestId,
+ 'x-amzn-errortype': 'ForbiddenException',
+ },
+ body: {
+ __type: 'ForbiddenException',
+ message: `Forbidden`,
+ },
+ };
+ const expectedError = {
+ name: 'ForbiddenException',
+ message: failureResponse.body.message,
+ };
+ (fetchTransferHandler as jest.Mock).mockResolvedValue(
+ mockJsonResponse(failureResponse)
+ );
+ expect.assertions(1);
+ try {
+ await updateEndpoint(pinpointHandlerOptions, params);
+ fail('test should fail');
+ } catch (e) {
+ expect(e).toEqual(expect.objectContaining(expectedError));
+ }
+ });
+});
diff --git a/packages/core/__tests__/AwsClients/testUtils/data.ts b/packages/core/__tests__/AwsClients/testUtils/data.ts
new file mode 100644
index 00000000000..df4da473174
--- /dev/null
+++ b/packages/core/__tests__/AwsClients/testUtils/data.ts
@@ -0,0 +1,53 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { HttpResponse } from '../../../src/clients/types';
+
+// Common
+const region = 'us-east-1';
+
+export const mockJsonResponse = ({
+ status,
+ headers,
+ body,
+}: {
+ status: number;
+ headers: Record;
+ body: any;
+}): HttpResponse => {
+ const responseBody = {
+ json: async () => body,
+ blob: async () => fail('blob() should not be called'),
+ text: async () => fail('text() should not be called'),
+ } as HttpResponse['body'];
+ return {
+ statusCode: status,
+ headers,
+ body: responseBody,
+ };
+};
+
+export const mockRequestId = 'ff1ca798-b930-4b81-9ef3-c02e770188af';
+
+export const mockCredentials = {
+ SecretKey: 'secret-access-key',
+ SessionToken: 'session-token',
+ Expiration: 1442877512.0,
+ AccessKeyId: 'access-key-id',
+};
+
+// CognitoIdentity
+export const mockIdentityId = 'us-east-1:88859bc9-0149-4183-bf10-39e36EXAMPLE';
+
+export const cognitoIdentityHandlerOptions = {
+ region,
+};
+
+// Pinpoint
+export const pinpointHandlerOptions = {
+ credentials: {
+ accessKeyId: mockCredentials.AccessKeyId,
+ secretAccessKey: mockCredentials.SecretKey,
+ },
+ region,
+};
diff --git a/packages/core/package.json b/packages/core/package.json
index efe9f76e591..27d9d15976d 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -48,6 +48,7 @@
"homepage": "https://aws-amplify.github.io/",
"devDependencies": {
"@aws-sdk/client-cognito-identity": "3.6.1",
+ "@aws-sdk/client-pinpoint": "3.186.1",
"@react-native-async-storage/async-storage": "1.15.17",
"find": "^0.2.7",
"genversion": "^2.2.0",
diff --git a/packages/core/src/AwsClients/CognitoIdentity/base.ts b/packages/core/src/AwsClients/CognitoIdentity/base.ts
index d9f368b498f..0e69a7c7274 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/base.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/base.ts
@@ -57,7 +57,7 @@ export const cognitoIdentityTransferHandler = composeTransferHandler<
/**
* @internal
*/
-export const defaultConfigs = {
+export const defaultConfig = {
service: SERVICE_NAME,
endpointResolver,
retryDecider: getRetryDecider(parseJsonError),
@@ -68,7 +68,7 @@ export const defaultConfigs = {
/**
* @internal
*/
-export const sharedHeaders = (operation: string): Headers => ({
+export const getSharedHeaders = (operation: string): Headers => ({
'content-type': 'application/x-amz-json-1.1',
'x-amz-target': `AWSCognitoIdentityService.${operation}`,
});
diff --git a/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts b/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
index 913b30c23b5..4b6257a247d 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
@@ -9,8 +9,8 @@ import type {
import {
buildHttpRpcRequest,
cognitoIdentityTransferHandler,
- defaultConfigs,
- sharedHeaders,
+ defaultConfig,
+ getSharedHeaders,
} from './base';
import { composeServiceApi } from '../../clients/internal/composeServiceApi';
import { Endpoint, HttpRequest, HttpResponse } from '../../clients/types';
@@ -20,16 +20,13 @@ import {
parseMetadata,
} from '../../clients/serde';
-export type {
- GetCredentialsForIdentityCommandInput as GetCredentialsForIdentityInput,
- GetCredentialsForIdentityCommandOutput as GetCredentialsForIdentityOutput,
-} from '@aws-sdk/client-cognito-identity';
+export type { GetCredentialsForIdentityInput, GetCredentialsForIdentityOutput };
const getCredentialsForIdentitySerializer = (
input: GetCredentialsForIdentityInput,
endpoint: Endpoint
): HttpRequest => {
- const headers = sharedHeaders('GetCredentialsForIdentity');
+ const headers = getSharedHeaders('GetCredentialsForIdentity');
const body = JSON.stringify(input);
return buildHttpRpcRequest(endpoint, headers, body);
};
@@ -61,5 +58,5 @@ export const getCredentialsForIdentity = composeServiceApi(
cognitoIdentityTransferHandler,
getCredentialsForIdentitySerializer,
getCredentialsForIdentityDeserializer,
- defaultConfigs
+ defaultConfig
);
diff --git a/packages/core/src/AwsClients/CognitoIdentity/getId.ts b/packages/core/src/AwsClients/CognitoIdentity/getId.ts
index 80ad71d752b..d5215769416 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/getId.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/getId.ts
@@ -8,8 +8,8 @@ import {
import {
buildHttpRpcRequest,
cognitoIdentityTransferHandler,
- defaultConfigs,
- sharedHeaders,
+ defaultConfig,
+ getSharedHeaders,
} from './base';
import { composeServiceApi } from '../../clients/internal/composeServiceApi';
import { Endpoint, HttpRequest, HttpResponse } from '../../clients/types';
@@ -19,16 +19,13 @@ import {
parseMetadata,
} from '../../clients/serde';
-export type {
- GetIdCommandInput as GetIdInput,
- GetIdCommandOutput as GetIdOutput,
-} from '@aws-sdk/client-cognito-identity';
+export type { GetIdInput, GetIdOutput };
const getIdSerializer = (
input: GetIdInput,
endpoint: Endpoint
): HttpRequest => {
- const headers = sharedHeaders('GetId');
+ const headers = getSharedHeaders('GetId');
const body = JSON.stringify(input);
return buildHttpRpcRequest(endpoint, headers, body);
};
@@ -52,5 +49,5 @@ export const getId = composeServiceApi(
cognitoIdentityTransferHandler,
getIdSerializer,
getIdDeserializer,
- defaultConfigs
+ defaultConfig
);
diff --git a/packages/core/src/AwsClients/Pinpoint/base.ts b/packages/core/src/AwsClients/Pinpoint/base.ts
new file mode 100644
index 00000000000..307e0618550
--- /dev/null
+++ b/packages/core/src/AwsClients/Pinpoint/base.ts
@@ -0,0 +1,40 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import {
+ jitteredBackoff,
+ getRetryDecider,
+} from '../../clients/middleware/retry';
+import { parseJsonError } from '../../clients/serde/json';
+import type { Headers } from '../../clients/types';
+import { getAmplifyUserAgent } from '../../Platform';
+
+/**
+ * The service name used to sign requests if the API requires authentication.
+ */
+const SERVICE_NAME = 'mobiletargeting';
+
+/**
+ * The endpoint resolver function that returns the endpoint URL for a given region.
+ */
+const endpointResolver = (endpointOptions: { region: string }) => ({
+ url: new URL(`https://pinpoint.${endpointOptions.region}.amazonaws.com`),
+});
+
+/**
+ * @internal
+ */
+export const defaultConfig = {
+ service: SERVICE_NAME,
+ endpointResolver,
+ retryDecider: getRetryDecider(parseJsonError),
+ computeDelay: jitteredBackoff,
+ userAgentValue: getAmplifyUserAgent(), // TODO: use getAmplifyUserAgentString() when available.
+};
+
+/**
+ * @internal
+ */
+export const getSharedHeaders = (): Headers => ({
+ 'content-type': 'application/json',
+});
diff --git a/packages/core/src/AwsClients/Pinpoint/index.ts b/packages/core/src/AwsClients/Pinpoint/index.ts
new file mode 100644
index 00000000000..f2af0f57c05
--- /dev/null
+++ b/packages/core/src/AwsClients/Pinpoint/index.ts
@@ -0,0 +1,8 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+export {
+ updateEndpoint,
+ UpdateEndpointInput,
+ UpdateEndpointOutput,
+} from './updateEndpoint';
diff --git a/packages/core/src/AwsClients/Pinpoint/updateEndpoint.ts b/packages/core/src/AwsClients/Pinpoint/updateEndpoint.ts
new file mode 100644
index 00000000000..eb204edad41
--- /dev/null
+++ b/packages/core/src/AwsClients/Pinpoint/updateEndpoint.ts
@@ -0,0 +1,57 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import type {
+ UpdateEndpointCommandInput as UpdateEndpointInput,
+ UpdateEndpointCommandOutput as UpdateEndpointOutput,
+} from '@aws-sdk/client-pinpoint';
+import { authenticatedHandler } from '../../clients/handlers/authenticated';
+import { composeServiceApi } from '../../clients/internal/composeServiceApi';
+import { extendedEncodeURIComponent } from '../../clients/middleware/signing/utils/extendedEncodeURIComponent';
+import {
+ parseJsonBody,
+ parseJsonError,
+ parseMetadata,
+} from '../../clients/serde';
+import { Endpoint, HttpRequest, HttpResponse } from '../../clients/types';
+import { defaultConfig, getSharedHeaders } from './base';
+
+export type { UpdateEndpointInput, UpdateEndpointOutput };
+
+const updateEndpointSerializer = (
+ { ApplicationId, EndpointId, EndpointRequest }: UpdateEndpointInput,
+ endpoint: Endpoint
+): HttpRequest => {
+ const headers = getSharedHeaders();
+ const url = new URL(endpoint.url);
+ url.pathname = `v1/apps/${extendedEncodeURIComponent(
+ ApplicationId
+ )}/endpoints/${extendedEncodeURIComponent(EndpointId)}`;
+ const body = JSON.stringify(EndpointRequest ?? {});
+ return { method: 'PUT', headers, url, body };
+};
+
+const updateEndpointDeserializer = async (
+ response: HttpResponse
+): Promise => {
+ if (response.statusCode >= 300) {
+ const error = await parseJsonError(response);
+ throw error;
+ } else {
+ const { Message, RequestID } = await parseJsonBody(response);
+ return {
+ MessageBody: {
+ Message,
+ RequestID,
+ },
+ $metadata: parseMetadata(response),
+ };
+ }
+};
+
+export const updateEndpoint = composeServiceApi(
+ authenticatedHandler,
+ updateEndpointSerializer,
+ updateEndpointDeserializer,
+ defaultConfig
+);
diff --git a/packages/core/src/clients/handlers/authenticated.ts b/packages/core/src/clients/handlers/authenticated.ts
new file mode 100644
index 00000000000..09bb90c494b
--- /dev/null
+++ b/packages/core/src/clients/handlers/authenticated.ts
@@ -0,0 +1,19 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { retryMiddleware, RetryOptions } from '../middleware/retry';
+import { signingMiddleware, SigningOptions } from '../middleware/signing';
+import { userAgentMiddleware, UserAgentOptions } from '../middleware/userAgent';
+import { composeTransferHandler } from '../internal/composeTransferHandler';
+import { fetchTransferHandler } from './fetch';
+import { HttpRequest, HttpResponse } from '../types';
+
+export const authenticatedHandler = composeTransferHandler<
+ [UserAgentOptions, RetryOptions, SigningOptions],
+ HttpRequest,
+ HttpResponse
+>(fetchTransferHandler, [
+ userAgentMiddleware,
+ retryMiddleware,
+ signingMiddleware,
+]);
From 40dbb3aadad1efce7990f18e078a06be0d8c5f31 Mon Sep 17 00:00:00 2001
From: Chris F <5827964+cshfang@users.noreply.github.com>
Date: Thu, 4 May 2023 15:17:25 -0700
Subject: [PATCH 19/41] chore(clients): Use DNS suffix util in Pinpoint client
(#11340)
---
packages/core/src/AwsClients/Pinpoint/base.ts | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/packages/core/src/AwsClients/Pinpoint/base.ts b/packages/core/src/AwsClients/Pinpoint/base.ts
index 307e0618550..5ca41c0ebaa 100644
--- a/packages/core/src/AwsClients/Pinpoint/base.ts
+++ b/packages/core/src/AwsClients/Pinpoint/base.ts
@@ -1,12 +1,13 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getDnsSuffix } from '../../clients/endpoints';
import {
jitteredBackoff,
getRetryDecider,
} from '../../clients/middleware/retry';
import { parseJsonError } from '../../clients/serde/json';
-import type { Headers } from '../../clients/types';
+import type { EndpointResolverOptions, Headers } from '../../clients/types';
import { getAmplifyUserAgent } from '../../Platform';
/**
@@ -17,8 +18,8 @@ const SERVICE_NAME = 'mobiletargeting';
/**
* The endpoint resolver function that returns the endpoint URL for a given region.
*/
-const endpointResolver = (endpointOptions: { region: string }) => ({
- url: new URL(`https://pinpoint.${endpointOptions.region}.amazonaws.com`),
+const endpointResolver = ({ region }: EndpointResolverOptions) => ({
+ url: new URL(`https://pinpoint.${region}.${getDnsSuffix(region)}`),
});
/**
From de56f9e028bf0e900350ffc5818a5f8637cc931f Mon Sep 17 00:00:00 2001
From: Chris F <5827964+cshfang@users.noreply.github.com>
Date: Fri, 5 May 2023 14:27:51 -0700
Subject: [PATCH 20/41] chore(clients): Annotate custom client APIs with
@internal (#11347)
---
.../CognitoIdentity/getCredentialsForIdentity.ts | 7 +++++--
packages/core/src/AwsClients/CognitoIdentity/getId.ts | 3 +++
packages/core/src/AwsClients/Pinpoint/updateEndpoint.ts | 3 +++
3 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts b/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
index 4b6257a247d..b758f414bb5 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
@@ -41,19 +41,22 @@ const getCredentialsForIdentityDeserializer = async (
const body = await parseJsonBody(response);
return {
IdentityId: body.IdentityId,
- Credentials: de_Credentials(body.Credentials),
+ Credentials: deserializeCredentials(body.Credentials),
$metadata: parseMetadata(response),
};
}
};
-const de_Credentials = (output: unknown = {}): Credentials => ({
+const deserializeCredentials = (output: unknown = {}): Credentials => ({
AccessKeyId: output['AccessKeyId'] as string,
SecretKey: output['SecretKey'] as string,
SessionToken: output['SessionToken'] as string,
Expiration: new Date((output['Expiration'] as number) * 1000),
});
+/**
+ * @internal
+ */
export const getCredentialsForIdentity = composeServiceApi(
cognitoIdentityTransferHandler,
getCredentialsForIdentitySerializer,
diff --git a/packages/core/src/AwsClients/CognitoIdentity/getId.ts b/packages/core/src/AwsClients/CognitoIdentity/getId.ts
index d5215769416..ad69f98f8bc 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/getId.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/getId.ts
@@ -45,6 +45,9 @@ const getIdDeserializer = async (
}
};
+/**
+ * @internal
+ */
export const getId = composeServiceApi(
cognitoIdentityTransferHandler,
getIdSerializer,
diff --git a/packages/core/src/AwsClients/Pinpoint/updateEndpoint.ts b/packages/core/src/AwsClients/Pinpoint/updateEndpoint.ts
index eb204edad41..99ebc4e9a1d 100644
--- a/packages/core/src/AwsClients/Pinpoint/updateEndpoint.ts
+++ b/packages/core/src/AwsClients/Pinpoint/updateEndpoint.ts
@@ -49,6 +49,9 @@ const updateEndpointDeserializer = async (
}
};
+/**
+ * @internal
+ */
export const updateEndpoint = composeServiceApi(
authenticatedHandler,
updateEndpointSerializer,
From a188ae73ee5aac09733fab27de85f11a9a295fab Mon Sep 17 00:00:00 2001
From: Chris F <5827964+cshfang@users.noreply.github.com>
Date: Fri, 5 May 2023 15:01:39 -0700
Subject: [PATCH 21/41] feat(clients) Add putEvents API (#11342)
* feat(clients) Add putEvents API
* Add additional verification for expected date format
* Mark API as internal
---
.../AwsClients/Pinpoint/putEvents-test.ts | 99 +++++++++++++++++++
.../Pinpoint/updateEndpoint-test.ts | 26 ++---
.../__tests__/AwsClients/testUtils/data.ts | 32 ++++++
.../core/src/AwsClients/Pinpoint/index.ts | 1 +
.../core/src/AwsClients/Pinpoint/putEvents.ts | 55 +++++++++++
5 files changed, 196 insertions(+), 17 deletions(-)
create mode 100644 packages/core/__tests__/AwsClients/Pinpoint/putEvents-test.ts
create mode 100644 packages/core/src/AwsClients/Pinpoint/putEvents.ts
diff --git a/packages/core/__tests__/AwsClients/Pinpoint/putEvents-test.ts b/packages/core/__tests__/AwsClients/Pinpoint/putEvents-test.ts
new file mode 100644
index 00000000000..41fe9b0c12e
--- /dev/null
+++ b/packages/core/__tests__/AwsClients/Pinpoint/putEvents-test.ts
@@ -0,0 +1,99 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { fetchTransferHandler } from '../../../src/clients/handlers/fetch';
+import {
+ putEvents,
+ PutEventsInput,
+ PutEventsOutput,
+} from '../../../src/AwsClients/Pinpoint';
+import {
+ mockApplicationId,
+ mockEventsRequest,
+ mockJsonResponse,
+ mockRequestId,
+ pinpointHandlerOptions,
+} from '../testUtils/data';
+
+jest.mock('../../../src/clients/handlers/fetch');
+
+// API reference: https://docs.aws.amazon.com/pinpoint/latest/apireference/apps-application-id-endpoints-endpoint-id.html#UpdateEndpoint
+describe('Pinpoint - putEvents', () => {
+ const MessageBody = {
+ Message: 'success',
+ RequestID: mockRequestId,
+ };
+ const params: PutEventsInput = {
+ ApplicationId: mockApplicationId,
+ EventsRequest: mockEventsRequest,
+ };
+
+ test('happy case', async () => {
+ const expectedRequest = expect.objectContaining({
+ url: expect.objectContaining({
+ href: `https://pinpoint.us-east-1.amazonaws.com/v1/apps/${mockApplicationId}/events`,
+ }),
+ method: 'POST',
+ headers: expect.objectContaining({
+ authorization: expect.stringContaining('Signature'),
+ 'content-type': 'application/json',
+ host: 'pinpoint.us-east-1.amazonaws.com',
+ 'x-amz-date': expect.stringMatching(/^\d{8}T\d{6}Z/),
+ 'x-amz-user-agent': expect.stringContaining('aws-amplify'),
+ }),
+ body: JSON.stringify(mockEventsRequest),
+ });
+ const successfulResponse = {
+ status: 200,
+ headers: {
+ 'x-amzn-requestid': mockRequestId,
+ },
+ body: { ...MessageBody },
+ };
+ const expectedOutput: PutEventsOutput = {
+ EventsResponse: {},
+ $metadata: expect.objectContaining({
+ attempts: 1,
+ requestId: mockRequestId,
+ httpStatusCode: 200,
+ }),
+ };
+ (fetchTransferHandler as jest.Mock).mockResolvedValue(
+ mockJsonResponse(successfulResponse)
+ );
+ const response = await putEvents(pinpointHandlerOptions, params);
+ expect(response).toEqual(expectedOutput);
+ expect(fetchTransferHandler).toBeCalledWith(
+ expectedRequest,
+ expect.anything()
+ );
+ });
+
+ test('error case', async () => {
+ const failureResponse = {
+ status: 400,
+ headers: {
+ 'x-amzn-requestid': mockRequestId,
+ 'x-amzn-errortype': 'ForbiddenException',
+ },
+ body: {
+ __type: 'ForbiddenException',
+ message: `Forbidden`,
+ },
+ };
+ const expectedError = {
+ name: 'ForbiddenException',
+ message: failureResponse.body.message,
+ };
+ (fetchTransferHandler as jest.Mock).mockResolvedValue(
+ mockJsonResponse(failureResponse)
+ );
+ expect.assertions(1);
+ try {
+ await putEvents(pinpointHandlerOptions, params);
+ fail('test should fail');
+ } catch (e) {
+ expect(e).toEqual(expect.objectContaining(expectedError));
+ }
+ });
+});
diff --git a/packages/core/__tests__/AwsClients/Pinpoint/updateEndpoint-test.ts b/packages/core/__tests__/AwsClients/Pinpoint/updateEndpoint-test.ts
index b8d15bb8f6f..e78fed52a29 100644
--- a/packages/core/__tests__/AwsClients/Pinpoint/updateEndpoint-test.ts
+++ b/packages/core/__tests__/AwsClients/Pinpoint/updateEndpoint-test.ts
@@ -8,6 +8,9 @@ import {
UpdateEndpointOutput,
} from '../../../src/AwsClients/Pinpoint';
import {
+ mockApplicationId,
+ mockEndpointId,
+ mockEndpointRequest,
mockJsonResponse,
mockRequestId,
pinpointHandlerOptions,
@@ -17,41 +20,30 @@ jest.mock('../../../src/clients/handlers/fetch');
// API reference: https://docs.aws.amazon.com/pinpoint/latest/apireference/apps-application-id-endpoints-endpoint-id.html#UpdateEndpoint
describe('Pinpoint - updateEndpoint', () => {
- const ApplicationId = 'fls189ysample154app128idpdsadk31';
- const EndpointId = '41232bb8-1c62-448f-a295-89019bbdce5a';
- const EndpointRequest = {
- Attributes: {
- hobbies: ['cooking', 'knitting'],
- },
- Demographic: {
- AppVersion: '1.0',
- },
- RequestId: mockRequestId,
- };
const MessageBody = {
Message: 'success',
RequestID: mockRequestId,
};
const params: UpdateEndpointInput = {
- ApplicationId,
- EndpointId,
- EndpointRequest,
+ ApplicationId: mockApplicationId,
+ EndpointId: mockEndpointId,
+ EndpointRequest: mockEndpointRequest,
};
test('happy case', async () => {
const expectedRequest = expect.objectContaining({
url: expect.objectContaining({
- href: `https://pinpoint.us-east-1.amazonaws.com/v1/apps/${ApplicationId}/endpoints/${EndpointId}`,
+ href: `https://pinpoint.us-east-1.amazonaws.com/v1/apps/${mockApplicationId}/endpoints/${mockEndpointId}`,
}),
method: 'PUT',
headers: expect.objectContaining({
authorization: expect.stringContaining('Signature'),
'content-type': 'application/json',
host: 'pinpoint.us-east-1.amazonaws.com',
- 'x-amz-date': expect.anything(),
+ 'x-amz-date': expect.stringMatching(/^\d{8}T\d{6}Z/),
'x-amz-user-agent': expect.stringContaining('aws-amplify'),
}),
- body: JSON.stringify(EndpointRequest),
+ body: JSON.stringify(mockEndpointRequest),
});
const successfulResponse = {
status: 200,
diff --git a/packages/core/__tests__/AwsClients/testUtils/data.ts b/packages/core/__tests__/AwsClients/testUtils/data.ts
index df4da473174..6e00eac4743 100644
--- a/packages/core/__tests__/AwsClients/testUtils/data.ts
+++ b/packages/core/__tests__/AwsClients/testUtils/data.ts
@@ -44,6 +44,38 @@ export const cognitoIdentityHandlerOptions = {
};
// Pinpoint
+export const mockApplicationId = 'fls189ysample154app128idpdsadk31';
+
+export const mockEndpointId = '41232bb8-1c62-448f-a295-89019bbdce5a';
+
+export const mockEndpointRequest = {
+ Attributes: {
+ hobbies: ['cooking', 'knitting'],
+ },
+ Demographic: {
+ AppVersion: '1.0',
+ },
+ RequestId: mockRequestId,
+};
+
+const mockEventId = '0e8f291b-e0dd-47d8-860a-6b7317bf7eba';
+
+const mockEvent = {
+ EventType: 'event-type',
+ Timestamp: '2023-05-03T16:22:19.597Z',
+};
+
+export const mockEventsRequest = {
+ BatchItem: {
+ [mockEndpointId]: {
+ Endpoint: mockEndpointRequest,
+ Events: {
+ [mockEventId]: mockEvent,
+ },
+ },
+ },
+};
+
export const pinpointHandlerOptions = {
credentials: {
accessKeyId: mockCredentials.AccessKeyId,
diff --git a/packages/core/src/AwsClients/Pinpoint/index.ts b/packages/core/src/AwsClients/Pinpoint/index.ts
index f2af0f57c05..94f539c8421 100644
--- a/packages/core/src/AwsClients/Pinpoint/index.ts
+++ b/packages/core/src/AwsClients/Pinpoint/index.ts
@@ -6,3 +6,4 @@ export {
UpdateEndpointInput,
UpdateEndpointOutput,
} from './updateEndpoint';
+export { putEvents, PutEventsInput, PutEventsOutput } from './putEvents';
diff --git a/packages/core/src/AwsClients/Pinpoint/putEvents.ts b/packages/core/src/AwsClients/Pinpoint/putEvents.ts
new file mode 100644
index 00000000000..2185386cb13
--- /dev/null
+++ b/packages/core/src/AwsClients/Pinpoint/putEvents.ts
@@ -0,0 +1,55 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import type {
+ PutEventsCommandInput as PutEventsInput,
+ PutEventsCommandOutput as PutEventsOutput,
+} from '@aws-sdk/client-pinpoint';
+import { authenticatedHandler } from '../../clients/handlers/authenticated';
+import { composeServiceApi } from '../../clients/internal/composeServiceApi';
+import { extendedEncodeURIComponent } from '../../clients/middleware/signing/utils/extendedEncodeURIComponent';
+import {
+ parseJsonBody,
+ parseJsonError,
+ parseMetadata,
+} from '../../clients/serde';
+import { Endpoint, HttpRequest, HttpResponse } from '../../clients/types';
+import { defaultConfig, getSharedHeaders } from './base';
+
+export type { PutEventsInput, PutEventsOutput };
+
+const putEventsSerializer = (
+ { ApplicationId, EventsRequest }: PutEventsInput,
+ endpoint: Endpoint
+): HttpRequest => {
+ const headers = getSharedHeaders();
+ const url = new URL(endpoint.url);
+ url.pathname = `v1/apps/${extendedEncodeURIComponent(ApplicationId)}/events`;
+ const body = JSON.stringify(EventsRequest ?? {});
+ return { method: 'POST', headers, url, body };
+};
+
+const putEventsDeserializer = async (
+ response: HttpResponse
+): Promise => {
+ if (response.statusCode >= 300) {
+ const error = await parseJsonError(response);
+ throw error;
+ } else {
+ const { Results } = await parseJsonBody(response);
+ return {
+ EventsResponse: { Results },
+ $metadata: parseMetadata(response),
+ };
+ }
+};
+
+/**
+ * @internal
+ */
+export const putEvents = composeServiceApi(
+ authenticatedHandler,
+ putEventsSerializer,
+ putEventsDeserializer,
+ defaultConfig
+);
From 48162c1b4c92b287f32e3af7dae96d40827cf979 Mon Sep 17 00:00:00 2001
From: Chris F <5827964+cshfang@users.noreply.github.com>
Date: Mon, 8 May 2023 16:42:15 -0700
Subject: [PATCH 22/41] feat(clients) Add getInAppMessages API (#11348)
* feat(clients) Add getInAppMessages API
* Update unit test
---
.../Pinpoint/getInAppMessages-test.ts | 87 +++++++++++++++++++
.../AwsClients/Pinpoint/putEvents-test.ts | 27 ++----
.../Pinpoint/updateEndpoint-test.ts | 16 +---
.../__tests__/AwsClients/testUtils/data.ts | 12 +++
.../AwsClients/Pinpoint/getInAppMessages.ts | 56 ++++++++++++
.../core/src/AwsClients/Pinpoint/index.ts | 7 +-
6 files changed, 171 insertions(+), 34 deletions(-)
create mode 100644 packages/core/__tests__/AwsClients/Pinpoint/getInAppMessages-test.ts
create mode 100644 packages/core/src/AwsClients/Pinpoint/getInAppMessages.ts
diff --git a/packages/core/__tests__/AwsClients/Pinpoint/getInAppMessages-test.ts b/packages/core/__tests__/AwsClients/Pinpoint/getInAppMessages-test.ts
new file mode 100644
index 00000000000..bdc1b474435
--- /dev/null
+++ b/packages/core/__tests__/AwsClients/Pinpoint/getInAppMessages-test.ts
@@ -0,0 +1,87 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { fetchTransferHandler } from '../../../src/clients/handlers/fetch';
+import {
+ getInAppMessages,
+ GetInAppMessagesInput,
+ GetInAppMessagesOutput,
+} from '../../../src/AwsClients/Pinpoint';
+import {
+ mockApplicationId,
+ mockEndpointId,
+ mockFailureResponse,
+ mockJsonResponse,
+ mockRequestId,
+ pinpointHandlerOptions,
+} from '../testUtils/data';
+
+jest.mock('../../../src/clients/handlers/fetch');
+
+// API reference: https://docs.aws.amazon.com/pinpoint/latest/apireference/apps-application-id-endpoints-endpoint-id-inappmessages.html#GetInAppMessages
+describe('Pinpoint - getInAppMessages', () => {
+ const InAppMessagesResponse = {
+ InAppMessageCampaigns: [],
+ };
+ const params: GetInAppMessagesInput = {
+ ApplicationId: mockApplicationId,
+ EndpointId: mockEndpointId,
+ };
+
+ test('happy case', async () => {
+ const expectedRequest = expect.objectContaining({
+ url: expect.objectContaining({
+ href: `https://pinpoint.us-east-1.amazonaws.com/v1/apps/${mockApplicationId}/endpoints/${mockEndpointId}/inappmessages`,
+ }),
+ method: 'GET',
+ headers: expect.objectContaining({
+ authorization: expect.stringContaining('Signature'),
+ 'content-type': 'application/json',
+ host: 'pinpoint.us-east-1.amazonaws.com',
+ 'x-amz-date': expect.stringMatching(/^\d{8}T\d{6}Z/),
+ 'x-amz-user-agent': expect.stringContaining('aws-amplify'),
+ }),
+ });
+ const successfulResponse = {
+ status: 200,
+ headers: {
+ 'x-amzn-requestid': mockRequestId,
+ },
+ body: { ...InAppMessagesResponse },
+ };
+ const expectedOutput: GetInAppMessagesOutput = {
+ InAppMessagesResponse,
+ $metadata: expect.objectContaining({
+ attempts: 1,
+ requestId: mockRequestId,
+ httpStatusCode: 200,
+ }),
+ };
+ (fetchTransferHandler as jest.Mock).mockResolvedValue(
+ mockJsonResponse(successfulResponse)
+ );
+ const response = await getInAppMessages(pinpointHandlerOptions, params);
+ expect(response).toEqual(expectedOutput);
+ expect(fetchTransferHandler).toBeCalledWith(
+ expectedRequest,
+ expect.anything()
+ );
+ });
+
+ test('error case', async () => {
+ const expectedError = {
+ name: 'ForbiddenException',
+ message: mockFailureResponse.body.message,
+ };
+ (fetchTransferHandler as jest.Mock).mockResolvedValue(
+ mockJsonResponse(mockFailureResponse)
+ );
+ expect.assertions(1);
+ try {
+ await getInAppMessages(pinpointHandlerOptions, params);
+ fail('test should fail');
+ } catch (e) {
+ expect(e).toEqual(expect.objectContaining(expectedError));
+ }
+ });
+});
diff --git a/packages/core/__tests__/AwsClients/Pinpoint/putEvents-test.ts b/packages/core/__tests__/AwsClients/Pinpoint/putEvents-test.ts
index 41fe9b0c12e..3b2b9e010a3 100644
--- a/packages/core/__tests__/AwsClients/Pinpoint/putEvents-test.ts
+++ b/packages/core/__tests__/AwsClients/Pinpoint/putEvents-test.ts
@@ -10,6 +10,7 @@ import {
import {
mockApplicationId,
mockEventsRequest,
+ mockFailureResponse,
mockJsonResponse,
mockRequestId,
pinpointHandlerOptions,
@@ -17,12 +18,9 @@ import {
jest.mock('../../../src/clients/handlers/fetch');
-// API reference: https://docs.aws.amazon.com/pinpoint/latest/apireference/apps-application-id-endpoints-endpoint-id.html#UpdateEndpoint
+// API reference: https://docs.aws.amazon.com/pinpoint/latest/apireference/apps-application-id-events.html#PutEvents
describe('Pinpoint - putEvents', () => {
- const MessageBody = {
- Message: 'success',
- RequestID: mockRequestId,
- };
+ const EventsResponse = { Results: {} };
const params: PutEventsInput = {
ApplicationId: mockApplicationId,
EventsRequest: mockEventsRequest,
@@ -48,10 +46,10 @@ describe('Pinpoint - putEvents', () => {
headers: {
'x-amzn-requestid': mockRequestId,
},
- body: { ...MessageBody },
+ body: { ...EventsResponse },
};
const expectedOutput: PutEventsOutput = {
- EventsResponse: {},
+ EventsResponse,
$metadata: expect.objectContaining({
attempts: 1,
requestId: mockRequestId,
@@ -70,23 +68,12 @@ describe('Pinpoint - putEvents', () => {
});
test('error case', async () => {
- const failureResponse = {
- status: 400,
- headers: {
- 'x-amzn-requestid': mockRequestId,
- 'x-amzn-errortype': 'ForbiddenException',
- },
- body: {
- __type: 'ForbiddenException',
- message: `Forbidden`,
- },
- };
const expectedError = {
name: 'ForbiddenException',
- message: failureResponse.body.message,
+ message: mockFailureResponse.body.message,
};
(fetchTransferHandler as jest.Mock).mockResolvedValue(
- mockJsonResponse(failureResponse)
+ mockJsonResponse(mockFailureResponse)
);
expect.assertions(1);
try {
diff --git a/packages/core/__tests__/AwsClients/Pinpoint/updateEndpoint-test.ts b/packages/core/__tests__/AwsClients/Pinpoint/updateEndpoint-test.ts
index e78fed52a29..ffacf4a8d97 100644
--- a/packages/core/__tests__/AwsClients/Pinpoint/updateEndpoint-test.ts
+++ b/packages/core/__tests__/AwsClients/Pinpoint/updateEndpoint-test.ts
@@ -11,6 +11,7 @@ import {
mockApplicationId,
mockEndpointId,
mockEndpointRequest,
+ mockFailureResponse,
mockJsonResponse,
mockRequestId,
pinpointHandlerOptions,
@@ -72,23 +73,12 @@ describe('Pinpoint - updateEndpoint', () => {
});
test('error case', async () => {
- const failureResponse = {
- status: 400,
- headers: {
- 'x-amzn-requestid': mockRequestId,
- 'x-amzn-errortype': 'ForbiddenException',
- },
- body: {
- __type: 'ForbiddenException',
- message: `Forbidden`,
- },
- };
const expectedError = {
name: 'ForbiddenException',
- message: failureResponse.body.message,
+ message: mockFailureResponse.body.message,
};
(fetchTransferHandler as jest.Mock).mockResolvedValue(
- mockJsonResponse(failureResponse)
+ mockJsonResponse(mockFailureResponse)
);
expect.assertions(1);
try {
diff --git a/packages/core/__tests__/AwsClients/testUtils/data.ts b/packages/core/__tests__/AwsClients/testUtils/data.ts
index 6e00eac4743..9227c964aac 100644
--- a/packages/core/__tests__/AwsClients/testUtils/data.ts
+++ b/packages/core/__tests__/AwsClients/testUtils/data.ts
@@ -76,6 +76,18 @@ export const mockEventsRequest = {
},
};
+export const mockFailureResponse = {
+ status: 403,
+ headers: {
+ 'x-amzn-requestid': mockRequestId,
+ 'x-amzn-errortype': 'ForbiddenException',
+ },
+ body: {
+ __type: 'ForbiddenException',
+ message: `Forbidden`,
+ },
+};
+
export const pinpointHandlerOptions = {
credentials: {
accessKeyId: mockCredentials.AccessKeyId,
diff --git a/packages/core/src/AwsClients/Pinpoint/getInAppMessages.ts b/packages/core/src/AwsClients/Pinpoint/getInAppMessages.ts
new file mode 100644
index 00000000000..672ab0a2110
--- /dev/null
+++ b/packages/core/src/AwsClients/Pinpoint/getInAppMessages.ts
@@ -0,0 +1,56 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import type {
+ GetInAppMessagesCommandInput as GetInAppMessagesInput,
+ GetInAppMessagesCommandOutput as GetInAppMessagesOutput,
+} from '@aws-sdk/client-pinpoint';
+import { authenticatedHandler } from '../../clients/handlers/authenticated';
+import { composeServiceApi } from '../../clients/internal/composeServiceApi';
+import { extendedEncodeURIComponent } from '../../clients/middleware/signing/utils/extendedEncodeURIComponent';
+import {
+ parseJsonBody,
+ parseJsonError,
+ parseMetadata,
+} from '../../clients/serde';
+import { Endpoint, HttpRequest, HttpResponse } from '../../clients/types';
+import { defaultConfig, getSharedHeaders } from './base';
+
+export type { GetInAppMessagesInput, GetInAppMessagesOutput };
+
+const getInAppMessagesSerializer = (
+ { ApplicationId, EndpointId }: GetInAppMessagesInput,
+ endpoint: Endpoint
+): HttpRequest => {
+ const headers = getSharedHeaders();
+ const url = new URL(endpoint.url);
+ url.pathname = `v1/apps/${extendedEncodeURIComponent(
+ ApplicationId
+ )}/endpoints/${extendedEncodeURIComponent(EndpointId)}/inappmessages`;
+ return { method: 'GET', headers, url };
+};
+
+const getInAppMessagesDeserializer = async (
+ response: HttpResponse
+): Promise => {
+ if (response.statusCode >= 300) {
+ const error = await parseJsonError(response);
+ throw error;
+ } else {
+ const { InAppMessageCampaigns } = await parseJsonBody(response);
+ return {
+ InAppMessagesResponse: { InAppMessageCampaigns },
+ $metadata: parseMetadata(response),
+ };
+ }
+};
+
+/**
+ * @internal
+ */
+export const getInAppMessages = composeServiceApi(
+ authenticatedHandler,
+ getInAppMessagesSerializer,
+ getInAppMessagesDeserializer,
+ defaultConfig
+);
diff --git a/packages/core/src/AwsClients/Pinpoint/index.ts b/packages/core/src/AwsClients/Pinpoint/index.ts
index 94f539c8421..445ada9ac97 100644
--- a/packages/core/src/AwsClients/Pinpoint/index.ts
+++ b/packages/core/src/AwsClients/Pinpoint/index.ts
@@ -1,9 +1,14 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
+export {
+ getInAppMessages,
+ GetInAppMessagesInput,
+ GetInAppMessagesOutput,
+} from './getInAppMessages';
+export { putEvents, PutEventsInput, PutEventsOutput } from './putEvents';
export {
updateEndpoint,
UpdateEndpointInput,
UpdateEndpointOutput,
} from './updateEndpoint';
-export { putEvents, PutEventsInput, PutEventsOutput } from './putEvents';
From bd7883def34b25580f6b2b491da710fcb6beaf31 Mon Sep 17 00:00:00 2001
From: Chris F <5827964+cshfang@users.noreply.github.com>
Date: Fri, 12 May 2023 15:10:35 -0700
Subject: [PATCH 23/41] chore(clients): Replace SDK Pinpoint Client (#11359)
Co-authored-by: Allan Zheng
---
.../Providers/AWSPinpointProvider.test.ts | 472 ++++++++----------
.../__tests__/Providers/EventBuffer.test.ts | 4 +-
packages/analytics/package.json | 8 +-
.../src/Providers/AWSPinpointProvider.ts | 180 +++----
.../analytics/src/Providers/EventBuffer.ts | 99 ++--
packages/notifications/__mocks__/data.ts | 18 +-
.../AWSPinpointProvider/index.test.ts | 11 +-
.../AWSPinpointProvider/utils.test.ts | 2 +-
.../AWSPinpointProvider/index.test.ts | 10 +-
.../AWSPinpointProviderCommon/index.test.ts | 23 +-
packages/notifications/package.json | 14 +-
.../Providers/AWSPinpointProvider/index.ts | 22 +-
.../Providers/AWSPinpointProvider/utils.ts | 9 +-
.../Providers/AWSPinpointProvider/index.ts | 6 +-
.../Providers/AWSPinpointProvider/utils.ts | 2 +-
.../common/AWSPinpointProviderCommon/index.ts | 98 ++--
.../common/AWSPinpointProviderCommon/types.ts | 15 +
17 files changed, 432 insertions(+), 561 deletions(-)
diff --git a/packages/analytics/__tests__/Providers/AWSPinpointProvider.test.ts b/packages/analytics/__tests__/Providers/AWSPinpointProvider.test.ts
index 082feb9f699..78b29b92814 100644
--- a/packages/analytics/__tests__/Providers/AWSPinpointProvider.test.ts
+++ b/packages/analytics/__tests__/Providers/AWSPinpointProvider.test.ts
@@ -1,10 +1,7 @@
import { Credentials, ClientDevice } from '@aws-amplify/core';
+import { putEvents } from '@aws-amplify/core/lib-esm/AwsClients/Pinpoint/putEvents';
+import { updateEndpoint } from '@aws-amplify/core/lib-esm/AwsClients/Pinpoint/updateEndpoint';
import { AWSPinpointProvider as AnalyticsProvider } from '../../src/Providers/AWSPinpointProvider';
-import {
- PinpointClient,
- UpdateEndpointCommand,
- PutEventsCommand,
-} from '@aws-sdk/client-pinpoint';
const endpointConfigure = {
address: 'configured', // The unique identifier for the recipient. For example, an address could be a device token, email address, or mobile phone number.
@@ -143,49 +140,34 @@ const optionsWithClientContext = {
},
};
-let response = {
- EventsResponse: {
- Results: {
- endpointId: {
- EventsItemResponse: {
- uuid: {
- Message: 'Accepted',
- StatusCode: 202,
- },
- },
- },
- },
- },
-};
-
let resolve = null;
let reject = null;
jest.mock('uuid', () => {
return { v1: () => 'uuid' };
});
+jest.mock('@aws-amplify/core/lib-esm/AwsClients/Pinpoint/putEvents');
+jest.mock('@aws-amplify/core/lib-esm/AwsClients/Pinpoint/updateEndpoint');
+
+const mockPutEvents = putEvents as jest.Mock;
+const mockUpdateEndpoint = updateEndpoint as jest.Mock;
beforeEach(() => {
- PinpointClient.prototype.send = jest.fn(async command => {
- if (command instanceof UpdateEndpointCommand) {
- return 'data';
- }
- if (command instanceof PutEventsCommand) {
- return {
- EventsResponse: {
- Results: {
- endpointId: {
- EventsItemResponse: {
- uuid: {
- Message: 'Accepted',
- StatusCode: 202,
- },
- },
+ jest.clearAllMocks();
+ mockUpdateEndpoint.mockReturnValue('data');
+ mockPutEvents.mockReturnValue({
+ EventsResponse: {
+ Results: {
+ endpointId: {
+ EventsItemResponse: {
+ uuid: {
+ Message: 'Accepted',
+ StatusCode: 202,
},
},
},
- };
- }
+ },
+ },
});
jest.spyOn(Date.prototype, 'getTime').mockImplementation(() => {
@@ -295,48 +277,47 @@ describe('AnalyticsProvider test', () => {
test('custom events', async () => {
const analytics = new AnalyticsProvider();
analytics.configure(options);
- const spyon = jest.spyOn(PinpointClient.prototype, 'send');
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
});
const params = { event: { name: 'custom event', immediate: true } };
await analytics.record(params, { resolve, reject });
- expect(spyon.mock.calls[0][0].input).toEqual({
- ApplicationId: 'appId',
- EventsRequest: {
- BatchItem: {
- endpointId: {
- Endpoint: {},
- Events: {
- uuid: {
- Attributes: undefined,
- EventType: 'custom event',
- Metrics: undefined,
- Session: {
- Id: 'uuid',
- StartTimestamp: 'isoString',
+ expect(mockPutEvents).toBeCalledWith(
+ { credentials, region: 'region' },
+ {
+ ApplicationId: 'appId',
+ EventsRequest: {
+ BatchItem: {
+ endpointId: {
+ Endpoint: {},
+ Events: {
+ uuid: {
+ Attributes: undefined,
+ EventType: 'custom event',
+ Metrics: undefined,
+ Session: {
+ Id: 'uuid',
+ StartTimestamp: 'isoString',
+ },
+ Timestamp: 'isoString',
},
- Timestamp: 'isoString',
},
},
},
},
- },
- });
+ }
+ );
expect(resolve).toBeCalled();
- spyon.mockRestore();
});
test('custom event error', async () => {
const analytics = new AnalyticsProvider();
analytics.configure(options);
- const spyon = jest
- .spyOn(PinpointClient.prototype, 'send')
- .mockImplementationOnce(async () => {
- throw 'data';
- });
+ mockPutEvents.mockImplementation(async () => {
+ throw 'data';
+ });
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
@@ -346,7 +327,6 @@ describe('AnalyticsProvider test', () => {
await analytics.record(params, { resolve, reject });
expect(reject).toBeCalled();
- spyon.mockRestore();
});
});
@@ -354,7 +334,6 @@ describe('AnalyticsProvider test', () => {
test('happy case', async () => {
const analytics = new AnalyticsProvider();
analytics.configure(options);
- const spyon = jest.spyOn(PinpointClient.prototype, 'send');
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
@@ -363,40 +342,40 @@ describe('AnalyticsProvider test', () => {
const params = { event: { name: '_session.start', immediate: true } };
await analytics.record(params, { resolve, reject });
- expect(spyon.mock.calls[0][0].input).toEqual({
- ApplicationId: 'appId',
- EventsRequest: {
- BatchItem: {
- endpointId: {
- Endpoint: {},
- Events: {
- uuid: {
- Attributes: undefined,
- EventType: '_session.start',
- Metrics: undefined,
- Session: {
- Id: 'uuid',
- StartTimestamp: 'isoString',
+ expect(mockPutEvents).toBeCalledWith(
+ { credentials, region: 'region' },
+ {
+ ApplicationId: 'appId',
+ EventsRequest: {
+ BatchItem: {
+ endpointId: {
+ Endpoint: {},
+ Events: {
+ uuid: {
+ Attributes: undefined,
+ EventType: '_session.start',
+ Metrics: undefined,
+ Session: {
+ Id: 'uuid',
+ StartTimestamp: 'isoString',
+ },
+ Timestamp: 'isoString',
},
- Timestamp: 'isoString',
},
},
},
},
- },
- });
+ }
+ );
expect(resolve).toBeCalled();
- spyon.mockRestore();
});
test('session start error', async () => {
const analytics = new AnalyticsProvider();
analytics.configure(options);
- const spyon = jest
- .spyOn(PinpointClient.prototype, 'send')
- .mockImplementationOnce(() => {
- throw 'data';
- });
+ mockPutEvents.mockImplementation(() => {
+ throw 'data';
+ });
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
@@ -407,7 +386,6 @@ describe('AnalyticsProvider test', () => {
await analytics.record(params, { resolve, reject });
expect(resolve).not.toBeCalled();
expect(reject).toBeCalled();
- spyon.mockRestore();
});
});
@@ -454,17 +432,14 @@ describe('AnalyticsProvider test', () => {
expect(spyon).toBeCalledWith(expectedUrl, expectedData);
expect(resolve).toBeCalled();
- spyon.mockRestore();
});
test('session stop error', async () => {
const analytics = new AnalyticsProvider();
analytics.configure(options);
- const spyon = jest
- .spyOn(PinpointClient.prototype, 'send')
- .mockImplementationOnce(async () => {
- throw 'data';
- });
+ mockPutEvents.mockImplementation(async () => {
+ throw 'data';
+ });
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
@@ -474,7 +449,6 @@ describe('AnalyticsProvider test', () => {
await analytics.record(params, { resolve, reject });
expect(reject).toBeCalled();
- spyon.mockRestore();
});
});
@@ -482,11 +456,6 @@ describe('AnalyticsProvider test', () => {
test('happy case with default client info', async () => {
const analytics = new AnalyticsProvider();
analytics.configure(options);
- const spyon = jest
- .spyOn(PinpointClient.prototype, 'send')
- .mockImplementationOnce(async params => {
- return 'data';
- });
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
@@ -494,41 +463,38 @@ describe('AnalyticsProvider test', () => {
const params = { event: { name: '_update_endpoint', immediate: true } };
await analytics.record(params, { resolve, reject });
- expect(spyon.mock.calls[0][0].input).toEqual({
- ApplicationId: 'appId',
- EndpointId: 'endpointId',
- EndpointRequest: {
- Attributes: {},
- ChannelType: undefined,
- Demographic: {
- AppVersion: 'clientInfoAppVersion',
- Make: 'clientInfoMake',
- Model: 'clientInfoModel',
- ModelVersion: 'clientInfoVersion',
- Platform: 'clientInfoPlatform',
- },
- EffectiveDate: 'isoString',
- Location: {},
- Metrics: {},
- RequestId: 'uuid',
- User: {
- UserAttributes: {},
- UserId: 'identityId',
+ expect(mockUpdateEndpoint).toBeCalledWith(
+ { credentials, region: 'region' },
+ {
+ ApplicationId: 'appId',
+ EndpointId: 'endpointId',
+ EndpointRequest: {
+ Attributes: {},
+ ChannelType: undefined,
+ Demographic: {
+ AppVersion: 'clientInfoAppVersion',
+ Make: 'clientInfoMake',
+ Model: 'clientInfoModel',
+ ModelVersion: 'clientInfoVersion',
+ Platform: 'clientInfoPlatform',
+ },
+ EffectiveDate: 'isoString',
+ Location: {},
+ Metrics: {},
+ RequestId: 'uuid',
+ User: {
+ UserAttributes: {},
+ UserId: 'identityId',
+ },
},
- },
- });
+ }
+ );
expect(resolve).toBeCalled();
- spyon.mockRestore();
});
test('happy case with client context provided', async () => {
const analytics = new AnalyticsProvider();
analytics.configure(optionsWithClientContext);
- const spyon = jest
- .spyOn(PinpointClient.prototype, 'send')
- .mockImplementationOnce(async params => {
- return 'data';
- });
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
@@ -536,43 +502,39 @@ describe('AnalyticsProvider test', () => {
const params = { event: { name: '_update_endpoint', immediate: true } };
await analytics.record(params, { resolve, reject });
- expect(spyon.mock.calls[0][0].input).toEqual({
- ApplicationId: 'appId',
- EndpointId: 'endpointId',
- EndpointRequest: {
- Attributes: {},
- ChannelType: undefined,
- Demographic: {
- AppVersion: 'clientInfoAppVersion',
- Locale: 'locale',
- Make: 'make',
- Model: 'model',
- ModelVersion: 'clientInfoVersion',
- Platform: 'platform',
- PlatformVersion: 'platformVersion',
- },
- EffectiveDate: 'isoString',
- Location: {},
- Metrics: {},
- RequestId: 'uuid',
- User: {
- UserAttributes: {},
- UserId: 'identityId',
+ expect(mockUpdateEndpoint).toBeCalledWith(
+ { credentials, region: 'region' },
+ {
+ ApplicationId: 'appId',
+ EndpointId: 'endpointId',
+ EndpointRequest: {
+ Attributes: {},
+ ChannelType: undefined,
+ Demographic: {
+ AppVersion: 'clientInfoAppVersion',
+ Locale: 'locale',
+ Make: 'make',
+ Model: 'model',
+ ModelVersion: 'clientInfoVersion',
+ Platform: 'platform',
+ PlatformVersion: 'platformVersion',
+ },
+ EffectiveDate: 'isoString',
+ Location: {},
+ Metrics: {},
+ RequestId: 'uuid',
+ User: {
+ UserAttributes: {},
+ UserId: 'identityId',
+ },
},
- },
- });
-
- spyon.mockRestore();
+ }
+ );
});
test('happy case with default enpoint configure provided', async () => {
const analytics = new AnalyticsProvider();
analytics.configure(optionsWithDefaultEndpointConfigure);
- const spyon = jest
- .spyOn(PinpointClient.prototype, 'send')
- .mockImplementationOnce(async params => {
- return 'data';
- });
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
@@ -581,57 +543,53 @@ describe('AnalyticsProvider test', () => {
const params = { event: { name: '_update_endpoint', immediate: true } };
await analytics.record(params, { resolve, reject });
- expect(spyon.mock.calls[0][0].input).toEqual({
- ApplicationId: 'appId',
- EndpointId: 'endpointId',
- EndpointRequest: {
- Address: 'default',
- Attributes: {
- hobbies: ['default'],
- },
- ChannelType: 'default',
- Demographic: {
- AppVersion: 'default',
- Locale: 'default',
- Make: 'default',
- Model: 'default',
- ModelVersion: 'default',
- Platform: 'default',
- PlatformVersion: 'default',
- Timezone: 'default',
- },
- EffectiveDate: 'isoString',
- Location: {
- City: 'default',
- Country: 'default',
- Latitude: 0,
- Longitude: 0,
- PostalCode: 'default',
- Region: 'default',
- },
- Metrics: {},
- OptOut: 'default',
- RequestId: 'uuid',
- User: {
- UserAttributes: {
- interests: ['default'],
+ expect(mockUpdateEndpoint).toBeCalledWith(
+ { credentials, region: 'region' },
+ {
+ ApplicationId: 'appId',
+ EndpointId: 'endpointId',
+ EndpointRequest: {
+ Address: 'default',
+ Attributes: {
+ hobbies: ['default'],
+ },
+ ChannelType: 'default',
+ Demographic: {
+ AppVersion: 'default',
+ Locale: 'default',
+ Make: 'default',
+ Model: 'default',
+ ModelVersion: 'default',
+ Platform: 'default',
+ PlatformVersion: 'default',
+ Timezone: 'default',
+ },
+ EffectiveDate: 'isoString',
+ Location: {
+ City: 'default',
+ Country: 'default',
+ Latitude: 0,
+ Longitude: 0,
+ PostalCode: 'default',
+ Region: 'default',
+ },
+ Metrics: {},
+ OptOut: 'default',
+ RequestId: 'uuid',
+ User: {
+ UserAttributes: {
+ interests: ['default'],
+ },
+ UserId: 'default',
},
- UserId: 'default',
},
- },
- });
-
- spyon.mockRestore();
+ }
+ );
});
test('happy case with specified enpoint configure provided', async () => {
const analytics = new AnalyticsProvider();
analytics.configure(optionsWithDefaultEndpointConfigure);
- const spyon = jest
- .spyOn(PinpointClient.prototype, 'send')
- .mockImplementationOnce(async params => {
- return 'data';
- });
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
@@ -646,47 +604,48 @@ describe('AnalyticsProvider test', () => {
};
await analytics.record(params, { resolve, reject });
- expect(spyon.mock.calls[0][0].input).toEqual({
- ApplicationId: 'appId',
- EndpointId: 'endpointId',
- EndpointRequest: {
- Address: 'configured',
- Attributes: {
- hobbies: ['configured'],
- },
- ChannelType: 'configured',
- Demographic: {
- AppVersion: 'configured',
- Locale: 'configured',
- Make: 'configured',
- Model: 'configured',
- ModelVersion: 'configured',
- Platform: 'configured',
- PlatformVersion: 'configured',
- Timezone: 'configured',
- },
- EffectiveDate: 'isoString',
- Location: {
- City: 'configured',
- Country: 'configured',
- Latitude: 0,
- Longitude: 0,
- PostalCode: 'configured',
- Region: 'configured',
- },
- Metrics: {},
- OptOut: 'configured',
- RequestId: 'uuid',
- User: {
- UserAttributes: {
- interests: ['configured'],
+ expect(mockUpdateEndpoint).toBeCalledWith(
+ { credentials, region: 'region' },
+ {
+ ApplicationId: 'appId',
+ EndpointId: 'endpointId',
+ EndpointRequest: {
+ Address: 'configured',
+ Attributes: {
+ hobbies: ['configured'],
+ },
+ ChannelType: 'configured',
+ Demographic: {
+ AppVersion: 'configured',
+ Locale: 'configured',
+ Make: 'configured',
+ Model: 'configured',
+ ModelVersion: 'configured',
+ Platform: 'configured',
+ PlatformVersion: 'configured',
+ Timezone: 'configured',
+ },
+ EffectiveDate: 'isoString',
+ Location: {
+ City: 'configured',
+ Country: 'configured',
+ Latitude: 0,
+ Longitude: 0,
+ PostalCode: 'configured',
+ Region: 'configured',
+ },
+ Metrics: {},
+ OptOut: 'configured',
+ RequestId: 'uuid',
+ User: {
+ UserAttributes: {
+ interests: ['configured'],
+ },
+ UserId: 'configured',
},
- UserId: 'configured',
},
- },
- });
-
- spyon.mockRestore();
+ }
+ );
});
test('error case', async () => {
@@ -694,11 +653,9 @@ describe('AnalyticsProvider test', () => {
const mockError = { message: 'error' };
analytics.configure(options);
- const spyon = jest
- .spyOn(PinpointClient.prototype, 'send')
- .mockImplementationOnce(async params => {
- throw { message: 'error' };
- });
+ mockUpdateEndpoint.mockImplementation(async params => {
+ throw { message: 'error' };
+ });
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
@@ -708,7 +665,6 @@ describe('AnalyticsProvider test', () => {
await analytics.record(params, { resolve, reject });
expect(reject).toBeCalledWith(mockError);
- spyon.mockRestore();
});
test('BAD_REQUEST_CODE without message rejects error', async () => {
@@ -716,11 +672,9 @@ describe('AnalyticsProvider test', () => {
const mockError = { debug: 'error', statusCode: 400 };
analytics.configure(options);
- const spyon = jest
- .spyOn(PinpointClient.prototype, 'send')
- .mockImplementationOnce(async params => {
- throw mockError;
- });
+ mockUpdateEndpoint.mockImplementation(async params => {
+ throw mockError;
+ });
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
@@ -730,7 +684,6 @@ describe('AnalyticsProvider test', () => {
await analytics.record(params, { resolve, reject });
expect(reject).toBeCalledWith(mockError);
- spyon.mockRestore();
});
test('Exceeded maximum endpoint per user count', async () => {
@@ -744,12 +697,9 @@ describe('AnalyticsProvider test', () => {
analytics.configure(options);
- const spyonUpdateEndpoint = jest
- .spyOn(PinpointClient.prototype, 'send')
- // Reject with error the first time we execute updateEndpoint
- .mockImplementationOnce(async params => {
- throw mockExceededMaxError;
- });
+ mockUpdateEndpoint.mockImplementation(async params => {
+ throw mockExceededMaxError;
+ });
jest
.spyOn(Credentials, 'get')
@@ -759,9 +709,7 @@ describe('AnalyticsProvider test', () => {
await analytics.record(params, { resolve, reject });
- expect(spyonUpdateEndpoint).toHaveBeenCalledTimes(1);
-
- spyonUpdateEndpoint.mockRestore();
+ expect(mockUpdateEndpoint).toHaveBeenCalledTimes(1);
});
});
});
diff --git a/packages/analytics/__tests__/Providers/EventBuffer.test.ts b/packages/analytics/__tests__/Providers/EventBuffer.test.ts
index f80bf7a04f1..894ee0509ae 100644
--- a/packages/analytics/__tests__/Providers/EventBuffer.test.ts
+++ b/packages/analytics/__tests__/Providers/EventBuffer.test.ts
@@ -41,13 +41,13 @@ describe('EventBuffer', () => {
});
test('can be constructed', () => {
- const buffer = new EventBuffer({}, DEFAULT_CONFIG);
+ const buffer = new EventBuffer(DEFAULT_CONFIG);
expect(buffer).toBeDefined();
});
test('does not allow buffer size to be exceeded', () => {
const config = { ...DEFAULT_CONFIG, bufferSize: 1 };
- const buffer = new EventBuffer({}, config);
+ const buffer = new EventBuffer(config);
buffer.push(EVENT_OBJECT);
buffer.push(EVENT_OBJECT);
expect(EVENT_OBJECT.handlers.reject).toBeCalledWith(
diff --git a/packages/analytics/package.json b/packages/analytics/package.json
index dcc71fa6440..4fa88d2be62 100644
--- a/packages/analytics/package.json
+++ b/packages/analytics/package.json
@@ -53,24 +53,26 @@
"@aws-sdk/client-firehose": "3.6.1",
"@aws-sdk/client-kinesis": "3.6.1",
"@aws-sdk/client-personalize-events": "3.6.1",
- "@aws-sdk/client-pinpoint": "3.6.1",
"@aws-sdk/util-utf8-browser": "3.6.1",
"lodash": "^4.17.20",
"tslib": "^1.8.0",
"uuid": "^3.2.1"
},
+ "devDependencies": {
+ "@aws-sdk/client-pinpoint": "3.186.1"
+ },
"size-limit": [
{
"name": "Analytics (Pinpoint)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Analytics, AWSPinpointProvider }",
- "limit": "54.7 kB"
+ "limit": "29.45 kB"
},
{
"name": "Analytics (Kinesis)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Analytics, AWSKinesisProvider }",
- "limit": "68.5 kB"
+ "limit": "57.91 kB"
}
],
"jest": {
diff --git a/packages/analytics/src/Providers/AWSPinpointProvider.ts b/packages/analytics/src/Providers/AWSPinpointProvider.ts
index 10741e2e64a..70c157239db 100644
--- a/packages/analytics/src/Providers/AWSPinpointProvider.ts
+++ b/packages/analytics/src/Providers/AWSPinpointProvider.ts
@@ -1,24 +1,24 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
+import { Cache } from '@aws-amplify/cache';
import {
ConsoleLogger as Logger,
ClientDevice,
Credentials,
Signer,
Hub,
- getAmplifyUserAgent,
transferKeyToLowerCase,
transferKeyToUpperCase,
} from '@aws-amplify/core';
import {
- EventsBatch,
- PinpointClient,
- PutEventsCommand,
- PutEventsCommandInput,
- UpdateEndpointCommand,
-} from '@aws-sdk/client-pinpoint';
-import { Cache } from '@aws-amplify/cache';
+ putEvents,
+ PutEventsInput,
+ PutEventsOutput,
+ updateEndpoint,
+ UpdateEndpointInput,
+ UpdateEndpointOutput,
+} from '@aws-amplify/core/lib-esm/AwsClients/Pinpoint'; // TODO: convert to subpath import from core
import {
AnalyticsProvider,
@@ -29,7 +29,7 @@ import {
EndpointFailureData,
} from '../types';
import { v1 as uuid } from 'uuid';
-import EventsBuffer from './EventBuffer';
+import EventBuffer from './EventBuffer';
const AMPLIFY_SYMBOL = (
typeof Symbol !== 'undefined' && typeof Symbol.for === 'function'
@@ -68,10 +68,9 @@ export class AWSPinpointProvider implements AnalyticsProvider {
static providerName = 'AWSPinpoint';
private _config;
- private pinpointClient;
private _sessionId;
private _sessionStartTimestamp;
- private _buffer: EventsBuffer;
+ private _buffer: EventBuffer | null;
private _endpointBuffer: EndpointBuffer;
private _clientInfo;
private _endpointGenerating = true;
@@ -152,7 +151,7 @@ export class AWSPinpointProvider implements AnalyticsProvider {
);
}
- this._initClients(credentials);
+ this._init(credentials);
const timestamp = new Date().getTime();
// attach the session and eventId
@@ -194,7 +193,7 @@ export class AWSPinpointProvider implements AnalyticsProvider {
return;
}
- this._buffer && this._buffer.push({ params, handlers });
+ this._buffer?.push({ params, handlers });
}
private _generateSession(params) {
@@ -249,34 +248,31 @@ export class AWSPinpointProvider implements AnalyticsProvider {
}
}
- private _generateBatchItemContext(params) {
+ private _generateBatchItemContext(params): PutEventsInput {
const { event, timestamp, config } = params;
const { name, attributes, metrics, eventId, session } = event;
const { appId, endpointId } = config;
-
const endpointContext = {};
- const eventParams: PutEventsCommandInput = {
+ return {
ApplicationId: appId,
EventsRequest: {
- BatchItem: {},
- },
- };
-
- const endpointObj: EventsBatch = {} as EventsBatch;
- endpointObj.Endpoint = endpointContext;
- endpointObj.Events = {
- [eventId]: {
- EventType: name,
- Timestamp: new Date(timestamp).toISOString(),
- Attributes: attributes,
- Metrics: metrics,
- Session: session,
+ BatchItem: {
+ [endpointId]: {
+ Endpoint: endpointContext,
+ Events: {
+ [eventId]: {
+ EventType: name,
+ Timestamp: new Date(timestamp).toISOString(),
+ Attributes: attributes,
+ Metrics: metrics,
+ Session: session,
+ },
+ },
+ },
+ },
},
};
- eventParams.EventsRequest.BatchItem[endpointId] = endpointObj;
-
- return eventParams;
}
private async _pinpointPutEvents(params, handlers) {
@@ -285,33 +281,30 @@ export class AWSPinpointProvider implements AnalyticsProvider {
config: { endpointId },
} = params;
const eventParams = this._generateBatchItemContext(params);
- const command: PutEventsCommand = new PutEventsCommand(eventParams);
try {
- const data = await this.pinpointClient.send(command);
- const {
- EventsResponse: {
- Results: {
- [endpointId]: {
- EventsItemResponse: {
- [eventId]: { StatusCode, Message },
- },
- },
- },
- },
- } = data;
- if (ACCEPTED_CODES.includes(StatusCode)) {
+ const { credentials, region } = this._config;
+ const data: PutEventsOutput = await putEvents(
+ { credentials, region },
+ eventParams
+ );
+
+ const { StatusCode, Message } =
+ data.EventsResponse?.Results?.[endpointId]?.EventsItemResponse?.[
+ eventId
+ ] ?? {};
+
+ if (StatusCode && ACCEPTED_CODES.includes(StatusCode)) {
logger.debug('record event success. ', data);
return handlers.resolve(data);
+ } else if (StatusCode && RETRYABLE_CODES.includes(StatusCode)) {
+ // TODO: v6 integrate retry to the service handler retryDecider
+ this._retry(params, handlers);
} else {
- if (RETRYABLE_CODES.includes(StatusCode)) {
- this._retry(params, handlers);
- } else {
- logger.error(
- `Event ${eventId} is not accepted, the error is ${Message}`
- );
- return handlers.reject(data);
- }
+ logger.error(
+ `Event ${eventId} is not accepted, the error is ${Message}`
+ );
+ return handlers.reject(data);
}
} catch (err) {
this._eventError(err);
@@ -319,7 +312,7 @@ export class AWSPinpointProvider implements AnalyticsProvider {
}
}
- private _pinpointSendStopSession(params, handlers): Promise {
+ private _pinpointSendStopSession(params, handlers): Promise | void {
if (!BEACON_SUPPORTED) {
this._pinpointPutEvents(params, handlers);
return;
@@ -348,12 +341,7 @@ export class AWSPinpointProvider implements AnalyticsProvider {
const serviceInfo = { region, service: MOBILE_SERVICE_NAME };
- const requestUrl: string = Signer.signUrl(
- request,
- accessInfo,
- serviceInfo,
- null
- );
+ const requestUrl: string = Signer.signUrl(request, accessInfo, serviceInfo);
const success: boolean = navigator.sendBeacon(requestUrl, body);
@@ -393,18 +381,18 @@ export class AWSPinpointProvider implements AnalyticsProvider {
['attributes', 'userAttributes', 'Attributes', 'UserAttributes']
)
);
- const update_params = {
+ const update_params: UpdateEndpointInput = {
ApplicationId: appId,
EndpointId: endpointId,
EndpointRequest: request,
};
try {
- const command: UpdateEndpointCommand = new UpdateEndpointCommand(
+ const { credentials, region } = this._config;
+ const data: UpdateEndpointOutput = await updateEndpoint(
+ { credentials, region },
update_params
);
- const data = await this.pinpointClient.send(command);
-
logger.debug('updateEndpoint success', data);
this._endpointGenerating = false;
this._resumeBuffer();
@@ -492,13 +480,12 @@ export class AWSPinpointProvider implements AnalyticsProvider {
/**
* @private
* @param config
- * Init the clients
+ * Configure credentials and init buffer
*/
- private async _initClients(credentials) {
- logger.debug('init clients');
+ private async _init(credentials) {
+ logger.debug('init provider');
if (
- this.pinpointClient &&
this._config.credentials &&
this._config.credentials.sessionToken === credentials.sessionToken &&
this._config.credentials.identityId === credentials.identityId
@@ -512,43 +499,17 @@ export class AWSPinpointProvider implements AnalyticsProvider {
: null;
this._config.credentials = credentials;
- const { region } = this._config;
- logger.debug('init clients with credentials', credentials);
- this.pinpointClient = new PinpointClient({
- region,
- credentials,
- customUserAgent: getAmplifyUserAgent(),
- });
-
- // TODO: remove this middleware once a long term fix is implemented by aws-sdk-js team.
- this.pinpointClient.middlewareStack.addRelativeTo(
- next => args => {
- delete args.request.headers['amz-sdk-invocation-id'];
- delete args.request.headers['amz-sdk-request'];
- return next(args);
- },
- {
- step: 'finalizeRequest',
- relation: 'after',
- toMiddleware: 'retryMiddleware',
- }
- );
- if (this._bufferExists() && identityId === credentials.identityId) {
- // if the identity has remained the same, pass the updated client to the buffer
- this._updateBufferClient();
- } else {
- // otherwise flush the buffer and instantiate a new one
+ if (!this._bufferExists() || identityId !== credentials.identityId) {
+ // if the identity has changed, flush the buffer and instantiate a new one
// this will cause the old buffer to send any remaining events
// with the old credentials and then stop looping and shortly thereafter get picked up by GC
this._initBuffer();
}
-
- this._customizePinpointClientReq();
}
private _bufferExists() {
- return this._buffer && this._buffer instanceof EventsBuffer;
+ return this._buffer && this._buffer instanceof EventBuffer;
}
private _initBuffer() {
@@ -556,7 +517,7 @@ export class AWSPinpointProvider implements AnalyticsProvider {
this._flushBuffer();
}
- this._buffer = new EventsBuffer(this.pinpointClient, this._config);
+ this._buffer = new EventBuffer(this._config);
// if the first endpoint update hasn't yet resolved pause the buffer to
// prevent race conditions. It will be resumed as soon as that request succeeds
@@ -565,36 +526,19 @@ export class AWSPinpointProvider implements AnalyticsProvider {
}
}
- private _updateBufferClient() {
- if (this._bufferExists()) {
- this._buffer.updateClient(this.pinpointClient);
- }
- }
-
private _flushBuffer() {
if (this._bufferExists()) {
- this._buffer.flush();
+ this._buffer?.flush();
this._buffer = null;
}
}
private _resumeBuffer() {
if (this._bufferExists()) {
- this._buffer.resume();
+ this._buffer?.resume();
}
}
- private _customizePinpointClientReq() {
- // TODO FIXME: Find a middleware to do this with AWS V3 SDK
- // if (Platform.isReactNative) {
- // this.pinpointClient.customizeRequests(request => {
- // request.on('build', req => {
- // req.httpRequest.headers['user-agent'] = Platform.userAgent;
- // });
- // });
- // }
- }
-
private async _getEndpointId(cacheKey) {
// try to get from cache
let endpointId = await Cache.getItem(cacheKey);
diff --git a/packages/analytics/src/Providers/EventBuffer.ts b/packages/analytics/src/Providers/EventBuffer.ts
index 8b9363525f7..4188a72cf2a 100644
--- a/packages/analytics/src/Providers/EventBuffer.ts
+++ b/packages/analytics/src/Providers/EventBuffer.ts
@@ -1,15 +1,10 @@
import { ConsoleLogger as Logger } from '@aws-amplify/core';
-
-import {
- PutEventsResponse,
- EventBuffer,
- EventObject,
- EventMap,
-} from '../types';
import {
- PutEventsCommand,
- PutEventsCommandOutput,
-} from '@aws-sdk/client-pinpoint';
+ putEvents,
+ PutEventsInput,
+ PutEventsOutput,
+} from '@aws-amplify/core/lib-esm/AwsClients/Pinpoint'; // TODO: convert to subpath import from core
+import { EventBuffer, EventObject, EventMap } from '../types';
import { isAppInForeground } from '../utils/AppUtils';
const logger = new Logger('EventsBuffer');
@@ -25,16 +20,14 @@ type EventsBufferConfig = {
export default class EventsBuffer {
private _config;
- private _client;
private _interval;
private _buffer: EventBuffer;
private _pause = false;
private _flush = false;
- constructor(client, config: EventsBufferConfig) {
+ constructor(config: EventsBufferConfig) {
logger.debug('Instantiating buffer with config:', config);
this._buffer = [];
- this._client = client;
this._config = config;
this._sendBatch = this._sendBatch.bind(this);
@@ -64,10 +57,6 @@ export default class EventsBuffer {
this._pause = false;
}
- public updateClient(client) {
- this._client = client;
- }
-
public flush() {
this._flush = true;
}
@@ -110,15 +99,18 @@ export default class EventsBuffer {
const batchEventParams = this._generateBatchEventParams(eventMap);
try {
- const command: PutEventsCommand = new PutEventsCommand(batchEventParams);
- const data: PutEventsCommandOutput = await this._client.send(command);
+ const { credentials, region } = this._config;
+ const data: PutEventsOutput = await putEvents(
+ { credentials, region },
+ batchEventParams
+ );
this._processPutEventsSuccessResponse(data, eventMap);
} catch (err) {
return this._handlePutEventsFailure(err, eventMap);
}
}
- private _generateBatchEventParams(eventMap: EventMap) {
+ private _generateBatchEventParams(eventMap: EventMap): PutEventsInput {
const batchEventParams = {
ApplicationId: '',
EventsRequest: {
@@ -167,50 +159,53 @@ export default class EventsBuffer {
}
private _processPutEventsSuccessResponse(
- data: PutEventsResponse,
+ data: PutEventsOutput,
eventMap: EventMap
) {
- const { Results } = data.EventsResponse;
+ const { Results = {} } = data.EventsResponse ?? {};
const retryableEvents: EventObject[] = [];
Object.entries(Results).forEach(([endpointId, endpointValues]) => {
- const responses = endpointValues.EventsItemResponse;
-
- Object.entries(responses).forEach(
- ([eventId, { StatusCode, Message }]) => {
- const eventObject = eventMap[eventId];
-
- // manually crafting handlers response to keep API consistant
- const response = {
- EventsResponse: {
- Results: {
- [endpointId]: {
- EventsItemResponse: {
- [eventId]: { StatusCode, Message },
- },
+ const responses = endpointValues.EventsItemResponse ?? {};
+
+ Object.entries(responses).forEach(([eventId, eventValues]) => {
+ const eventObject = eventMap[eventId];
+ if (!eventObject) {
+ return;
+ }
+
+ const { StatusCode, Message } = eventValues ?? {};
+
+ // manually crafting handlers response to keep API consistant
+ const response = {
+ EventsResponse: {
+ Results: {
+ [endpointId]: {
+ EventsItemResponse: {
+ [eventId]: { StatusCode, Message },
},
},
},
- };
+ },
+ };
- if (ACCEPTED_CODES.includes(StatusCode)) {
- eventObject.handlers.resolve(response);
- return;
- }
+ if (StatusCode && ACCEPTED_CODES.includes(StatusCode)) {
+ eventObject.handlers.resolve(response);
+ return;
+ }
- if (RETRYABLE_CODES.includes(StatusCode)) {
- retryableEvents.push(eventObject);
- return;
- }
+ if (StatusCode && RETRYABLE_CODES.includes(StatusCode)) {
+ retryableEvents.push(eventObject);
+ return;
+ }
- const { name } = eventObject.params.event;
+ const { name } = eventObject.params.event;
- logger.error(
- `event ${eventId} : ${name} failed with error: ${Message}`
- );
- return eventObject.handlers.reject(response);
- }
- );
+ logger.error(
+ `event ${eventId} : ${name} failed with error: ${Message}`
+ );
+ return eventObject.handlers.reject(response);
+ });
});
if (retryableEvents.length) {
diff --git a/packages/notifications/__mocks__/data.ts b/packages/notifications/__mocks__/data.ts
index 4cbcce6a5f0..a6ddd2c8b40 100644
--- a/packages/notifications/__mocks__/data.ts
+++ b/packages/notifications/__mocks__/data.ts
@@ -1,12 +1,8 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-import {
- Alignment,
- ButtonAction,
- DimensionType,
+import type {
Event,
- FilterType,
InAppMessageCampaign as PinpointInAppMessage,
} from '@aws-sdk/client-pinpoint';
import { InAppMessage, InAppMessagingEvent } from '../src/InAppMessaging';
@@ -139,12 +135,12 @@ export const pinpointInAppMessage: PinpointInAppMessage = {
{
BackgroundColor: '#FFFF88',
BodyConfig: {
- Alignment: Alignment.LEFT,
+ Alignment: 'LEFT',
Body: 'Body content',
TextColor: '#FF8888',
},
HeaderConfig: {
- Alignment: Alignment.CENTER,
+ Alignment: 'CENTER',
Header: 'Header content',
TextColor: '#88FF88',
},
@@ -153,7 +149,7 @@ export const pinpointInAppMessage: PinpointInAppMessage = {
DefaultConfig: {
BackgroundColor: '#8888FF',
BorderRadius: 4,
- ButtonAction: ButtonAction.CLOSE,
+ ButtonAction: 'CLOSE',
Link: undefined,
Text: 'Close button',
TextColor: '#FF88FF',
@@ -163,7 +159,7 @@ export const pinpointInAppMessage: PinpointInAppMessage = {
DefaultConfig: {
BackgroundColor: '#88FFFF',
BorderRadius: 4,
- ButtonAction: ButtonAction.LINK,
+ ButtonAction: 'LINK',
Link: 'http://link.fakeurl',
Text: 'Link button',
TextColor: '#FFFFFF',
@@ -178,11 +174,11 @@ export const pinpointInAppMessage: PinpointInAppMessage = {
Schedule: {
EndDate: '2021-01-01T00:00:00Z',
EventFilter: {
- FilterType: FilterType.SYSTEM,
+ FilterType: 'SYSTEM',
Dimensions: {
Attributes: {},
EventType: {
- DimensionType: DimensionType.INCLUSIVE,
+ DimensionType: 'INCLUSIVE',
Values: ['clicked', 'swiped'],
},
Metrics: {},
diff --git a/packages/notifications/__tests__/InAppMessaging/Providers/AWSPinpointProvider/index.test.ts b/packages/notifications/__tests__/InAppMessaging/Providers/AWSPinpointProvider/index.test.ts
index 04efb6c90c5..ddc04e0c50c 100644
--- a/packages/notifications/__tests__/InAppMessaging/Providers/AWSPinpointProvider/index.test.ts
+++ b/packages/notifications/__tests__/InAppMessaging/Providers/AWSPinpointProvider/index.test.ts
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { Credentials, StorageHelper } from '@aws-amplify/core';
-import { PinpointClient } from '@aws-sdk/client-pinpoint';
+import { getInAppMessages } from '@aws-amplify/core/lib-esm/AwsClients/Pinpoint/getInAppMessages';
import cloneDeep from 'lodash/cloneDeep';
import { addEventListener } from '../../../../src/common/eventListeners';
@@ -29,7 +29,8 @@ import {
import { mockStorage } from '../../../../__mocks__/mocks';
jest.mock('@aws-amplify/core');
-jest.mock('@aws-sdk/client-pinpoint');
+jest.mock('@aws-amplify/core/lib-esm/AwsClients/Pinpoint/getInAppMessages');
+jest.mock('@aws-amplify/core/lib-esm/AwsClients/Pinpoint/updateEndpoint');
jest.mock('../../../../src/common/eventListeners');
jest.mock('../../../../src/InAppMessaging/Providers/AWSPinpointProvider/utils');
jest.mock(
@@ -45,12 +46,12 @@ jest.mock(
const getStorageSpy = jest.spyOn(StorageHelper.prototype, 'getStorage');
const credentialsGetSpy = jest.spyOn(Credentials, 'get');
const credentialsShearSpy = jest.spyOn(Credentials, 'shear');
-const clientSendSpy = jest.spyOn(PinpointClient.prototype, 'send') as jest.Mock;
const mockAddEventListener = addEventListener as jest.Mock;
const mockIsBeforeEndDate = isBeforeEndDate as jest.Mock;
const mockMatchesAttributes = matchesAttributes as jest.Mock;
const mockMatchesEventType = matchesEventType as jest.Mock;
const mockMatchesMetrics = matchesMetrics as jest.Mock;
+const mockGetInAppMessages = getInAppMessages as jest.Mock;
describe('AWSPinpoint InAppMessaging Provider', () => {
let provider: AWSPinpointProvider;
@@ -85,7 +86,7 @@ describe('AWSPinpoint InAppMessaging Provider', () => {
});
test('gets in-app messages from Pinpoint', async () => {
- clientSendSpy.mockResolvedValueOnce(null).mockResolvedValueOnce({
+ mockGetInAppMessages.mockResolvedValueOnce({
InAppMessagesResponse: {
InAppMessageCampaigns: messages,
},
@@ -95,7 +96,7 @@ describe('AWSPinpoint InAppMessaging Provider', () => {
});
test('throws an error on client failure', async () => {
- clientSendSpy.mockImplementationOnce(() => {
+ mockGetInAppMessages.mockImplementationOnce(() => {
throw new Error();
});
diff --git a/packages/notifications/__tests__/InAppMessaging/Providers/AWSPinpointProvider/utils.test.ts b/packages/notifications/__tests__/InAppMessaging/Providers/AWSPinpointProvider/utils.test.ts
index 0bd9bb31166..43ec20a04fa 100644
--- a/packages/notifications/__tests__/InAppMessaging/Providers/AWSPinpointProvider/utils.test.ts
+++ b/packages/notifications/__tests__/InAppMessaging/Providers/AWSPinpointProvider/utils.test.ts
@@ -1,7 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-import { InAppMessageCampaign as PinpointInAppMessage } from '@aws-sdk/client-pinpoint';
+import type { InAppMessageCampaign as PinpointInAppMessage } from '@aws-sdk/client-pinpoint';
import { Amplify, ConsoleLogger, Hub } from '@aws-amplify/core';
import cloneDeep from 'lodash/cloneDeep';
diff --git a/packages/notifications/__tests__/PushNotification/Providers/AWSPinpointProvider/index.test.ts b/packages/notifications/__tests__/PushNotification/Providers/AWSPinpointProvider/index.test.ts
index c1f8077d8dc..00eb4091403 100644
--- a/packages/notifications/__tests__/PushNotification/Providers/AWSPinpointProvider/index.test.ts
+++ b/packages/notifications/__tests__/PushNotification/Providers/AWSPinpointProvider/index.test.ts
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { Credentials, StorageHelper } from '@aws-amplify/core';
-import { PinpointClient } from '@aws-sdk/client-pinpoint';
+import { updateEndpoint } from '@aws-amplify/core/lib-esm/AwsClients/Pinpoint/updateEndpoint';
import { addEventListener } from '../../../../src/common/eventListeners';
import { Platform } from '../../../../src/PushNotification/Platform';
@@ -24,7 +24,7 @@ import {
import { mockStorage } from '../../../../__mocks__/mocks';
jest.mock('@aws-amplify/core');
-jest.mock('@aws-sdk/client-pinpoint');
+jest.mock('@aws-amplify/core/lib-esm/AwsClients/Pinpoint/updateEndpoint');
jest.mock('../../../../src/common/eventListeners');
jest.mock('../../../../src/PushNotification/Platform');
jest.mock(
@@ -34,8 +34,8 @@ jest.mock(
const getStorageSpy = jest.spyOn(StorageHelper.prototype, 'getStorage');
const credentialsGetSpy = jest.spyOn(Credentials, 'get');
const credentialsShearSpy = jest.spyOn(Credentials, 'shear');
-const clientSendSpy = jest.spyOn(PinpointClient.prototype, 'send');
const mockAddEventListener = addEventListener as jest.Mock;
+const mockUpdateEndpoint = updateEndpoint as jest.Mock;
describe('AWSPinpoint InAppMessaging Provider', () => {
let provider: AWSPinpointProvider;
@@ -136,11 +136,11 @@ describe('AWSPinpoint InAppMessaging Provider', () => {
test('registers device with Pinpoint', async () => {
await provider.registerDevice(pushToken);
- expect(clientSendSpy).toBeCalled();
+ expect(mockUpdateEndpoint).toBeCalled();
});
test('throws an error on client failure', async () => {
- clientSendSpy.mockImplementationOnce(() => {
+ mockUpdateEndpoint.mockImplementationOnce(() => {
throw new Error();
});
diff --git a/packages/notifications/__tests__/common/AWSPinpointProviderCommon/index.test.ts b/packages/notifications/__tests__/common/AWSPinpointProviderCommon/index.test.ts
index 07d27fbfc0c..0d1e0a42605 100644
--- a/packages/notifications/__tests__/common/AWSPinpointProviderCommon/index.test.ts
+++ b/packages/notifications/__tests__/common/AWSPinpointProviderCommon/index.test.ts
@@ -2,7 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
import { Credentials, StorageHelper } from '@aws-amplify/core';
-import { PinpointClient } from '@aws-sdk/client-pinpoint';
+import {
+ putEvents,
+ updateEndpoint,
+} from '@aws-amplify/core/lib-esm/AwsClients/Pinpoint'; // TODO: convert to subpath import from core
import { AWSPinpointProviderCommon } from '../../../src/common';
@@ -17,13 +20,13 @@ import { mockLogger, mockStorage } from '../../../__mocks__/mocks';
import { NotificationsSubCategory } from '../../../src/types';
jest.mock('@aws-amplify/core');
-jest.mock('@aws-sdk/client-pinpoint');
+jest.mock('@aws-amplify/core/lib-esm/AwsClients/Pinpoint/putEvents');
+jest.mock('@aws-amplify/core/lib-esm/AwsClients/Pinpoint/updateEndpoint');
jest.mock('../../../src/common/eventListeners');
const SUB_CATEGORY = 'SubCategory';
const getStorageSpy = jest.spyOn(StorageHelper.prototype, 'getStorage');
-const clientSendSpy = jest.spyOn(PinpointClient.prototype, 'send');
class AWSPinpointProviderTest extends AWSPinpointProviderCommon {
getSubCategory() {
@@ -41,6 +44,8 @@ class AWSPinpointProviderTest extends AWSPinpointProviderCommon {
const credentialsGetSpy = jest.spyOn(Credentials, 'get');
const credentialsShearSpy = jest.spyOn(Credentials, 'shear');
+const mockPutEvents = putEvents as jest.Mock;
+const mockUpdateEndpoint = updateEndpoint as jest.Mock;
describe('AWSPinpoint Common Provider', () => {
let provider: AWSPinpointProviderTest;
@@ -113,7 +118,7 @@ describe('AWSPinpoint Common Provider', () => {
await provider.testRecordAnalyticsEvent(analyticsEvent);
expect(mockLogger.debug).toBeCalledWith('recording analytics event');
- expect(clientSendSpy).toBeCalled();
+ expect(mockPutEvents).toBeCalled();
});
test('throws an error if credentials are empty', async () => {
@@ -124,7 +129,7 @@ describe('AWSPinpoint Common Provider', () => {
).rejects.toThrow();
expect(mockLogger.debug).toBeCalledWith('no credentials found');
- expect(clientSendSpy).not.toBeCalled();
+ expect(mockPutEvents).not.toBeCalled();
});
test('throws an error on credentials get failure', async () => {
@@ -140,11 +145,11 @@ describe('AWSPinpoint Common Provider', () => {
expect.stringContaining('Error getting credentials'),
expect.any(Error)
);
- expect(clientSendSpy).not.toBeCalled();
+ expect(mockPutEvents).not.toBeCalled();
});
test('throws an error on client failure', async () => {
- clientSendSpy.mockImplementationOnce(() => {
+ mockPutEvents.mockImplementationOnce(() => {
throw new Error();
});
@@ -168,11 +173,11 @@ describe('AWSPinpoint Common Provider', () => {
await provider.identifyUser(userId, userInfo);
expect(mockLogger.debug).toBeCalledWith('updating endpoint');
- expect(clientSendSpy).toBeCalled();
+ expect(mockUpdateEndpoint).toBeCalled();
});
test('throws an error on client failure', async () => {
- clientSendSpy.mockImplementationOnce(() => {
+ mockUpdateEndpoint.mockImplementationOnce(() => {
throw new Error();
});
diff --git a/packages/notifications/package.json b/packages/notifications/package.json
index ca0e389e3fb..17cd8c9cfff 100644
--- a/packages/notifications/package.json
+++ b/packages/notifications/package.json
@@ -51,25 +51,25 @@
"dependencies": {
"@aws-amplify/cache": "5.0.25",
"@aws-amplify/core": "5.1.8",
- "@aws-sdk/client-pinpoint": "3.186.1",
"lodash": "^4.17.21",
"uuid": "^3.2.1"
},
+ "devDependencies": {
+ "@aws-amplify/rtn-push-notification": "1.0.0",
+ "@aws-sdk/client-pinpoint": "3.186.1"
+ },
"size-limit": [
{
"name": "Notifications (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Notifications }",
- "limit": "59.3 kB"
+ "limit": "28.75 kB"
},
{
"name": "Notifications (with Analytics)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Notifications, Analytics }",
- "limit": "59.3 kB"
+ "limit": "28.76 kB"
}
- ],
- "devDependencies": {
- "@aws-amplify/rtn-push-notification": "1.0.0"
- }
+ ]
}
diff --git a/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts b/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts
index fb0c8d10a17..f55d260dfbe 100644
--- a/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts
+++ b/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts
@@ -1,12 +1,12 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
+import type { InAppMessageCampaign as PinpointInAppMessage } from '@aws-sdk/client-pinpoint';
import {
- ChannelType,
- GetInAppMessagesCommand,
- GetInAppMessagesCommandInput,
- InAppMessageCampaign as PinpointInAppMessage,
-} from '@aws-sdk/client-pinpoint';
+ getInAppMessages,
+ GetInAppMessagesInput,
+ GetInAppMessagesOutput,
+} from '@aws-amplify/core/lib-esm/AwsClients/Pinpoint'; // TODO: convert to subpath import from core
import { addEventListener, AWSPinpointProviderCommon } from '../../../common';
import SessionTracker, {
@@ -69,7 +69,7 @@ export default class AWSPinpointProvider
configure = (config = {}): Record => {
this.config = {
...super.configure(config),
- endpointInfo: { channelType: ChannelType.IN_APP },
+ endpointInfo: { channelType: 'IN_APP' },
};
// some configuration steps should not be re-run even if provider is re-configured for some reason
@@ -121,16 +121,16 @@ export default class AWSPinpointProvider
clearMemo();
try {
await this.updateEndpoint();
- const { appId, endpointId, pinpointClient } = this.config;
- const input: GetInAppMessagesCommandInput = {
+ const { appId, credentials, endpointId, region } = this.config;
+ const input: GetInAppMessagesInput = {
ApplicationId: appId,
EndpointId: endpointId,
};
- const command: GetInAppMessagesCommand = new GetInAppMessagesCommand(
+ this.logger.debug('getting in-app messages');
+ const response: GetInAppMessagesOutput = await getInAppMessages(
+ { credentials, region },
input
);
- this.logger.debug('getting in-app messages');
- const response = await pinpointClient.send(command);
const { InAppMessageCampaigns: messages } =
response.InAppMessagesResponse;
dispatchInAppMessagingEvent('getInAppMessages', messages);
diff --git a/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/utils.ts b/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/utils.ts
index ccceb82dee8..a467f7c81c7 100644
--- a/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/utils.ts
+++ b/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/utils.ts
@@ -2,10 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { Amplify, ConsoleLogger, Hub } from '@aws-amplify/core';
-import {
- InAppMessageCampaign as PinpointInAppMessage,
- Layout as PinpointInAppMessageLayout,
-} from '@aws-sdk/client-pinpoint';
+import type { InAppMessageCampaign as PinpointInAppMessage } from '@aws-sdk/client-pinpoint';
import isEmpty from 'lodash/isEmpty';
import { AMPLIFY_SYMBOL } from '../../../common';
import {
@@ -218,11 +215,11 @@ export const clearMemo = () => {
export const interpretLayout = (
layout: PinpointInAppMessage['InAppMessage']['Layout']
): InAppMessageLayout => {
- if (layout === PinpointInAppMessageLayout.MOBILE_FEED) {
+ if (layout === 'MOBILE_FEED') {
return 'MODAL';
}
- if (layout === PinpointInAppMessageLayout.OVERLAYS) {
+ if (layout === 'OVERLAYS') {
return 'FULL_SCREEN';
}
diff --git a/packages/notifications/src/PushNotification/Providers/AWSPinpointProvider/index.ts b/packages/notifications/src/PushNotification/Providers/AWSPinpointProvider/index.ts
index 7daf718d856..49e88d35acc 100644
--- a/packages/notifications/src/PushNotification/Providers/AWSPinpointProvider/index.ts
+++ b/packages/notifications/src/PushNotification/Providers/AWSPinpointProvider/index.ts
@@ -1,8 +1,8 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ChannelType } from '@aws-sdk/client-pinpoint';
import { addEventListener, AWSPinpointProviderCommon } from '../../../common';
+import { ChannelType } from '../../../common/AWSPinpointProviderCommon/types';
import PlatformNotSupportedError from '../../PlatformNotSupportedError';
import { Platform } from '../../Platform';
import {
@@ -108,11 +108,11 @@ export default class AWSPinpointProvider
switch (Platform.OS) {
case 'android': {
// FCM was previously known as GCM and continues to be the channel type in Pinpoint
- return ChannelType.GCM;
+ return 'GCM';
}
case 'ios': {
// If building in debug mode, use the APNs sandbox
- return global['__DEV__'] ? ChannelType.APNS_SANDBOX : ChannelType.APNS;
+ return global['__DEV__'] ? 'APNS_SANDBOX' : 'APNS';
}
default: {
throw new PlatformNotSupportedError();
diff --git a/packages/notifications/src/PushNotification/Providers/AWSPinpointProvider/utils.ts b/packages/notifications/src/PushNotification/Providers/AWSPinpointProvider/utils.ts
index d0aac431531..679caa35abd 100644
--- a/packages/notifications/src/PushNotification/Providers/AWSPinpointProvider/utils.ts
+++ b/packages/notifications/src/PushNotification/Providers/AWSPinpointProvider/utils.ts
@@ -1,7 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-import { Event as AWSPinpointAnalyticsEvent } from '@aws-sdk/client-pinpoint';
+import type { Event as AWSPinpointAnalyticsEvent } from '@aws-sdk/client-pinpoint';
import { ConsoleLogger, Hub } from '@aws-amplify/core';
import { AMPLIFY_SYMBOL } from '../../../common';
import { PushNotificationMessage } from '../../types';
diff --git a/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts b/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts
index 8bbddfb5513..7a6bb68601c 100644
--- a/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts
+++ b/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts
@@ -5,19 +5,17 @@ import {
ClientDevice,
ConsoleLogger,
Credentials,
- getAmplifyUserAgent,
StorageHelper,
transferKeyToUpperCase,
} from '@aws-amplify/core';
import { Cache } from '@aws-amplify/cache';
+import type { Event as AWSPinpointAnalyticsEvent } from '@aws-sdk/client-pinpoint';
import {
- Event as AWSPinpointAnalyticsEvent,
- UpdateEndpointCommand,
- UpdateEndpointCommandInput,
- PinpointClient,
- PutEventsCommand,
- PutEventsCommandInput,
-} from '@aws-sdk/client-pinpoint';
+ putEvents,
+ PutEventsInput,
+ updateEndpoint,
+ UpdateEndpointInput,
+} from '@aws-amplify/core/lib-esm/AwsClients/Pinpoint'; // TODO: convert to subpath import from core
import { v4 as uuid } from 'uuid';
import {
@@ -107,23 +105,15 @@ export default abstract class AWSPinpointProviderCommon
protected recordAnalyticsEvent = async (
event: AWSPinpointAnalyticsEvent
): Promise => {
- const { appId, credentials, endpointId, pinpointClient } = this.config;
- const currentCredentials = await this.getCredentials();
- // Shallow compare to determine if credentials stored here are outdated
- const credentialsUpdated =
- !credentials ||
- Object.keys(currentCredentials).some(
- key => currentCredentials[key] !== credentials[key]
- );
// Update credentials
- this.config.credentials = currentCredentials;
+ this.config.credentials = await this.getCredentials();
+ // Assert required configuration properties to make `putEvents` request are present
+ this.assertValidConfiguration();
+ const { appId, credentials, endpointId, region } = this.config;
+
try {
- // Initialize a new pinpoint client if one isn't already configured or if credentials changed
- if (!pinpointClient || credentialsUpdated) {
- await this.initPinpointClient();
- }
// Create the PutEvents input
- const input: PutEventsCommandInput = {
+ const input: PutEventsInput = {
ApplicationId: appId,
EventsRequest: {
BatchItem: {
@@ -136,9 +126,8 @@ export default abstract class AWSPinpointProviderCommon
},
},
};
- const command: PutEventsCommand = new PutEventsCommand(input);
this.logger.debug('recording analytics event');
- await this.config.pinpointClient.send(command);
+ await putEvents({ credentials, region }, input);
} catch (err) {
this.logger.error('Error recording analytics event', err);
throw err;
@@ -149,19 +138,12 @@ export default abstract class AWSPinpointProviderCommon
userId: string = null,
userInfo: AWSPinpointUserInfo = null
): Promise => {
- const {
- appId,
- credentials,
- endpointId,
- endpointInfo = {},
- pinpointClient,
- } = this.config;
- const currentCredentials = await this.getCredentials();
+ const credentials = await this.getCredentials();
// Shallow compare to determine if credentials stored here are outdated
const credentialsUpdated =
- !credentials ||
- Object.keys(currentCredentials).some(
- key => currentCredentials[key] !== credentials[key]
+ !this.config.credentials ||
+ Object.keys(credentials).some(
+ key => credentials[key] !== this.config.credentials[key]
);
// If endpoint is already initialized, and nothing else is changing, just early return
if (
@@ -173,18 +155,17 @@ export default abstract class AWSPinpointProviderCommon
return;
}
// Update credentials
- this.config.credentials = currentCredentials;
+ this.config.credentials = credentials;
+ // Assert required configuration properties to make `updateEndpoint` request are present
+ this.assertValidConfiguration();
+ const { appId, endpointId, endpointInfo = {}, region } = this.config;
try {
- // Initialize a new pinpoint client if one isn't already configured or if credentials changed
- if (!pinpointClient || credentialsUpdated) {
- this.initPinpointClient();
- }
const { address, attributes, demographic, location, metrics, optOut } =
userInfo ?? {};
const { appVersion, make, model, platform, version } = this.clientInfo;
// Create the UpdateEndpoint input, prioritizing passed in user info and falling back to
// defaults (if any) obtained from the config
- const input: UpdateEndpointCommandInput = {
+ const input: UpdateEndpointInput = {
ApplicationId: appId,
EndpointId: endpointId,
EndpointRequest: {
@@ -217,41 +198,19 @@ export default abstract class AWSPinpointProviderCommon
},
OptOut: optOut ?? endpointInfo.optOut,
User: {
- UserId:
- userId ?? endpointInfo.userId ?? currentCredentials.identityId,
+ UserId: userId ?? endpointInfo.userId ?? credentials.identityId,
UserAttributes: attributes ?? endpointInfo.userAttributes,
},
},
};
- const command: UpdateEndpointCommand = new UpdateEndpointCommand(input);
this.logger.debug('updating endpoint');
- await this.config.pinpointClient.send(command);
+ await updateEndpoint({ credentials, region }, input);
this.endpointInitialized = true;
} catch (err) {
throw err;
}
};
- private initPinpointClient = (): void => {
- const { appId, credentials, pinpointClient, region } = this.config;
-
- if (!appId || !credentials || !region) {
- throw new Error(
- 'One or more of credentials, appId or region is not configured'
- );
- }
-
- if (pinpointClient) {
- pinpointClient.destroy();
- }
-
- this.config.pinpointClient = new PinpointClient({
- region,
- credentials,
- customUserAgent: getAmplifyUserAgent(`/${this.getSubCategory()}`),
- });
- };
-
private getEndpointId = async (): Promise => {
const { appId } = this.config;
// Each Pinpoint channel requires its own Endpoint ID
@@ -292,4 +251,13 @@ export default abstract class AWSPinpointProviderCommon
return null;
}
};
+
+ private assertValidConfiguration = () => {
+ const { appId, credentials, region } = this.config;
+ if (!appId || !credentials || !region) {
+ throw new Error(
+ 'One or more of credentials, appId or region is not configured'
+ );
+ }
+ };
}
diff --git a/packages/notifications/src/common/AWSPinpointProviderCommon/types.ts b/packages/notifications/src/common/AWSPinpointProviderCommon/types.ts
index d7e8f9988bb..36041ad5745 100644
--- a/packages/notifications/src/common/AWSPinpointProviderCommon/types.ts
+++ b/packages/notifications/src/common/AWSPinpointProviderCommon/types.ts
@@ -12,3 +12,18 @@ export interface AWSPinpointUserInfo extends UserInfo {
address?: string;
optOut?: 'ALL' | 'NONE';
}
+
+export type ChannelType =
+ | 'ADM'
+ | 'APNS'
+ | 'APNS_SANDBOX'
+ | 'APNS_VOIP'
+ | 'APNS_VOIP_SANDBOX'
+ | 'BAIDU'
+ | 'CUSTOM'
+ | 'EMAIL'
+ | 'GCM'
+ | 'IN_APP'
+ | 'PUSH'
+ | 'SMS'
+ | 'VOICE';
From a2c3f498df389d2accc576e14747c57c55d08e17 Mon Sep 17 00:00:00 2001
From: Allan Zheng
Date: Mon, 15 May 2023 18:20:25 -0700
Subject: [PATCH 24/41] chore: update size limit
---
packages/analytics/package.json | 4 ++--
packages/api-graphql/package.json | 2 +-
packages/api-rest/package.json | 2 +-
packages/api/package.json | 2 +-
packages/core/package.json | 2 +-
packages/datastore/package.json | 2 +-
packages/pubsub/package.json | 2 +-
7 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/packages/analytics/package.json b/packages/analytics/package.json
index 1fabe017694..9e380c44aa3 100644
--- a/packages/analytics/package.json
+++ b/packages/analytics/package.json
@@ -66,13 +66,13 @@
"name": "Analytics (Pinpoint)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Analytics, AWSPinpointProvider }",
- "limit": "29.45 kB"
+ "limit": "29.65 kB"
},
{
"name": "Analytics (Kinesis)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Analytics, AWSKinesisProvider }",
- "limit": "57.91 kB"
+ "limit": "58.1 kB"
}
],
"jest": {
diff --git a/packages/api-graphql/package.json b/packages/api-graphql/package.json
index 6361bbb7c0d..1fa85be4ac1 100644
--- a/packages/api-graphql/package.json
+++ b/packages/api-graphql/package.json
@@ -64,7 +64,7 @@
"name": "API (GraphQL client)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, GraphQLAPI }",
- "limit": "86.85 kB"
+ "limit": "87.05 kB"
}
],
"jest": {
diff --git a/packages/api-rest/package.json b/packages/api-rest/package.json
index 632d1047c6f..5e15fe9cd0f 100644
--- a/packages/api-rest/package.json
+++ b/packages/api-rest/package.json
@@ -55,7 +55,7 @@
"name": "API (rest client)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, RestAPI }",
- "limit": "29.5 kB"
+ "limit": "29.7 kB"
}
],
"jest": {
diff --git a/packages/api/package.json b/packages/api/package.json
index 50cf3dc074d..81b30dc3451 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -66,7 +66,7 @@
"name": "API (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, API }",
- "limit": "88.1 kB"
+ "limit": "88.3 kB"
}
],
"jest": {
diff --git a/packages/core/package.json b/packages/core/package.json
index d12bfd1306b..993b08f1d87 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -109,7 +109,7 @@
"name": "Core (Signer)",
"path": "./lib-esm/index.js",
"import": "{ Signer }",
- "limit": "7.08 kB"
+ "limit": "7.3 kB"
},
{
"name": "Custom clients (fetch handler)",
diff --git a/packages/datastore/package.json b/packages/datastore/package.json
index 29332110491..e1a59d3e18b 100644
--- a/packages/datastore/package.json
+++ b/packages/datastore/package.json
@@ -72,7 +72,7 @@
"name": "DataStore (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, DataStore }",
- "limit": "136.1 kB"
+ "limit": "136.45 kB"
}
],
"jest": {
diff --git a/packages/pubsub/package.json b/packages/pubsub/package.json
index e4fcdf80445..e6a8dda06a1 100644
--- a/packages/pubsub/package.json
+++ b/packages/pubsub/package.json
@@ -69,7 +69,7 @@
"name": "PubSub (Mqtt provider)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, PubSub, MqttOverWSProvider }",
- "limit": "78.9 kB"
+ "limit": "78.95 kB"
}
],
"jest": {
From b9b5c472c131df11653a23589d388d16a5a8d56f Mon Sep 17 00:00:00 2001
From: AllanZhengYP
Date: Tue, 16 May 2023 12:11:21 -0700
Subject: [PATCH 25/41] chore(clients): export pinpoint client from internal
subpath (#11369)
---
.../Providers/AWSPinpointProvider.test.ts | 9 +++++----
.../src/Providers/AWSPinpointProvider.ts | 2 +-
packages/analytics/src/Providers/EventBuffer.ts | 2 +-
.../core/internals/aws-client-utils/package.json | 7 +++++++
.../internals/aws-clients/pinpoint/package.json | 7 +++++++
packages/core/package.json | 3 ++-
.../core/src/AwsClients/CognitoIdentity/base.ts | 10 +++++-----
.../CognitoIdentity/getCredentialsForIdentity.ts | 8 +++++---
.../core/src/AwsClients/CognitoIdentity/getId.ts | 8 +++++---
packages/core/src/clients/index.ts | 15 +++++++++++++++
.../Providers/AWSPinpointProvider/index.test.ts | 5 ++---
.../Providers/AWSPinpointProvider/index.test.ts | 4 ++--
.../AWSPinpointProviderCommon/index.test.ts | 5 ++---
packages/notifications/package.json | 4 ++--
.../Providers/AWSPinpointProvider/index.ts | 3 ++-
.../Providers/AWSPinpointProvider/utils.ts | 1 +
.../src/common/AWSPinpointProviderCommon/index.ts | 2 +-
17 files changed, 65 insertions(+), 30 deletions(-)
create mode 100644 packages/core/internals/aws-client-utils/package.json
create mode 100644 packages/core/internals/aws-clients/pinpoint/package.json
create mode 100644 packages/core/src/clients/index.ts
diff --git a/packages/analytics/__tests__/Providers/AWSPinpointProvider.test.ts b/packages/analytics/__tests__/Providers/AWSPinpointProvider.test.ts
index 78b29b92814..2c9cd1646c0 100644
--- a/packages/analytics/__tests__/Providers/AWSPinpointProvider.test.ts
+++ b/packages/analytics/__tests__/Providers/AWSPinpointProvider.test.ts
@@ -1,6 +1,8 @@
import { Credentials, ClientDevice } from '@aws-amplify/core';
-import { putEvents } from '@aws-amplify/core/lib-esm/AwsClients/Pinpoint/putEvents';
-import { updateEndpoint } from '@aws-amplify/core/lib-esm/AwsClients/Pinpoint/updateEndpoint';
+import {
+ putEvents,
+ updateEndpoint,
+} from '@aws-amplify/core/internals/aws-clients/pinpoint';
import { AWSPinpointProvider as AnalyticsProvider } from '../../src/Providers/AWSPinpointProvider';
const endpointConfigure = {
@@ -146,8 +148,7 @@ let reject = null;
jest.mock('uuid', () => {
return { v1: () => 'uuid' };
});
-jest.mock('@aws-amplify/core/lib-esm/AwsClients/Pinpoint/putEvents');
-jest.mock('@aws-amplify/core/lib-esm/AwsClients/Pinpoint/updateEndpoint');
+jest.mock('@aws-amplify/core/internals/aws-clients/pinpoint');
const mockPutEvents = putEvents as jest.Mock;
const mockUpdateEndpoint = updateEndpoint as jest.Mock;
diff --git a/packages/analytics/src/Providers/AWSPinpointProvider.ts b/packages/analytics/src/Providers/AWSPinpointProvider.ts
index 70c157239db..ff4ffe52f9c 100644
--- a/packages/analytics/src/Providers/AWSPinpointProvider.ts
+++ b/packages/analytics/src/Providers/AWSPinpointProvider.ts
@@ -18,7 +18,7 @@ import {
updateEndpoint,
UpdateEndpointInput,
UpdateEndpointOutput,
-} from '@aws-amplify/core/lib-esm/AwsClients/Pinpoint'; // TODO: convert to subpath import from core
+} from '@aws-amplify/core/internals/aws-clients/pinpoint';
import {
AnalyticsProvider,
diff --git a/packages/analytics/src/Providers/EventBuffer.ts b/packages/analytics/src/Providers/EventBuffer.ts
index 4188a72cf2a..6166d062324 100644
--- a/packages/analytics/src/Providers/EventBuffer.ts
+++ b/packages/analytics/src/Providers/EventBuffer.ts
@@ -3,7 +3,7 @@ import {
putEvents,
PutEventsInput,
PutEventsOutput,
-} from '@aws-amplify/core/lib-esm/AwsClients/Pinpoint'; // TODO: convert to subpath import from core
+} from '@aws-amplify/core/internals/aws-clients/pinpoint';
import { EventBuffer, EventObject, EventMap } from '../types';
import { isAppInForeground } from '../utils/AppUtils';
diff --git a/packages/core/internals/aws-client-utils/package.json b/packages/core/internals/aws-client-utils/package.json
new file mode 100644
index 00000000000..900ba9b16ac
--- /dev/null
+++ b/packages/core/internals/aws-client-utils/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "@aws-amplify/core/aws-client-utils",
+ "types": "../../../lib-esm/clients/index.d.ts",
+ "main": "../../../lib/clients/index.js",
+ "module": "../../../lib-esm/clients/index.js",
+ "sideEffects": false
+}
\ No newline at end of file
diff --git a/packages/core/internals/aws-clients/pinpoint/package.json b/packages/core/internals/aws-clients/pinpoint/package.json
new file mode 100644
index 00000000000..bb9cfaed1be
--- /dev/null
+++ b/packages/core/internals/aws-clients/pinpoint/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "@aws-amplify/core/aws-clients/pinpoint",
+ "types": "../../../lib-esm/AwsClients/Pinpoint/index.d.ts",
+ "main": "../../../lib/AwsClients/Pinpoint/index.js",
+ "module": "../../../lib-esm/AwsClients/Pinpoint/index.js",
+ "sideEffects": false
+}
\ No newline at end of file
diff --git a/packages/core/package.json b/packages/core/package.json
index 993b08f1d87..8152ff37be5 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -61,7 +61,8 @@
"lib",
"lib-esm",
"src",
- "typings.d.ts"
+ "typings.d.ts",
+ "internals"
],
"dependencies": {
"@aws-crypto/sha256-js": "1.2.2",
diff --git a/packages/core/src/AwsClients/CognitoIdentity/base.ts b/packages/core/src/AwsClients/CognitoIdentity/base.ts
index 0e69a7c7274..2315dcb9aa8 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/base.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/base.ts
@@ -1,22 +1,22 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-import type {
+import {
Endpoint,
EndpointResolverOptions,
Headers,
HttpRequest,
HttpResponse,
Middleware,
-} from '../../clients/types';
-import { getDnsSuffix } from '../../clients/endpoints';
+ getDnsSuffix,
+ unauthenticatedHandler,
+ parseJsonError,
+} from '../../clients';
import { composeTransferHandler } from '../../clients/internal/composeTransferHandler';
-import { unauthenticatedHandler } from '../../clients/handlers/unauthenticated';
import {
jitteredBackoff,
getRetryDecider,
} from '../../clients/middleware/retry';
-import { parseJsonError } from '../../clients/serde/json';
import { getAmplifyUserAgent } from '../../Platform';
/**
diff --git a/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts b/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
index b758f414bb5..041f5bb2f97 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
@@ -12,13 +12,15 @@ import {
defaultConfig,
getSharedHeaders,
} from './base';
-import { composeServiceApi } from '../../clients/internal/composeServiceApi';
-import { Endpoint, HttpRequest, HttpResponse } from '../../clients/types';
import {
+ Endpoint,
+ HttpRequest,
+ HttpResponse,
+ composeServiceApi,
parseJsonBody,
parseJsonError,
parseMetadata,
-} from '../../clients/serde';
+} from '../../clients';
export type { GetCredentialsForIdentityInput, GetCredentialsForIdentityOutput };
diff --git a/packages/core/src/AwsClients/CognitoIdentity/getId.ts b/packages/core/src/AwsClients/CognitoIdentity/getId.ts
index ad69f98f8bc..87cc3a326e1 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/getId.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/getId.ts
@@ -11,13 +11,15 @@ import {
defaultConfig,
getSharedHeaders,
} from './base';
-import { composeServiceApi } from '../../clients/internal/composeServiceApi';
-import { Endpoint, HttpRequest, HttpResponse } from '../../clients/types';
import {
+ Endpoint,
+ HttpRequest,
+ HttpResponse,
+ composeServiceApi,
parseJsonBody,
parseJsonError,
parseMetadata,
-} from '../../clients/serde';
+} from '../../clients';
export type { GetIdInput, GetIdOutput };
diff --git a/packages/core/src/clients/index.ts b/packages/core/src/clients/index.ts
new file mode 100644
index 00000000000..46200886549
--- /dev/null
+++ b/packages/core/src/clients/index.ts
@@ -0,0 +1,15 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+export { getDnsSuffix } from './endpoints';
+
+export { fetchTransferHandler } from './handlers/fetch';
+export { unauthenticatedHandler } from './handlers/unauthenticated';
+export { authenticatedHandler } from './handlers/authenticated';
+export { composeServiceApi } from './internal/composeServiceApi';
+export {
+ signRequest,
+ presignUrl,
+} from './middleware/signing/signer/signatureV4';
+export { parseJsonBody, parseJsonError, parseMetadata } from './serde';
+export * from './types';
diff --git a/packages/notifications/__tests__/InAppMessaging/Providers/AWSPinpointProvider/index.test.ts b/packages/notifications/__tests__/InAppMessaging/Providers/AWSPinpointProvider/index.test.ts
index ddc04e0c50c..c467f3260a2 100644
--- a/packages/notifications/__tests__/InAppMessaging/Providers/AWSPinpointProvider/index.test.ts
+++ b/packages/notifications/__tests__/InAppMessaging/Providers/AWSPinpointProvider/index.test.ts
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { Credentials, StorageHelper } from '@aws-amplify/core';
-import { getInAppMessages } from '@aws-amplify/core/lib-esm/AwsClients/Pinpoint/getInAppMessages';
+import { getInAppMessages } from '@aws-amplify/core/internals/aws-clients/pinpoint';
import cloneDeep from 'lodash/cloneDeep';
import { addEventListener } from '../../../../src/common/eventListeners';
@@ -29,8 +29,7 @@ import {
import { mockStorage } from '../../../../__mocks__/mocks';
jest.mock('@aws-amplify/core');
-jest.mock('@aws-amplify/core/lib-esm/AwsClients/Pinpoint/getInAppMessages');
-jest.mock('@aws-amplify/core/lib-esm/AwsClients/Pinpoint/updateEndpoint');
+jest.mock('@aws-amplify/core/internals/aws-clients/pinpoint');
jest.mock('../../../../src/common/eventListeners');
jest.mock('../../../../src/InAppMessaging/Providers/AWSPinpointProvider/utils');
jest.mock(
diff --git a/packages/notifications/__tests__/PushNotification/Providers/AWSPinpointProvider/index.test.ts b/packages/notifications/__tests__/PushNotification/Providers/AWSPinpointProvider/index.test.ts
index 00eb4091403..f0c9115040e 100644
--- a/packages/notifications/__tests__/PushNotification/Providers/AWSPinpointProvider/index.test.ts
+++ b/packages/notifications/__tests__/PushNotification/Providers/AWSPinpointProvider/index.test.ts
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { Credentials, StorageHelper } from '@aws-amplify/core';
-import { updateEndpoint } from '@aws-amplify/core/lib-esm/AwsClients/Pinpoint/updateEndpoint';
+import { updateEndpoint } from '@aws-amplify/core/internals/aws-clients/pinpoint';
import { addEventListener } from '../../../../src/common/eventListeners';
import { Platform } from '../../../../src/PushNotification/Platform';
@@ -24,7 +24,7 @@ import {
import { mockStorage } from '../../../../__mocks__/mocks';
jest.mock('@aws-amplify/core');
-jest.mock('@aws-amplify/core/lib-esm/AwsClients/Pinpoint/updateEndpoint');
+jest.mock('@aws-amplify/core/internals/aws-clients/pinpoint');
jest.mock('../../../../src/common/eventListeners');
jest.mock('../../../../src/PushNotification/Platform');
jest.mock(
diff --git a/packages/notifications/__tests__/common/AWSPinpointProviderCommon/index.test.ts b/packages/notifications/__tests__/common/AWSPinpointProviderCommon/index.test.ts
index 0d1e0a42605..e5eab4ee100 100644
--- a/packages/notifications/__tests__/common/AWSPinpointProviderCommon/index.test.ts
+++ b/packages/notifications/__tests__/common/AWSPinpointProviderCommon/index.test.ts
@@ -5,7 +5,7 @@ import { Credentials, StorageHelper } from '@aws-amplify/core';
import {
putEvents,
updateEndpoint,
-} from '@aws-amplify/core/lib-esm/AwsClients/Pinpoint'; // TODO: convert to subpath import from core
+} from '@aws-amplify/core/internals/aws-clients/pinpoint';
import { AWSPinpointProviderCommon } from '../../../src/common';
@@ -20,8 +20,7 @@ import { mockLogger, mockStorage } from '../../../__mocks__/mocks';
import { NotificationsSubCategory } from '../../../src/types';
jest.mock('@aws-amplify/core');
-jest.mock('@aws-amplify/core/lib-esm/AwsClients/Pinpoint/putEvents');
-jest.mock('@aws-amplify/core/lib-esm/AwsClients/Pinpoint/updateEndpoint');
+jest.mock('@aws-amplify/core/internals/aws-clients/pinpoint');
jest.mock('../../../src/common/eventListeners');
const SUB_CATEGORY = 'SubCategory';
diff --git a/packages/notifications/package.json b/packages/notifications/package.json
index 010b01afad9..6e50ff4d9c7 100644
--- a/packages/notifications/package.json
+++ b/packages/notifications/package.json
@@ -52,12 +52,12 @@
"@aws-amplify/cache": "5.0.32",
"@aws-amplify/core": "5.3.0",
"@aws-amplify/rtn-push-notification": "1.1.1",
+ "@aws-sdk/client-pinpoint": "3.186.1",
"lodash": "^4.17.21",
"uuid": "^3.2.1"
},
"devDependencies": {
- "@aws-amplify/rtn-push-notification": "1.0.0",
- "@aws-sdk/client-pinpoint": "3.186.1"
+ "@aws-amplify/rtn-push-notification": "1.0.0"
},
"size-limit": [
{
diff --git a/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts b/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts
index f55d260dfbe..fdb8e05a9c7 100644
--- a/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts
+++ b/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts
@@ -1,12 +1,13 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
+// TODO: [v6] do not leak AWS SDK types to public so we can remove it from runtime dependencies.
import type { InAppMessageCampaign as PinpointInAppMessage } from '@aws-sdk/client-pinpoint';
import {
getInAppMessages,
GetInAppMessagesInput,
GetInAppMessagesOutput,
-} from '@aws-amplify/core/lib-esm/AwsClients/Pinpoint'; // TODO: convert to subpath import from core
+} from '@aws-amplify/core/internals/aws-clients/pinpoint';
import { addEventListener, AWSPinpointProviderCommon } from '../../../common';
import SessionTracker, {
diff --git a/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/utils.ts b/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/utils.ts
index a467f7c81c7..e8e869e52a8 100644
--- a/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/utils.ts
+++ b/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/utils.ts
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { Amplify, ConsoleLogger, Hub } from '@aws-amplify/core';
+// TODO: [v6] do not leak AWS SDK types to public so we can remove it from runtime dependencies.
import type { InAppMessageCampaign as PinpointInAppMessage } from '@aws-sdk/client-pinpoint';
import isEmpty from 'lodash/isEmpty';
import { AMPLIFY_SYMBOL } from '../../../common';
diff --git a/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts b/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts
index 7a6bb68601c..09b3769e388 100644
--- a/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts
+++ b/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts
@@ -15,7 +15,7 @@ import {
PutEventsInput,
updateEndpoint,
UpdateEndpointInput,
-} from '@aws-amplify/core/lib-esm/AwsClients/Pinpoint'; // TODO: convert to subpath import from core
+} from '@aws-amplify/core/internals/aws-clients/pinpoint';
import { v4 as uuid } from 'uuid';
import {
From 868fb4af05d35f76364014c74dd5f780959465c5 Mon Sep 17 00:00:00 2001
From: Allan Zheng
Date: Tue, 16 May 2023 17:15:46 -0700
Subject: [PATCH 26/41] feat(clients): allow fetch handler to read body
multiple times
---
packages/core/__tests__/clients/fetch-test.ts | 12 +++++++---
packages/core/src/clients/handlers/fetch.ts | 22 ++++++++++++++++---
2 files changed, 28 insertions(+), 6 deletions(-)
diff --git a/packages/core/__tests__/clients/fetch-test.ts b/packages/core/__tests__/clients/fetch-test.ts
index 506672a8000..75139ee4531 100644
--- a/packages/core/__tests__/clients/fetch-test.ts
+++ b/packages/core/__tests__/clients/fetch-test.ts
@@ -47,30 +47,36 @@ describe(fetchTransferHandler.name, () => {
expect(headers).toEqual({ bar: 'foo' });
});
- test('should support text() in response.body', async () => {
+ test('should support text() in response.body with caching', async () => {
+ mockBody.text.mockResolvedValue('payload value');
const { body } = await fetchTransferHandler(mockRequest, {});
if (!body) {
fail('body should exist');
}
await body.text();
+ await body.text(); // test caching
expect(mockBody.text).toBeCalledTimes(1);
});
- test('should support blob() in response.body', async () => {
+ test('should support blob() in response.body with caching', async () => {
+ mockBody.blob.mockResolvedValue('payload value');
const { body } = await fetchTransferHandler(mockRequest, {});
if (!body) {
fail('body should exist');
}
await body.blob();
+ await body.blob(); // test caching
expect(mockBody.blob).toBeCalledTimes(1);
});
- test('should support json() in response.body', async () => {
+ test('should support json() in response.body with caching', async () => {
+ mockBody.json.mockResolvedValue('payload value');
const { body } = await fetchTransferHandler(mockRequest, {});
if (!body) {
fail('body should exist');
}
await body.json();
+ await body.json(); // test caching
expect(mockBody.json).toBeCalledTimes(1);
});
diff --git a/packages/core/src/clients/handlers/fetch.ts b/packages/core/src/clients/handlers/fetch.ts
index a111d64f620..99684b6ee98 100644
--- a/packages/core/src/clients/handlers/fetch.ts
+++ b/packages/core/src/clients/handlers/fetch.ts
@@ -42,9 +42,9 @@ export const fetchTransferHandler: TransferHandler<
// resp.body is a ReadableStream according to Fetch API spec, but React Native
// does not implement it.
const bodyWithMixin = Object.assign(resp.body ?? {}, {
- text: () => resp.text(),
- blob: () => resp.blob(),
- json: () => resp.json(),
+ text: withPayloadCaching(() => resp.text()),
+ blob: withPayloadCaching(() => resp.blob()),
+ json: withPayloadCaching(() => resp.json()),
});
return {
@@ -52,3 +52,19 @@ export const fetchTransferHandler: TransferHandler<
body: bodyWithMixin,
};
};
+
+/**
+ * Cache the payload of a response body. It allows multiple calls to the body,
+ * for example, when reading the body in both retry decider and error deserializer.
+ * Caching body is allowed here because we call the body accessor(blob(), json(),
+ * etc.) when body is small or streaming implementation is not available(RN).
+ */
+const withPayloadCaching = (payloadAccessor: () => Promise) => {
+ let cached: T;
+ return async () => {
+ if (!cached) {
+ cached = await payloadAccessor();
+ }
+ return cached;
+ };
+};
From b42d3c7a88e47338c0d1468e7ef2cfb0095c00f0 Mon Sep 17 00:00:00 2001
From: Allan Zheng
Date: Wed, 17 May 2023 12:07:04 -0700
Subject: [PATCH 27/41] chore: update size limit
---
packages/analytics/package.json | 2 +-
packages/api-graphql/package.json | 2 +-
packages/api-rest/package.json | 2 +-
packages/api/package.json | 2 +-
packages/auth/package.json | 2 +-
packages/core/package.json | 6 +++---
packages/datastore/package.json | 2 +-
packages/pubsub/package.json | 4 ++--
packages/storage/package.json | 2 +-
9 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/packages/analytics/package.json b/packages/analytics/package.json
index 9e380c44aa3..a108300657d 100644
--- a/packages/analytics/package.json
+++ b/packages/analytics/package.json
@@ -66,7 +66,7 @@
"name": "Analytics (Pinpoint)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Analytics, AWSPinpointProvider }",
- "limit": "29.65 kB"
+ "limit": "29.7 kB"
},
{
"name": "Analytics (Kinesis)",
diff --git a/packages/api-graphql/package.json b/packages/api-graphql/package.json
index 1fa85be4ac1..0a44ee65379 100644
--- a/packages/api-graphql/package.json
+++ b/packages/api-graphql/package.json
@@ -64,7 +64,7 @@
"name": "API (GraphQL client)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, GraphQLAPI }",
- "limit": "87.05 kB"
+ "limit": "87.1 kB"
}
],
"jest": {
diff --git a/packages/api-rest/package.json b/packages/api-rest/package.json
index 5e15fe9cd0f..d3afc82be97 100644
--- a/packages/api-rest/package.json
+++ b/packages/api-rest/package.json
@@ -55,7 +55,7 @@
"name": "API (rest client)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, RestAPI }",
- "limit": "29.7 kB"
+ "limit": "29.75 kB"
}
],
"jest": {
diff --git a/packages/api/package.json b/packages/api/package.json
index 81b30dc3451..62fb1abf18e 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -66,7 +66,7 @@
"name": "API (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, API }",
- "limit": "88.3 kB"
+ "limit": "88.35 kB"
}
],
"jest": {
diff --git a/packages/auth/package.json b/packages/auth/package.json
index fc5bd840fb5..918de35c9f5 100644
--- a/packages/auth/package.json
+++ b/packages/auth/package.json
@@ -58,7 +58,7 @@
"name": "Auth (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Auth }",
- "limit": "53.7 kB"
+ "limit": "53.8 kB"
}
],
"jest": {
diff --git a/packages/core/package.json b/packages/core/package.json
index 8152ff37be5..9d25c89e03d 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -104,7 +104,7 @@
"name": "Core (Credentials)",
"path": "./lib-esm/index.js",
"import": "{ Credentials }",
- "limit": "12 kB"
+ "limit": "12.05 kB"
},
{
"name": "Core (Signer)",
@@ -116,13 +116,13 @@
"name": "Custom clients (fetch handler)",
"path": "./lib-esm/clients/handlers/fetch.js",
"import": "{ fetchTransferHandler }",
- "limit": "1.73 kB"
+ "limit": "1.85 kB"
},
{
"name": "Custom clients (unauthenticated handler)",
"path": "./lib-esm/clients/handlers/unauthenticated.js",
"import": "{ unauthenticatedHandler }",
- "limit": "2.75 kB"
+ "limit": "2.85 kB"
},
{
"name": "Custom clients (retry middleware)",
diff --git a/packages/datastore/package.json b/packages/datastore/package.json
index e1a59d3e18b..fb7d1825d90 100644
--- a/packages/datastore/package.json
+++ b/packages/datastore/package.json
@@ -72,7 +72,7 @@
"name": "DataStore (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, DataStore }",
- "limit": "136.45 kB"
+ "limit": "136.5 kB"
}
],
"jest": {
diff --git a/packages/pubsub/package.json b/packages/pubsub/package.json
index e6a8dda06a1..29ed50942d6 100644
--- a/packages/pubsub/package.json
+++ b/packages/pubsub/package.json
@@ -63,13 +63,13 @@
"name": "PubSub (IoT provider)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, PubSub, AWSIoTProvider }",
- "limit": "79.1 kB"
+ "limit": "79.15 kB"
},
{
"name": "PubSub (Mqtt provider)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, PubSub, MqttOverWSProvider }",
- "limit": "78.95 kB"
+ "limit": "79 kB"
}
],
"jest": {
diff --git a/packages/storage/package.json b/packages/storage/package.json
index 542d054c7d9..312283f57f8 100644
--- a/packages/storage/package.json
+++ b/packages/storage/package.json
@@ -60,7 +60,7 @@
"name": "Storage (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Storage }",
- "limit": "84.8 kB"
+ "limit": "84.85 kB"
}
],
"jest": {
From c1535ddbfb198dc669e8a00eda52323975b37dc6 Mon Sep 17 00:00:00 2001
From: Allan Zheng
Date: Wed, 17 May 2023 15:54:20 -0700
Subject: [PATCH 28/41] fix(clients): add react-native entrypoint for internal
modules
---
packages/core/internals/aws-client-utils/package.json | 3 ++-
packages/core/internals/aws-clients/pinpoint/package.json | 3 ++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/core/internals/aws-client-utils/package.json b/packages/core/internals/aws-client-utils/package.json
index 900ba9b16ac..34da4d5e10b 100644
--- a/packages/core/internals/aws-client-utils/package.json
+++ b/packages/core/internals/aws-client-utils/package.json
@@ -1,7 +1,8 @@
{
- "name": "@aws-amplify/core/aws-client-utils",
+ "name": "@aws-amplify/core/internals/aws-client-utils",
"types": "../../../lib-esm/clients/index.d.ts",
"main": "../../../lib/clients/index.js",
"module": "../../../lib-esm/clients/index.js",
+ "react-native": "../../../lib-esm/clients/index.js",
"sideEffects": false
}
\ No newline at end of file
diff --git a/packages/core/internals/aws-clients/pinpoint/package.json b/packages/core/internals/aws-clients/pinpoint/package.json
index bb9cfaed1be..4205492184a 100644
--- a/packages/core/internals/aws-clients/pinpoint/package.json
+++ b/packages/core/internals/aws-clients/pinpoint/package.json
@@ -1,7 +1,8 @@
{
- "name": "@aws-amplify/core/aws-clients/pinpoint",
+ "name": "@aws-amplify/core/internals/aws-clients/pinpoint",
"types": "../../../lib-esm/AwsClients/Pinpoint/index.d.ts",
"main": "../../../lib/AwsClients/Pinpoint/index.js",
"module": "../../../lib-esm/AwsClients/Pinpoint/index.js",
+ "react-native": "../../../lib-esm/AwsClients/Pinpoint/index.js",
"sideEffects": false
}
\ No newline at end of file
From 17724c1decd8ee2debc225a4ab7fe76906978d1a Mon Sep 17 00:00:00 2001
From: Allan Zheng
Date: Thu, 18 May 2023 12:48:13 -0700
Subject: [PATCH 29/41] chore: address feedbacks
---
packages/core/__tests__/clients/fetch-test.ts | 25 ++++++++++---------
1 file changed, 13 insertions(+), 12 deletions(-)
diff --git a/packages/core/__tests__/clients/fetch-test.ts b/packages/core/__tests__/clients/fetch-test.ts
index 75139ee4531..ad8cd6fde52 100644
--- a/packages/core/__tests__/clients/fetch-test.ts
+++ b/packages/core/__tests__/clients/fetch-test.ts
@@ -24,6 +24,7 @@ describe(fetchTransferHandler.name, () => {
headers: {},
url: new URL('https://foo.bar'),
};
+ const mockPayloadValue = 'payload value';
beforeEach(() => {
jest.clearAllMocks();
@@ -48,36 +49,36 @@ describe(fetchTransferHandler.name, () => {
});
test('should support text() in response.body with caching', async () => {
- mockBody.text.mockResolvedValue('payload value');
+ mockBody.text.mockResolvedValue(mockPayloadValue);
const { body } = await fetchTransferHandler(mockRequest, {});
if (!body) {
fail('body should exist');
}
- await body.text();
- await body.text(); // test caching
- expect(mockBody.text).toBeCalledTimes(1);
+ expect(await body.text()).toBe(mockPayloadValue);
+ expect(await body.text()).toBe(mockPayloadValue);
+ expect(mockBody.text).toBeCalledTimes(1); // test caching
});
test('should support blob() in response.body with caching', async () => {
- mockBody.blob.mockResolvedValue('payload value');
+ mockBody.blob.mockResolvedValue(mockPayloadValue);
const { body } = await fetchTransferHandler(mockRequest, {});
if (!body) {
fail('body should exist');
}
- await body.blob();
- await body.blob(); // test caching
- expect(mockBody.blob).toBeCalledTimes(1);
+ expect(await body.blob()).toBe(mockPayloadValue);
+ expect(await body.blob()).toBe(mockPayloadValue);
+ expect(mockBody.blob).toBeCalledTimes(1); // test caching
});
test('should support json() in response.body with caching', async () => {
- mockBody.json.mockResolvedValue('payload value');
+ mockBody.json.mockResolvedValue(mockPayloadValue);
const { body } = await fetchTransferHandler(mockRequest, {});
if (!body) {
fail('body should exist');
}
- await body.json();
- await body.json(); // test caching
- expect(mockBody.json).toBeCalledTimes(1);
+ expect(await body.json()).toBe(mockPayloadValue);
+ expect(await body.json()).toBe(mockPayloadValue);
+ expect(mockBody.json).toBeCalledTimes(1); // test caching
});
test.each(['GET', 'HEAD', 'DELETE'])(
From 9a8569ab98480ad96b53a7104366640c66343aa2 Mon Sep 17 00:00:00 2001
From: AllanZhengYP
Date: Wed, 24 May 2023 13:28:59 -0700
Subject: [PATCH 30/41] feat(clients): vendor TS types from pinpoint and
cognito-identity clients (#11393)
* chore: dts bundler script for AWS SDK types
* feat(clients): vendor AWS SDK client types
* chore: workaround the tslint errors for generated rollup types
* docs: add readme to build clients script
* feat: update SDK types
---
package.json | 1 +
packages/analytics/package.json | 5 +-
packages/core/package.json | 2 -
.../getCredentialsForIdentity.ts | 10 +-
.../src/AwsClients/CognitoIdentity/getId.ts | 8 +-
.../src/AwsClients/CognitoIdentity/types.ts | 159 ++++
.../AwsClients/Pinpoint/getInAppMessages.ts | 8 +-
.../core/src/AwsClients/Pinpoint/index.ts | 1 +
.../core/src/AwsClients/Pinpoint/putEvents.ts | 8 +-
.../core/src/AwsClients/Pinpoint/types.ts | 844 ++++++++++++++++++
.../src/AwsClients/Pinpoint/updateEndpoint.ts | 8 +-
packages/core/tslint.json | 2 +-
packages/notifications/__mocks__/data.ts | 2 +-
.../AWSPinpointProvider/utils.test.ts | 2 +-
packages/notifications/package.json | 1 -
.../Providers/AWSPinpointProvider/index.ts | 3 +-
.../Providers/AWSPinpointProvider/utils.ts | 3 +-
.../Providers/AWSPinpointProvider/utils.ts | 2 +-
.../common/AWSPinpointProviderCommon/index.ts | 2 +-
scripts/dts-bundler/README.md | 10 +
scripts/dts-bundler/cognito-identity.d.ts | 15 +
scripts/dts-bundler/dts-bundler.config.js | 32 +
scripts/dts-bundler/package.json | 11 +
scripts/dts-bundler/pinpoint.d.ts | 21 +
scripts/dts-bundler/tsconfig.json | 3 +
25 files changed, 1126 insertions(+), 37 deletions(-)
create mode 100644 packages/core/src/AwsClients/CognitoIdentity/types.ts
create mode 100644 packages/core/src/AwsClients/Pinpoint/types.ts
create mode 100644 scripts/dts-bundler/README.md
create mode 100644 scripts/dts-bundler/cognito-identity.d.ts
create mode 100644 scripts/dts-bundler/dts-bundler.config.js
create mode 100644 scripts/dts-bundler/package.json
create mode 100644 scripts/dts-bundler/pinpoint.d.ts
create mode 100644 scripts/dts-bundler/tsconfig.json
diff --git a/package.json b/package.json
index 4908fca2f9e..7a2957d42de 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"build:watch": "concurrently 'lerna run build:cjs:watch --parallel' 'lerna run build:esm:watch --parallel' --raw",
"build:esm:watch": "lerna run build:esm:watch --parallel",
"build:cjs:watch": "lerna run build:cjs:watch --parallel",
+ "build:client-types": "cd scripts/dts-bundler && yarn && yarn run build",
"clean": "lerna run clean --parallel",
"clean:size": "lerna run clean:size --parallel",
"format": "lerna run format",
diff --git a/packages/analytics/package.json b/packages/analytics/package.json
index a108300657d..d8d9ee8d22a 100644
--- a/packages/analytics/package.json
+++ b/packages/analytics/package.json
@@ -58,9 +58,6 @@
"tslib": "^1.8.0",
"uuid": "^3.2.1"
},
- "devDependencies": {
- "@aws-sdk/client-pinpoint": "3.186.1"
- },
"size-limit": [
{
"name": "Analytics (Pinpoint)",
@@ -72,7 +69,7 @@
"name": "Analytics (Kinesis)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Analytics, AWSKinesisProvider }",
- "limit": "58.1 kB"
+ "limit": "58.2 kB"
}
],
"jest": {
diff --git a/packages/core/package.json b/packages/core/package.json
index 9d25c89e03d..ac9ea9e6bd4 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -48,8 +48,6 @@
},
"homepage": "https://aws-amplify.github.io/",
"devDependencies": {
- "@aws-sdk/client-cognito-identity": "3.6.1",
- "@aws-sdk/client-pinpoint": "3.186.1",
"@react-native-async-storage/async-storage": "1.15.17",
"find": "^0.2.7",
"genversion": "^2.2.0",
diff --git a/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts b/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
index 041f5bb2f97..84aa2566711 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/getCredentialsForIdentity.ts
@@ -1,11 +1,6 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-import type {
- GetCredentialsForIdentityCommandInput as GetCredentialsForIdentityInput,
- GetCredentialsForIdentityCommandOutput as GetCredentialsForIdentityOutput,
- Credentials,
-} from '@aws-sdk/client-cognito-identity';
import {
buildHttpRpcRequest,
cognitoIdentityTransferHandler,
@@ -21,6 +16,11 @@ import {
parseJsonError,
parseMetadata,
} from '../../clients';
+import type {
+ GetCredentialsForIdentityCommandInput as GetCredentialsForIdentityInput,
+ GetCredentialsForIdentityCommandOutput as GetCredentialsForIdentityOutput,
+ Credentials,
+} from './types';
export type { GetCredentialsForIdentityInput, GetCredentialsForIdentityOutput };
diff --git a/packages/core/src/AwsClients/CognitoIdentity/getId.ts b/packages/core/src/AwsClients/CognitoIdentity/getId.ts
index 87cc3a326e1..9e5e68e941e 100644
--- a/packages/core/src/AwsClients/CognitoIdentity/getId.ts
+++ b/packages/core/src/AwsClients/CognitoIdentity/getId.ts
@@ -1,10 +1,6 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-import {
- GetIdCommandInput as GetIdInput,
- GetIdCommandOutput as GetIdOutput,
-} from '@aws-sdk/client-cognito-identity';
import {
buildHttpRpcRequest,
cognitoIdentityTransferHandler,
@@ -20,6 +16,10 @@ import {
parseJsonError,
parseMetadata,
} from '../../clients';
+import {
+ GetIdCommandInput as GetIdInput,
+ GetIdCommandOutput as GetIdOutput,
+} from './types';
export type { GetIdInput, GetIdOutput };
diff --git a/packages/core/src/AwsClients/CognitoIdentity/types.ts b/packages/core/src/AwsClients/CognitoIdentity/types.ts
new file mode 100644
index 00000000000..6ce1d27ca2a
--- /dev/null
+++ b/packages/core/src/AwsClients/CognitoIdentity/types.ts
@@ -0,0 +1,159 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+// Generated by scripts/dts-bundler/README.md
+
+import { MetadataBearer as __MetadataBearer } from '@aws-sdk/types';
+
+/**
+ * @public
+ * Credentials for the provided identity ID.
+ */
+export interface Credentials {
+ /**
+ * The Access Key portion of the credentials.
+ */
+ AccessKeyId?: string;
+ /**
+ * The Secret Access Key portion of the credentials
+ */
+ SecretKey?: string;
+ /**
+ * The Session Token portion of the credentials
+ */
+ SessionToken?: string;
+ /**
+ * The date at which these credentials will expire.
+ */
+ Expiration?: Date;
+}
+/**
+ * @public
+ *
+ * The input for {@link GetCredentialsForIdentityCommand}.
+ */
+export interface GetCredentialsForIdentityCommandInput
+ extends GetCredentialsForIdentityInput {}
+/**
+ * @public
+ *
+ * The output of {@link GetCredentialsForIdentityCommand}.
+ */
+export interface GetCredentialsForIdentityCommandOutput
+ extends GetCredentialsForIdentityResponse,
+ __MetadataBearer {}
+/**
+ * @public
+ * Input to the GetCredentialsForIdentity
action.
+ */
+export interface GetCredentialsForIdentityInput {
+ /**
+ * A unique identifier in the format REGION:GUID.
+ */
+ IdentityId: string | undefined;
+ /**
+ * A set of optional name-value pairs that map provider names to provider tokens. The
+ * name-value pair will follow the syntax "provider_name":
+ * "provider_user_identifier".
+ * Logins should not be specified when trying to get credentials for an unauthenticated
+ * identity.
+ * The Logins parameter is required when using identities associated with external
+ * identity providers such as Facebook. For examples of Logins
maps, see the code
+ * examples in the External Identity Providers section of the Amazon Cognito Developer
+ * Guide.
+ */
+ Logins?: Record;
+ /**
+ * The Amazon Resource Name (ARN) of the role to be assumed when multiple roles were
+ * received in the token from the identity provider. For example, a SAML-based identity
+ * provider. This parameter is optional for identity providers that do not support role
+ * customization.
+ */
+ CustomRoleArn?: string;
+}
+/**
+ * @public
+ * Returned in response to a successful GetCredentialsForIdentity
+ * operation.
+ */
+export interface GetCredentialsForIdentityResponse {
+ /**
+ * A unique identifier in the format REGION:GUID.
+ */
+ IdentityId?: string;
+ /**
+ * Credentials for the provided identity ID.
+ */
+ Credentials?: Credentials;
+}
+/**
+ * @public
+ *
+ * The input for {@link GetIdCommand}.
+ */
+export interface GetIdCommandInput extends GetIdInput {}
+/**
+ * @public
+ *
+ * The output of {@link GetIdCommand}.
+ */
+export interface GetIdCommandOutput extends GetIdResponse, __MetadataBearer {}
+/**
+ * @public
+ * Input to the GetId action.
+ */
+export interface GetIdInput {
+ /**
+ * A standard AWS account ID (9+ digits).
+ */
+ AccountId?: string;
+ /**
+ * An identity pool ID in the format REGION:GUID.
+ */
+ IdentityPoolId: string | undefined;
+ /**
+ * A set of optional name-value pairs that map provider names to provider tokens. The
+ * available provider names for Logins
are as follows:
+ *
+ *
+ * Facebook: graph.facebook.com
+ *
+ *
+ *
+ * Amazon Cognito user pool:
+ * cognito-idp..amazonaws.com/
,
+ * for example, cognito-idp.us-east-1.amazonaws.com/us-east-1_123456789
.
+ *
+ *
+ *
+ * Google: accounts.google.com
+ *
+ *
+ *
+ * Amazon: www.amazon.com
+ *
+ *
+ *
+ * Twitter: api.twitter.com
+ *
+ *
+ *
+ * Digits: www.digits.com
+ *
+ *
+ *
+ */
+ Logins?: Record;
+}
+/**
+ * @public
+ * Returned in response to a GetId request.
+ */
+export interface GetIdResponse {
+ /**
+ * A unique identifier in the format REGION:GUID.
+ */
+ IdentityId?: string;
+}
+
+export {};
diff --git a/packages/core/src/AwsClients/Pinpoint/getInAppMessages.ts b/packages/core/src/AwsClients/Pinpoint/getInAppMessages.ts
index 672ab0a2110..17a74e8b95c 100644
--- a/packages/core/src/AwsClients/Pinpoint/getInAppMessages.ts
+++ b/packages/core/src/AwsClients/Pinpoint/getInAppMessages.ts
@@ -1,10 +1,6 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-import type {
- GetInAppMessagesCommandInput as GetInAppMessagesInput,
- GetInAppMessagesCommandOutput as GetInAppMessagesOutput,
-} from '@aws-sdk/client-pinpoint';
import { authenticatedHandler } from '../../clients/handlers/authenticated';
import { composeServiceApi } from '../../clients/internal/composeServiceApi';
import { extendedEncodeURIComponent } from '../../clients/middleware/signing/utils/extendedEncodeURIComponent';
@@ -15,6 +11,10 @@ import {
} from '../../clients/serde';
import { Endpoint, HttpRequest, HttpResponse } from '../../clients/types';
import { defaultConfig, getSharedHeaders } from './base';
+import type {
+ GetInAppMessagesCommandInput as GetInAppMessagesInput,
+ GetInAppMessagesCommandOutput as GetInAppMessagesOutput,
+} from './types';
export type { GetInAppMessagesInput, GetInAppMessagesOutput };
diff --git a/packages/core/src/AwsClients/Pinpoint/index.ts b/packages/core/src/AwsClients/Pinpoint/index.ts
index 445ada9ac97..72a08a15ce3 100644
--- a/packages/core/src/AwsClients/Pinpoint/index.ts
+++ b/packages/core/src/AwsClients/Pinpoint/index.ts
@@ -12,3 +12,4 @@ export {
UpdateEndpointInput,
UpdateEndpointOutput,
} from './updateEndpoint';
+export { Event, InAppMessageCampaign } from './types';
diff --git a/packages/core/src/AwsClients/Pinpoint/putEvents.ts b/packages/core/src/AwsClients/Pinpoint/putEvents.ts
index 2185386cb13..c072a11e40d 100644
--- a/packages/core/src/AwsClients/Pinpoint/putEvents.ts
+++ b/packages/core/src/AwsClients/Pinpoint/putEvents.ts
@@ -1,10 +1,6 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-import type {
- PutEventsCommandInput as PutEventsInput,
- PutEventsCommandOutput as PutEventsOutput,
-} from '@aws-sdk/client-pinpoint';
import { authenticatedHandler } from '../../clients/handlers/authenticated';
import { composeServiceApi } from '../../clients/internal/composeServiceApi';
import { extendedEncodeURIComponent } from '../../clients/middleware/signing/utils/extendedEncodeURIComponent';
@@ -15,6 +11,10 @@ import {
} from '../../clients/serde';
import { Endpoint, HttpRequest, HttpResponse } from '../../clients/types';
import { defaultConfig, getSharedHeaders } from './base';
+import type {
+ PutEventsCommandInput as PutEventsInput,
+ PutEventsCommandOutput as PutEventsOutput,
+} from './types';
export type { PutEventsInput, PutEventsOutput };
diff --git a/packages/core/src/AwsClients/Pinpoint/types.ts b/packages/core/src/AwsClients/Pinpoint/types.ts
new file mode 100644
index 00000000000..efd08fa884c
--- /dev/null
+++ b/packages/core/src/AwsClients/Pinpoint/types.ts
@@ -0,0 +1,844 @@
+/* tslint:disable:max-line-length */
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+// Generated by scripts/dts-bundler/README.md
+
+import { MetadataBearer as __MetadataBearer } from '@aws-sdk/types';
+
+declare const Alignment: {
+ readonly CENTER: 'CENTER';
+ readonly LEFT: 'LEFT';
+ readonly RIGHT: 'RIGHT';
+};
+declare const AttributeType: {
+ readonly AFTER: 'AFTER';
+ readonly BEFORE: 'BEFORE';
+ readonly BETWEEN: 'BETWEEN';
+ readonly CONTAINS: 'CONTAINS';
+ readonly EXCLUSIVE: 'EXCLUSIVE';
+ readonly INCLUSIVE: 'INCLUSIVE';
+ readonly ON: 'ON';
+};
+declare const ButtonAction: {
+ readonly CLOSE: 'CLOSE';
+ readonly DEEP_LINK: 'DEEP_LINK';
+ readonly LINK: 'LINK';
+};
+declare const ChannelType: {
+ readonly ADM: 'ADM';
+ readonly APNS: 'APNS';
+ readonly APNS_SANDBOX: 'APNS_SANDBOX';
+ readonly APNS_VOIP: 'APNS_VOIP';
+ readonly APNS_VOIP_SANDBOX: 'APNS_VOIP_SANDBOX';
+ readonly BAIDU: 'BAIDU';
+ readonly CUSTOM: 'CUSTOM';
+ readonly EMAIL: 'EMAIL';
+ readonly GCM: 'GCM';
+ readonly IN_APP: 'IN_APP';
+ readonly PUSH: 'PUSH';
+ readonly SMS: 'SMS';
+ readonly VOICE: 'VOICE';
+};
+declare const DimensionType: {
+ readonly EXCLUSIVE: 'EXCLUSIVE';
+ readonly INCLUSIVE: 'INCLUSIVE';
+};
+declare const FilterType: {
+ readonly ENDPOINT: 'ENDPOINT';
+ readonly SYSTEM: 'SYSTEM';
+};
+declare const Layout: {
+ readonly BOTTOM_BANNER: 'BOTTOM_BANNER';
+ readonly CAROUSEL: 'CAROUSEL';
+ readonly MIDDLE_BANNER: 'MIDDLE_BANNER';
+ readonly MOBILE_FEED: 'MOBILE_FEED';
+ readonly OVERLAYS: 'OVERLAYS';
+ readonly TOP_BANNER: 'TOP_BANNER';
+};
+/**
+ * @public
+ * Specifies attribute-based criteria for including or excluding endpoints from a segment.
+ */
+export interface AttributeDimension {
+ /**
+ * The type of segment dimension to use. Valid values are:
INCLUSIVE - endpoints that have attributes matching the values are included in the segment. EXCLUSIVE - endpoints that have attributes matching the values are excluded in the segment. CONTAINS - endpoints that have attributes' substrings match the values are included in the segment. BEFORE - endpoints with attributes read as ISO_INSTANT datetimes before the value are included in the segment. AFTER - endpoints with attributes read as ISO_INSTANT datetimes after the value are included in the segment. ON - endpoints with attributes read as ISO_INSTANT dates on the value are included in the segment. Time is ignored in this comparison. BETWEEN - endpoints with attributes read as ISO_INSTANT datetimes between the values are included in the segment.
+ */
+ AttributeType?: AttributeType | string;
+ /**
+ * The criteria values to use for the segment dimension. Depending on the value of the AttributeType property, endpoints are included or excluded from the segment if their attribute values match the criteria values.
+ */
+ Values: string[] | undefined;
+}
+/**
+ * @public
+ * Specifies the settings for events that cause a campaign to be sent.
+ */
+export interface CampaignEventFilter {
+ /**
+ * The dimension settings of the event filter for the campaign.
+ */
+ Dimensions: EventDimensions | undefined;
+ /**
+ * The type of event that causes the campaign to be sent. Valid values are: SYSTEM, sends the campaign when a system event occurs; and, ENDPOINT, sends the campaign when an endpoint event ( Events resource) occurs.
+ */
+ FilterType: FilterType | string | undefined;
+}
+/**
+ * @public
+ * Default button configuration.
+ */
+export interface DefaultButtonConfiguration {
+ /**
+ * The background color of the button.
+ */
+ BackgroundColor?: string;
+ /**
+ * The border radius of the button.
+ */
+ BorderRadius?: number;
+ /**
+ * Action triggered by the button.
+ */
+ ButtonAction: ButtonAction | string | undefined;
+ /**
+ * Button destination.
+ */
+ Link?: string;
+ /**
+ * Button text.
+ */
+ Text: string | undefined;
+ /**
+ * The text color of the button.
+ */
+ TextColor?: string;
+}
+/**
+ * @public
+ * Specifies demographic information about an endpoint, such as the applicable time zone and platform.
+ */
+export interface EndpointDemographic {
+ /**
+ * The version of the app that's associated with the endpoint.
+ */
+ AppVersion?: string;
+ /**
+ * The locale of the endpoint, in the following format: the ISO 639-1 alpha-2 code, followed by an underscore (_), followed by an ISO 3166-1 alpha-2 value.
+ */
+ Locale?: string;
+ /**
+ * The manufacturer of the endpoint device, such as apple or samsung.
+ */
+ Make?: string;
+ /**
+ * The model name or number of the endpoint device, such as iPhone or SM-G900F.
+ */
+ Model?: string;
+ /**
+ * The model version of the endpoint device.
+ */
+ ModelVersion?: string;
+ /**
+ * The platform of the endpoint device, such as ios.
+ */
+ Platform?: string;
+ /**
+ * The platform version of the endpoint device.
+ */
+ PlatformVersion?: string;
+ /**
+ * The time zone of the endpoint, specified as a tz database name value, such as America/Los_Angeles.
+ */
+ Timezone?: string;
+}
+/**
+ * @public
+ * Provides the status code and message that result from processing data for an endpoint.
+ */
+export interface EndpointItemResponse {
+ /**
+ * The custom message that's returned in the response as a result of processing the endpoint data.
+ */
+ Message?: string;
+ /**
+ * The status code that's returned in the response as a result of processing the endpoint data.
+ */
+ StatusCode?: number;
+}
+/**
+ * @public
+ * Specifies geographic information about an endpoint.
+ */
+export interface EndpointLocation {
+ /**
+ * The name of the city where the endpoint is located.
+ */
+ City?: string;
+ /**
+ * The two-character code, in ISO 3166-1 alpha-2 format, for the country or region where the endpoint is located. For example, US for the United States.
+ */
+ Country?: string;
+ /**
+ * The latitude coordinate of the endpoint location, rounded to one decimal place.
+ */
+ Latitude?: number;
+ /**
+ * The longitude coordinate of the endpoint location, rounded to one decimal place.
+ */
+ Longitude?: number;
+ /**
+ * The postal or ZIP code for the area where the endpoint is located.
+ */
+ PostalCode?: string;
+ /**
+ * The name of the region where the endpoint is located. For locations in the United States, this value is the name of a state.
+ */
+ Region?: string;
+}
+/**
+ * @public
+ * Specifies the channel type and other settings for an endpoint.
+ */
+export interface EndpointRequest {
+ /**
+ * The destination address for messages or push notifications that you send to the endpoint. The address varies by channel. For a push-notification channel, use the token provided by the push notification service, such as an Apple Push Notification service (APNs) device token or a Firebase Cloud Messaging (FCM) registration token. For the SMS channel, use a phone number in E.164 format, such as +12065550100. For the email channel, use an email address.
+ */
+ Address?: string;
+ /**
+ * One or more custom attributes that describe the endpoint by associating a name with an array of values. For example, the value of a custom attribute named Interests might be: ["Science", "Music", "Travel"]. You can use these attributes as filter criteria when you create segments. Attribute names are case sensitive.
An attribute name can contain up to 50 characters. An attribute value can contain up to 100 characters. When you define the name of a custom attribute, avoid using the following characters: number sign (#), colon (:), question mark (?), backslash (\), and slash (/). The Amazon Pinpoint console can't display attribute names that contain these characters. This restriction doesn't apply to attribute values.
+ */
+ Attributes?: Record;
+ /**
+ * The channel to use when sending messages or push notifications to the endpoint.
+ */
+ ChannelType?: ChannelType | string;
+ /**
+ * The demographic information for the endpoint, such as the time zone and platform.
+ */
+ Demographic?: EndpointDemographic;
+ /**
+ * The date and time, in ISO 8601 format, when the endpoint is updated.
+ */
+ EffectiveDate?: string;
+ /**
+ * Specifies whether to send messages or push notifications to the endpoint. Valid values are: ACTIVE, messages are sent to the endpoint; and, INACTIVE, messages aren’t sent to the endpoint.
Amazon Pinpoint automatically sets this value to ACTIVE when you create an endpoint or update an existing endpoint. Amazon Pinpoint automatically sets this value to INACTIVE if you update another endpoint that has the same address specified by the Address property.
+ */
+ EndpointStatus?: string;
+ /**
+ * The geographic information for the endpoint.
+ */
+ Location?: EndpointLocation;
+ /**
+ * One or more custom metrics that your app reports to Amazon Pinpoint for the endpoint.
+ */
+ Metrics?: Record;
+ /**
+ * Specifies whether the user who's associated with the endpoint has opted out of receiving messages and push notifications from you. Possible values are: ALL, the user has opted out and doesn't want to receive any messages or push notifications; and, NONE, the user hasn't opted out and wants to receive all messages and push notifications.
+ */
+ OptOut?: string;
+ /**
+ * The unique identifier for the most recent request to update the endpoint.
+ */
+ RequestId?: string;
+ /**
+ * One or more custom attributes that describe the user who's associated with the endpoint.
+ */
+ User?: EndpointUser;
+}
+/**
+ * @public
+ * Specifies data for one or more attributes that describe the user who's associated with an endpoint.
+ */
+export interface EndpointUser {
+ /**
+ * One or more custom attributes that describe the user by associating a name with an array of values. For example, the value of an attribute named Interests might be: ["Science", "Music", "Travel"]. You can use these attributes as filter criteria when you create segments. Attribute names are case sensitive.
An attribute name can contain up to 50 characters. An attribute value can contain up to 100 characters. When you define the name of a custom attribute, avoid using the following characters: number sign (#), colon (:), question mark (?), backslash (\), and slash (/). The Amazon Pinpoint console can't display attribute names that contain these characters. This restriction doesn't apply to attribute values.
+ */
+ UserAttributes?: Record;
+ /**
+ * The unique identifier for the user.
+ */
+ UserId?: string;
+}
+/**
+ * @public
+ * Specifies information about an event that reports data to Amazon Pinpoint.
+ */
+export interface Event {
+ /**
+ * The package name of the app that's recording the event.
+ */
+ AppPackageName?: string;
+ /**
+ * The title of the app that's recording the event.
+ */
+ AppTitle?: string;
+ /**
+ * The version number of the app that's recording the event.
+ */
+ AppVersionCode?: string;
+ /**
+ * One or more custom attributes that are associated with the event.
+ */
+ Attributes?: Record;
+ /**
+ * The version of the SDK that's running on the client device.
+ */
+ ClientSdkVersion?: string;
+ /**
+ * The name of the event.
+ */
+ EventType: string | undefined;
+ /**
+ * One or more custom metrics that are associated with the event.
+ */
+ Metrics?: Record;
+ /**
+ * The name of the SDK that's being used to record the event.
+ */
+ SdkName?: string;
+ /**
+ * Information about the session in which the event occurred.
+ */
+ Session?: Session;
+ /**
+ * The date and time, in ISO 8601 format, when the event occurred.
+ */
+ Timestamp: string | undefined;
+}
+/**
+ * @public
+ * Specifies the dimensions for an event filter that determines when a campaign is sent or a journey activity is performed.
+ */
+export interface EventDimensions {
+ /**
+ * One or more custom attributes that your application reports to Amazon Pinpoint. You can use these attributes as selection criteria when you create an event filter.
+ */
+ Attributes?: Record;
+ /**
+ * The name of the event that causes the campaign to be sent or the journey activity to be performed. This can be a standard event that Amazon Pinpoint generates, such as _email.delivered. For campaigns, this can also be a custom event that's specific to your application. For information about standard events, see Streaming Amazon Pinpoint Events in the Amazon Pinpoint Developer Guide .
+ */
+ EventType?: SetDimension;
+ /**
+ * One or more custom metrics that your application reports to Amazon Pinpoint. You can use these metrics as selection criteria when you create an event filter.
+ */
+ Metrics?: Record;
+}
+/**
+ * @public
+ * Provides the status code and message that result from processing an event.
+ */
+export interface EventItemResponse {
+ /**
+ * A custom message that's returned in the response as a result of processing the event.
+ */
+ Message?: string;
+ /**
+ * The status code that's returned in the response as a result of processing the event. Possible values are: 202, for events that were accepted; and, 400, for events that weren't valid.
+ */
+ StatusCode?: number;
+}
+/**
+ * @public
+ * Specifies a batch of endpoints and events to process.
+ */
+export interface EventsBatch {
+ /**
+ * A set of properties and attributes that are associated with the endpoint.
+ */
+ Endpoint: PublicEndpoint | undefined;
+ /**
+ * A set of properties that are associated with the event.
+ */
+ Events: Record | undefined;
+}
+/**
+ * @public
+ * Specifies a batch of events to process.
+ */
+export interface EventsRequest {
+ /**
+ * The batch of events to process. For each item in a batch, the endpoint ID acts as a key that has an EventsBatch object as its value.
+ */
+ BatchItem: Record | undefined;
+}
+/**
+ * @public
+ * Provides information about endpoints and the events that they're associated with.
+ */
+export interface EventsResponse {
+ /**
+ * A map that contains a multipart response for each endpoint. For each item in this object, the endpoint ID is the key and the item response is the value. If no item response exists, the value can also be one of the following: 202, the request was processed successfully; or 400, the payload wasn't valid or required fields were missing.
+ */
+ Results?: Record;
+}
+/**
+ * @public
+ *
+ * The input for {@link GetInAppMessagesCommand}.
+ */
+export interface GetInAppMessagesCommandInput extends GetInAppMessagesRequest {}
+/**
+ * @public
+ *
+ * The output of {@link GetInAppMessagesCommand}.
+ */
+export interface GetInAppMessagesCommandOutput
+ extends GetInAppMessagesResponse,
+ __MetadataBearer {}
+/**
+ * @public
+ */
+export interface GetInAppMessagesRequest {
+ /**
+ * The unique identifier for the application. This identifier is displayed as the Project ID on the Amazon Pinpoint console.
+ */
+ ApplicationId: string | undefined;
+ /**
+ * The unique identifier for the endpoint.
+ */
+ EndpointId: string | undefined;
+}
+/**
+ * @public
+ */
+export interface GetInAppMessagesResponse {
+ /**
+ * Get in-app messages response object.
+ */
+ InAppMessagesResponse: InAppMessagesResponse | undefined;
+}
+/**
+ * @public
+ * Schedule of the campaign.
+ */
+export interface InAppCampaignSchedule {
+ /**
+ * The scheduled time after which the in-app message should not be shown. Timestamp is in ISO 8601 format.
+ */
+ EndDate?: string;
+ /**
+ * The event filter the SDK has to use to show the in-app message in the application.
+ */
+ EventFilter?: CampaignEventFilter;
+ /**
+ * Time during which the in-app message should not be shown to the user.
+ */
+ QuietTime?: QuietTime;
+}
+/**
+ * @public
+ * Provides all fields required for building an in-app message.
+ */
+export interface InAppMessage {
+ /**
+ * In-app message content.
+ */
+ Content?: InAppMessageContent[];
+ /**
+ * Custom config to be sent to SDK.
+ */
+ CustomConfig?: Record;
+ /**
+ * The layout of the message.
+ */
+ Layout?: Layout | string;
+}
+/**
+ * @public
+ * Text config for Message Body.
+ */
+export interface InAppMessageBodyConfig {
+ /**
+ * The alignment of the text. Valid values: LEFT, CENTER, RIGHT.
+ */
+ Alignment: Alignment | string | undefined;
+ /**
+ * Message Body.
+ */
+ Body: string | undefined;
+ /**
+ * The text color.
+ */
+ TextColor: string | undefined;
+}
+/**
+ * @public
+ * Button Config for an in-app message.
+ */
+export interface InAppMessageButton {
+ /**
+ * Default button content.
+ */
+ Android?: OverrideButtonConfiguration;
+ /**
+ * Default button content.
+ */
+ DefaultConfig?: DefaultButtonConfiguration;
+ /**
+ * Default button content.
+ */
+ IOS?: OverrideButtonConfiguration;
+ /**
+ * Default button content.
+ */
+ Web?: OverrideButtonConfiguration;
+}
+/**
+ * @public
+ * Targeted in-app message campaign.
+ */
+export interface InAppMessageCampaign {
+ /**
+ * Campaign id of the corresponding campaign.
+ */
+ CampaignId?: string;
+ /**
+ * Daily cap which controls the number of times any in-app messages can be shown to the endpoint during a day.
+ */
+ DailyCap?: number;
+ /**
+ * In-app message content with all fields required for rendering an in-app message.
+ */
+ InAppMessage?: InAppMessage;
+ /**
+ * Priority of the in-app message.
+ */
+ Priority?: number;
+ /**
+ * Schedule of the campaign.
+ */
+ Schedule?: InAppCampaignSchedule;
+ /**
+ * Session cap which controls the number of times an in-app message can be shown to the endpoint during an application session.
+ */
+ SessionCap?: number;
+ /**
+ * Total cap which controls the number of times an in-app message can be shown to the endpoint.
+ */
+ TotalCap?: number;
+ /**
+ * Treatment id of the campaign.
+ */
+ TreatmentId?: string;
+}
+/**
+ * @public
+ * The configuration for the message content.
+ */
+export interface InAppMessageContent {
+ /**
+ * The background color for the message.
+ */
+ BackgroundColor?: string;
+ /**
+ * The configuration for the message body.
+ */
+ BodyConfig?: InAppMessageBodyConfig;
+ /**
+ * The configuration for the message header.
+ */
+ HeaderConfig?: InAppMessageHeaderConfig;
+ /**
+ * The image url for the background of message.
+ */
+ ImageUrl?: string;
+ /**
+ * The first button inside the message.
+ */
+ PrimaryBtn?: InAppMessageButton;
+ /**
+ * The second button inside message.
+ */
+ SecondaryBtn?: InAppMessageButton;
+}
+/**
+ * @public
+ * Text config for Message Header.
+ */
+export interface InAppMessageHeaderConfig {
+ /**
+ * The alignment of the text. Valid values: LEFT, CENTER, RIGHT.
+ */
+ Alignment: Alignment | string | undefined;
+ /**
+ * Message Header.
+ */
+ Header: string | undefined;
+ /**
+ * The text color.
+ */
+ TextColor: string | undefined;
+}
+/**
+ * @public
+ * Get in-app messages response object.
+ */
+export interface InAppMessagesResponse {
+ /**
+ * List of targeted in-app message campaigns.
+ */
+ InAppMessageCampaigns?: InAppMessageCampaign[];
+}
+/**
+ * @public
+ * Provides information about the results of a request to create or update an endpoint that's associated with an event.
+ */
+export interface ItemResponse {
+ /**
+ * The response that was received after the endpoint data was accepted.
+ */
+ EndpointItemResponse?: EndpointItemResponse;
+ /**
+ * A multipart response object that contains a key and a value for each event in the request. In each object, the event ID is the key and an EventItemResponse object is the value.
+ */
+ EventsItemResponse?: Record;
+}
+/**
+ * @public
+ * Provides information about an API request or response.
+ */
+export interface MessageBody {
+ /**
+ * The message that's returned from the API.
+ */
+ Message?: string;
+ /**
+ * The unique identifier for the request or response.
+ */
+ RequestID?: string;
+}
+/**
+ * @public
+ * Specifies metric-based criteria for including or excluding endpoints from a segment. These criteria derive from custom metrics that you define for endpoints.
+ */
+export interface MetricDimension {
+ /**
+ * The operator to use when comparing metric values. Valid values are: GREATER_THAN, LESS_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL, and EQUAL.
+ */
+ ComparisonOperator: string | undefined;
+ /**
+ * The value to compare.
+ */
+ Value: number | undefined;
+}
+/**
+ * @public
+ * Override button configuration.
+ */
+export interface OverrideButtonConfiguration {
+ /**
+ * Action triggered by the button.
+ */
+ ButtonAction: ButtonAction | string | undefined;
+ /**
+ * Button destination.
+ */
+ Link?: string;
+}
+/**
+ * @public
+ * Specifies the properties and attributes of an endpoint that's associated with an event.
+ */
+export interface PublicEndpoint {
+ /**
+ * The unique identifier for the recipient, such as a device token, email address, or mobile phone number.
+ */
+ Address?: string;
+ /**
+ * One or more custom attributes that describe the endpoint by associating a name with an array of values. You can use these attributes as filter criteria when you create segments.
+ */
+ Attributes?: Record;
+ /**
+ * The channel that's used when sending messages or push notifications to the endpoint.
+ */
+ ChannelType?: ChannelType | string;
+ /**
+ * The demographic information for the endpoint, such as the time zone and platform.
+ */
+ Demographic?: EndpointDemographic;
+ /**
+ * The date and time, in ISO 8601 format, when the endpoint was last updated.
+ */
+ EffectiveDate?: string;
+ /**
+ * Specifies whether to send messages or push notifications to the endpoint. Valid values are: ACTIVE, messages are sent to the endpoint; and, INACTIVE, messages aren’t sent to the endpoint.
Amazon Pinpoint automatically sets this value to ACTIVE when you create an endpoint or update an existing endpoint. Amazon Pinpoint automatically sets this value to INACTIVE if you update another endpoint that has the same address specified by the Address property.
+ */
+ EndpointStatus?: string;
+ /**
+ * The geographic information for the endpoint.
+ */
+ Location?: EndpointLocation;
+ /**
+ * One or more custom metrics that your app reports to Amazon Pinpoint for the endpoint.
+ */
+ Metrics?: Record;
+ /**
+ * Specifies whether the user who's associated with the endpoint has opted out of receiving messages and push notifications from you. Possible values are: ALL, the user has opted out and doesn't want to receive any messages or push notifications; and, NONE, the user hasn't opted out and wants to receive all messages and push notifications.
+ */
+ OptOut?: string;
+ /**
+ * A unique identifier that's generated each time the endpoint is updated.
+ */
+ RequestId?: string;
+ /**
+ * One or more custom user attributes that your app reports to Amazon Pinpoint for the user who's associated with the endpoint.
+ */
+ User?: EndpointUser;
+}
+/**
+ * @public
+ *
+ * The input for {@link PutEventsCommand}.
+ */
+export interface PutEventsCommandInput extends PutEventsRequest {}
+/**
+ * @public
+ *
+ * The output of {@link PutEventsCommand}.
+ */
+export interface PutEventsCommandOutput
+ extends PutEventsResponse,
+ __MetadataBearer {}
+/**
+ * @public
+ */
+export interface PutEventsRequest {
+ /**
+ * The unique identifier for the application. This identifier is displayed as the Project ID on the Amazon Pinpoint console.
+ */
+ ApplicationId: string | undefined;
+ /**
+ * Specifies a batch of events to process.
+ */
+ EventsRequest: EventsRequest | undefined;
+}
+/**
+ * @public
+ */
+export interface PutEventsResponse {
+ /**
+ * Provides information about endpoints and the events that they're associated with.
+ */
+ EventsResponse: EventsResponse | undefined;
+}
+/**
+ * @public
+ * Specifies the start and end times that define a time range when messages aren't sent to endpoints.
+ */
+export interface QuietTime {
+ /**
+ * The specific time when quiet time ends. This value has to use 24-hour notation and be in HH:MM format, where HH is the hour (with a leading zero, if applicable) and MM is the minutes. For example, use 02:30 to represent 2:30 AM, or 14:30 to represent 2:30 PM.
+ */
+ End?: string;
+ /**
+ * The specific time when quiet time begins. This value has to use 24-hour notation and be in HH:MM format, where HH is the hour (with a leading zero, if applicable) and MM is the minutes. For example, use 02:30 to represent 2:30 AM, or 14:30 to represent 2:30 PM.
+ */
+ Start?: string;
+}
+/**
+ * @public
+ * Provides information about a session.
+ */
+export interface Session {
+ /**
+ * The duration of the session, in milliseconds.
+ */
+ Duration?: number;
+ /**
+ * The unique identifier for the session.
+ */
+ Id: string | undefined;
+ /**
+ * The date and time when the session began.
+ */
+ StartTimestamp: string | undefined;
+ /**
+ * The date and time when the session ended.
+ */
+ StopTimestamp?: string;
+}
+/**
+ * @public
+ * Specifies the dimension type and values for a segment dimension.
+ */
+export interface SetDimension {
+ /**
+ * The type of segment dimension to use. Valid values are: INCLUSIVE, endpoints that match the criteria are included in the segment; and, EXCLUSIVE, endpoints that match the criteria are excluded from the segment.
+ */
+ DimensionType?: DimensionType | string;
+ /**
+ * The criteria values to use for the segment dimension. Depending on the value of the DimensionType property, endpoints are included or excluded from the segment if their values match the criteria values.
+ */
+ Values: string[] | undefined;
+}
+/**
+ * @public
+ *
+ * The input for {@link UpdateEndpointCommand}.
+ */
+export interface UpdateEndpointCommandInput extends UpdateEndpointRequest {}
+/**
+ * @public
+ *
+ * The output of {@link UpdateEndpointCommand}.
+ */
+export interface UpdateEndpointCommandOutput
+ extends UpdateEndpointResponse,
+ __MetadataBearer {}
+/**
+ * @public
+ */
+export interface UpdateEndpointRequest {
+ /**
+ * The unique identifier for the application. This identifier is displayed as the Project ID on the Amazon Pinpoint console.
+ */
+ ApplicationId: string | undefined;
+ /**
+ * The unique identifier for the endpoint.
+ */
+ EndpointId: string | undefined;
+ /**
+ * Specifies the channel type and other settings for an endpoint.
+ */
+ EndpointRequest: EndpointRequest | undefined;
+}
+/**
+ * @public
+ */
+export interface UpdateEndpointResponse {
+ /**
+ * Provides information about an API request or response.
+ */
+ MessageBody: MessageBody | undefined;
+}
+/**
+ * @public
+ */
+export type Alignment = (typeof Alignment)[keyof typeof Alignment];
+/**
+ * @public
+ */
+export type AttributeType = (typeof AttributeType)[keyof typeof AttributeType];
+/**
+ * @public
+ */
+export type ButtonAction = (typeof ButtonAction)[keyof typeof ButtonAction];
+/**
+ * @public
+ */
+export type ChannelType = (typeof ChannelType)[keyof typeof ChannelType];
+/**
+ * @public
+ */
+export type DimensionType = (typeof DimensionType)[keyof typeof DimensionType];
+/**
+ * @public
+ */
+export type FilterType = (typeof FilterType)[keyof typeof FilterType];
+/**
+ * @public
+ */
+export type Layout = (typeof Layout)[keyof typeof Layout];
+
+export {};
diff --git a/packages/core/src/AwsClients/Pinpoint/updateEndpoint.ts b/packages/core/src/AwsClients/Pinpoint/updateEndpoint.ts
index 99ebc4e9a1d..cf7f30216a9 100644
--- a/packages/core/src/AwsClients/Pinpoint/updateEndpoint.ts
+++ b/packages/core/src/AwsClients/Pinpoint/updateEndpoint.ts
@@ -1,10 +1,6 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-import type {
- UpdateEndpointCommandInput as UpdateEndpointInput,
- UpdateEndpointCommandOutput as UpdateEndpointOutput,
-} from '@aws-sdk/client-pinpoint';
import { authenticatedHandler } from '../../clients/handlers/authenticated';
import { composeServiceApi } from '../../clients/internal/composeServiceApi';
import { extendedEncodeURIComponent } from '../../clients/middleware/signing/utils/extendedEncodeURIComponent';
@@ -15,6 +11,10 @@ import {
} from '../../clients/serde';
import { Endpoint, HttpRequest, HttpResponse } from '../../clients/types';
import { defaultConfig, getSharedHeaders } from './base';
+import type {
+ UpdateEndpointCommandInput as UpdateEndpointInput,
+ UpdateEndpointCommandOutput as UpdateEndpointOutput,
+} from './types';
export type { UpdateEndpointInput, UpdateEndpointOutput };
diff --git a/packages/core/tslint.json b/packages/core/tslint.json
index 9ede9f85fac..f4f64297075 100644
--- a/packages/core/tslint.json
+++ b/packages/core/tslint.json
@@ -12,7 +12,7 @@
"limit": 120
}
],
- "no-empty-interface": true,
+ "no-empty-interface": false,
"no-var-keyword": true,
"object-literal-shorthand": true,
"no-eval": true,
diff --git a/packages/notifications/__mocks__/data.ts b/packages/notifications/__mocks__/data.ts
index a6ddd2c8b40..8edd8de5adb 100644
--- a/packages/notifications/__mocks__/data.ts
+++ b/packages/notifications/__mocks__/data.ts
@@ -4,7 +4,7 @@
import type {
Event,
InAppMessageCampaign as PinpointInAppMessage,
-} from '@aws-sdk/client-pinpoint';
+} from '@aws-amplify/core/internals/aws-clients/pinpoint';
import { InAppMessage, InAppMessagingEvent } from '../src/InAppMessaging';
import { PushNotificationMessage } from '../src/PushNotification';
import { UserInfo } from '../src';
diff --git a/packages/notifications/__tests__/InAppMessaging/Providers/AWSPinpointProvider/utils.test.ts b/packages/notifications/__tests__/InAppMessaging/Providers/AWSPinpointProvider/utils.test.ts
index 43ec20a04fa..c5df3686df1 100644
--- a/packages/notifications/__tests__/InAppMessaging/Providers/AWSPinpointProvider/utils.test.ts
+++ b/packages/notifications/__tests__/InAppMessaging/Providers/AWSPinpointProvider/utils.test.ts
@@ -1,7 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-import type { InAppMessageCampaign as PinpointInAppMessage } from '@aws-sdk/client-pinpoint';
+import type { InAppMessageCampaign as PinpointInAppMessage } from '@aws-amplify/core/internals/aws-clients/pinpoint';
import { Amplify, ConsoleLogger, Hub } from '@aws-amplify/core';
import cloneDeep from 'lodash/cloneDeep';
diff --git a/packages/notifications/package.json b/packages/notifications/package.json
index 6e50ff4d9c7..968e0f41da5 100644
--- a/packages/notifications/package.json
+++ b/packages/notifications/package.json
@@ -52,7 +52,6 @@
"@aws-amplify/cache": "5.0.32",
"@aws-amplify/core": "5.3.0",
"@aws-amplify/rtn-push-notification": "1.1.1",
- "@aws-sdk/client-pinpoint": "3.186.1",
"lodash": "^4.17.21",
"uuid": "^3.2.1"
},
diff --git a/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts b/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts
index fdb8e05a9c7..e00becb7ff1 100644
--- a/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts
+++ b/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts
@@ -1,12 +1,11 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-// TODO: [v6] do not leak AWS SDK types to public so we can remove it from runtime dependencies.
-import type { InAppMessageCampaign as PinpointInAppMessage } from '@aws-sdk/client-pinpoint';
import {
getInAppMessages,
GetInAppMessagesInput,
GetInAppMessagesOutput,
+ InAppMessageCampaign as PinpointInAppMessage,
} from '@aws-amplify/core/internals/aws-clients/pinpoint';
import { addEventListener, AWSPinpointProviderCommon } from '../../../common';
diff --git a/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/utils.ts b/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/utils.ts
index e8e869e52a8..29643ff9bcf 100644
--- a/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/utils.ts
+++ b/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/utils.ts
@@ -2,8 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { Amplify, ConsoleLogger, Hub } from '@aws-amplify/core';
-// TODO: [v6] do not leak AWS SDK types to public so we can remove it from runtime dependencies.
-import type { InAppMessageCampaign as PinpointInAppMessage } from '@aws-sdk/client-pinpoint';
+import type { InAppMessageCampaign as PinpointInAppMessage } from '@aws-amplify/core/internals/aws-clients/pinpoint';
import isEmpty from 'lodash/isEmpty';
import { AMPLIFY_SYMBOL } from '../../../common';
import {
diff --git a/packages/notifications/src/PushNotification/Providers/AWSPinpointProvider/utils.ts b/packages/notifications/src/PushNotification/Providers/AWSPinpointProvider/utils.ts
index 679caa35abd..05d372da2ca 100644
--- a/packages/notifications/src/PushNotification/Providers/AWSPinpointProvider/utils.ts
+++ b/packages/notifications/src/PushNotification/Providers/AWSPinpointProvider/utils.ts
@@ -1,7 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-import type { Event as AWSPinpointAnalyticsEvent } from '@aws-sdk/client-pinpoint';
+import type { Event as AWSPinpointAnalyticsEvent } from '@aws-amplify/core/internals/aws-clients/pinpoint';
import { ConsoleLogger, Hub } from '@aws-amplify/core';
import { AMPLIFY_SYMBOL } from '../../../common';
import { PushNotificationMessage } from '../../types';
diff --git a/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts b/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts
index 09b3769e388..a1b45d9d63b 100644
--- a/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts
+++ b/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts
@@ -9,8 +9,8 @@ import {
transferKeyToUpperCase,
} from '@aws-amplify/core';
import { Cache } from '@aws-amplify/cache';
-import type { Event as AWSPinpointAnalyticsEvent } from '@aws-sdk/client-pinpoint';
import {
+ Event as AWSPinpointAnalyticsEvent,
putEvents,
PutEventsInput,
updateEndpoint,
diff --git a/scripts/dts-bundler/README.md b/scripts/dts-bundler/README.md
new file mode 100644
index 00000000000..ce6b8459f27
--- /dev/null
+++ b/scripts/dts-bundler/README.md
@@ -0,0 +1,10 @@
+This project is used to rollup the TS types from the AWS SDK into the custom AWS clients. You can regenerate them
+by running the `build` script in this project, and commit the generated file changes.
+
+To update the generated types files, you need to:
+
+1. Update existing `*.d.ts` files in this folder or add new ones.
+1. If new `*.d.ts` file is added, update the `dts-bundler.config.js` with additional entries.
+1. Run the generating script `yarn && yarn build`. The generated files will be shown in the console.
+1. Inspect generated files and make sure headers are not changed.
+1. Commit the changes
diff --git a/scripts/dts-bundler/cognito-identity.d.ts b/scripts/dts-bundler/cognito-identity.d.ts
new file mode 100644
index 00000000000..c53d69e8d03
--- /dev/null
+++ b/scripts/dts-bundler/cognito-identity.d.ts
@@ -0,0 +1,15 @@
+import {
+ GetIdCommandInput,
+ GetIdCommandOutput,
+ GetCredentialsForIdentityCommandInput,
+ GetCredentialsForIdentityCommandOutput,
+ Credentials,
+} from '@aws-sdk/client-cognito-identity';
+
+export {
+ GetIdCommandInput,
+ GetIdCommandOutput,
+ GetCredentialsForIdentityCommandInput,
+ GetCredentialsForIdentityCommandOutput,
+ Credentials,
+};
diff --git a/scripts/dts-bundler/dts-bundler.config.js b/scripts/dts-bundler/dts-bundler.config.js
new file mode 100644
index 00000000000..b4140b88889
--- /dev/null
+++ b/scripts/dts-bundler/dts-bundler.config.js
@@ -0,0 +1,32 @@
+const outputConfig = {
+ sortNodes: true,
+ respectPreserveConstEnum: true,
+ noBanner: true,
+};
+
+/** @type import('dts-bundle-generator/config-schema').BundlerConfig */
+const config = {
+ compilationOptions: {
+ preferredConfigPath: '../../packages/tsconfig.base.json',
+ },
+ entries: [
+ {
+ filePath: './pinpoint.d.ts',
+ outFile: '../../packages/core/src/AwsClients/Pinpoint/types.ts',
+ libraries: {
+ inlinedLibraries: ['@aws-sdk/client-pinpoint'],
+ },
+ output: outputConfig,
+ },
+ {
+ filePath: './cognito-identity.d.ts',
+ outFile: '../../packages/core/src/AwsClients/CognitoIdentity/types.ts',
+ libraries: {
+ inlinedLibraries: ['@aws-sdk/client-cognito-identity'],
+ },
+ output: outputConfig,
+ },
+ ],
+};
+
+module.exports = config;
diff --git a/scripts/dts-bundler/package.json b/scripts/dts-bundler/package.json
new file mode 100644
index 00000000000..df674fd7332
--- /dev/null
+++ b/scripts/dts-bundler/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "api-extract-aws-clients",
+ "devDependencies": {
+ "@aws-sdk/client-pinpoint": "3.335.0",
+ "@aws-sdk/client-cognito-identity": "3.335.0",
+ "dts-bundle-generator": "^8.0.1"
+ },
+ "scripts": {
+ "build": "dts-bundle-generator --config ./dts-bundler.config.js"
+ }
+}
\ No newline at end of file
diff --git a/scripts/dts-bundler/pinpoint.d.ts b/scripts/dts-bundler/pinpoint.d.ts
new file mode 100644
index 00000000000..9608b781166
--- /dev/null
+++ b/scripts/dts-bundler/pinpoint.d.ts
@@ -0,0 +1,21 @@
+import {
+ GetInAppMessagesCommandInput,
+ GetInAppMessagesCommandOutput,
+ UpdateEndpointCommandInput,
+ UpdateEndpointCommandOutput,
+ PutEventsCommandInput,
+ PutEventsCommandOutput,
+ Event,
+ InAppMessageCampaign,
+} from '@aws-sdk/client-pinpoint';
+
+export {
+ GetInAppMessagesCommandInput,
+ GetInAppMessagesCommandOutput,
+ UpdateEndpointCommandInput,
+ UpdateEndpointCommandOutput,
+ PutEventsCommandInput,
+ PutEventsCommandOutput,
+ Event,
+ InAppMessageCampaign,
+};
diff --git a/scripts/dts-bundler/tsconfig.json b/scripts/dts-bundler/tsconfig.json
new file mode 100644
index 00000000000..3a236aa798e
--- /dev/null
+++ b/scripts/dts-bundler/tsconfig.json
@@ -0,0 +1,3 @@
+{
+ "extends": "../../../tsconfig.base.json"
+}
From 62fa528196c5ee814f737e6e6a33eec362a4269b Mon Sep 17 00:00:00 2001
From: AllanZhengYP
Date: Wed, 24 May 2023 16:07:19 -0700
Subject: [PATCH 31/41] chore(storage): update size limit (#11406)
---
packages/storage/package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/storage/package.json b/packages/storage/package.json
index 312283f57f8..98de1e1b5c6 100644
--- a/packages/storage/package.json
+++ b/packages/storage/package.json
@@ -60,7 +60,7 @@
"name": "Storage (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Storage }",
- "limit": "84.85 kB"
+ "limit": "85.07 kB"
}
],
"jest": {
From f576be5ad4fd3e6625d5bc555fa8cbd4e1783f06 Mon Sep 17 00:00:00 2001
From: AllanZhengYP
Date: Tue, 30 May 2023 13:28:25 -0700
Subject: [PATCH 32/41] fix(clients): middleware chain revert after every
invocation (#11432)
---
.../clients/composeTransferHandler-test.ts | 14 +++++++++++++-
.../src/clients/internal/composeTransferHandler.ts | 3 ++-
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/packages/core/__tests__/clients/composeTransferHandler-test.ts b/packages/core/__tests__/clients/composeTransferHandler-test.ts
index fb0cad674de..fef390e1a9f 100644
--- a/packages/core/__tests__/clients/composeTransferHandler-test.ts
+++ b/packages/core/__tests__/clients/composeTransferHandler-test.ts
@@ -44,7 +44,7 @@ describe(composeTransferHandler.name, () => {
};
const coreHandler: TransferHandler = jest
.fn()
- .mockResolvedValue({ body: '' } as Response);
+ .mockResolvedValueOnce({ body: '' } as Response);
const handler = composeTransferHandler<[OptionsType, OptionsType]>(
coreHandler,
[middlewareA, middlewareB]
@@ -64,5 +64,17 @@ describe(composeTransferHandler.name, () => {
// Validate middleware share a same option object
expect(options.mockFnInOptions).toHaveBeenNthCalledWith(1, 'A');
expect(options.mockFnInOptions).toHaveBeenNthCalledWith(2, 'B');
+
+ // order does not change in consecutive calls
+ (coreHandler as jest.Mock).mockResolvedValueOnce({ body: '' } as Response);
+ const resp2 = await handler(
+ { url: new URL('https://a.b'), body: '' },
+ options
+ );
+ expect(resp2).toEqual({ body: 'BA' });
+ expect(coreHandler).toBeCalledWith(
+ expect.objectContaining({ body: 'AB' }),
+ expect.anything()
+ );
});
});
diff --git a/packages/core/src/clients/internal/composeTransferHandler.ts b/packages/core/src/clients/internal/composeTransferHandler.ts
index 81f26d6bba8..fd9deeddbd4 100644
--- a/packages/core/src/clients/internal/composeTransferHandler.ts
+++ b/packages/core/src/clients/internal/composeTransferHandler.ts
@@ -41,7 +41,8 @@ export const composeTransferHandler =
let composedHandler: MiddlewareHandler = (
request: Request
) => coreHandler(request, options);
- for (const m of middleware.reverse()) {
+ for (let i = middleware.length - 1; i >= 0; i--) {
+ const m = middleware[i];
const resolvedMiddleware = m(options);
composedHandler = resolvedMiddleware(composedHandler, context);
}
From 47ff2e1baac071b3e25296c7b984d914a4e2532e Mon Sep 17 00:00:00 2001
From: Allan Zheng
Date: Wed, 31 May 2023 13:31:28 -0700
Subject: [PATCH 33/41] chore: prepare for release; split out data packages
diffs
---
.circleci/config.yml | 1 -
.github/workflows/issue-pending-response.yml | 2 +-
.github/workflows/snyk-security.yml | 2 +-
package.json | 1 -
.../amazon-cognito-identity-js/CHANGELOG.md | 37 ++++-
packages/amazon-cognito-identity-js/README.md | 133 +++++++++---------
.../amazon-cognito-identity-js/index.d.ts | 6 +-
packages/api-graphql/package.json | 2 +-
packages/api-rest/package.json | 2 +-
packages/api/package.json | 2 +-
packages/datastore/package.json | 2 +-
11 files changed, 101 insertions(+), 89 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index e0961646c48..37a34ee8c6a 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1632,7 +1632,6 @@ releasable_branches: &releasable_branches
- release
- main
- next
- - v5/custom-clients
# List of test browsers that are always used in every E2E test. Tests that aren't expected to interact with browser APIs
# should use `minimal_browser_list` to keep test execution time low.
diff --git a/.github/workflows/issue-pending-response.yml b/.github/workflows/issue-pending-response.yml
index d69cd643bcd..3a09d137621 100644
--- a/.github/workflows/issue-pending-response.yml
+++ b/.github/workflows/issue-pending-response.yml
@@ -12,4 +12,4 @@ jobs:
- uses: siegerts/pending-author-response@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- pending-response-label: pending-response
+ pending-response-label: pending-response
\ No newline at end of file
diff --git a/.github/workflows/snyk-security.yml b/.github/workflows/snyk-security.yml
index c1000a48191..0f20e0a1f11 100644
--- a/.github/workflows/snyk-security.yml
+++ b/.github/workflows/snyk-security.yml
@@ -2,7 +2,7 @@ name: 'Snyk Security'
on:
push:
- branches: ['release']
+ branches: ["release"]
schedule:
- cron: '0 0 * * *' # run daily at midnight GMT
diff --git a/package.json b/package.json
index b4c8f72c6c2..4cb8fbd7732 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,6 @@
"publish:next": "lerna publish --canary --force-publish \"*\" --yes --dist-tag=next --preid=next${PREID_HASH_SUFFIX} --exact --no-verify-access",
"publish:release": "lerna publish --conventional-commits --yes --message 'chore(release): Publish [ci skip]' --no-verify-access",
"publish:verdaccio": "lerna publish --no-push --canary minor --dist-tag=unstable --preid=unstable --exact --force-publish --yes --no-verify-access",
- "publish:v5/custom-clients": "lerna publish --canary --force-publish \"*\" --yes --dist-tag=v5-custom-clients --preid=v5-custom-clients${PREID_HASH_SUFFIX} --exact --no-verify-access",
"ts-coverage": "lerna run ts-coverage"
},
"husky": {
diff --git a/packages/amazon-cognito-identity-js/CHANGELOG.md b/packages/amazon-cognito-identity-js/CHANGELOG.md
index a1dd8369fa9..ac36f7539cb 100644
--- a/packages/amazon-cognito-identity-js/CHANGELOG.md
+++ b/packages/amazon-cognito-identity-js/CHANGELOG.md
@@ -5,37 +5,62 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
# [6.2.0](https://github.com/aws-amplify/amplify-js/compare/amazon-cognito-identity-js@6.1.2...amazon-cognito-identity-js@6.2.0) (2023-03-23)
+
### Features
-- **cognito:** make cookiestorage constructor parameter optional ([9b8e759](https://github.com/aws-amplify/amplify-js/commit/9b8e75973762ce498269de0162a3e70038238f0f))
-- **cognito:** remove required domain param when create CookieStorage ([c985454](https://github.com/aws-amplify/amplify-js/commit/c985454b0af9f2205941ab95d6ec8c42748ab63d))
+* **cognito:** make cookiestorage constructor parameter optional ([9b8e759](https://github.com/aws-amplify/amplify-js/commit/9b8e75973762ce498269de0162a3e70038238f0f))
+* **cognito:** remove required domain param when create CookieStorage ([c985454](https://github.com/aws-amplify/amplify-js/commit/c985454b0af9f2205941ab95d6ec8c42748ab63d))
+
+
+
+
## [6.1.2](https://github.com/aws-amplify/amplify-js/compare/amazon-cognito-identity-js@6.1.1...amazon-cognito-identity-js@6.1.2) (2022-12-27)
+
### Bug Fixes
-- **amazon-cognito-identity-js:** sets no-store header for cognito user pools ([#10804](https://github.com/aws-amplify/amplify-js/issues/10804)) ([b9ec192](https://github.com/aws-amplify/amplify-js/commit/b9ec1926c8ba3d2d0b2289a9b84090fb5d724ad9))
+* **amazon-cognito-identity-js:** sets no-store header for cognito user pools ([#10804](https://github.com/aws-amplify/amplify-js/issues/10804)) ([b9ec192](https://github.com/aws-amplify/amplify-js/commit/b9ec1926c8ba3d2d0b2289a9b84090fb5d724ad9))
+
+
+
+
## [6.1.1](https://github.com/aws-amplify/amplify-js/compare/amazon-cognito-identity-js@6.1.0...amazon-cognito-identity-js@6.1.1) (2022-12-16)
+
### Bug Fixes
-- **amazon-cognito-identity-js:** specify the correct userAgent/deviceName when remembering devices (React Native) ([#10724](https://github.com/aws-amplify/amplify-js/issues/10724)) ([01a5b84](https://github.com/aws-amplify/amplify-js/commit/01a5b84ea010f7fb66c4e19e73301cce82fc7370))
+* **amazon-cognito-identity-js:** specify the correct userAgent/deviceName when remembering devices (React Native) ([#10724](https://github.com/aws-amplify/amplify-js/issues/10724)) ([01a5b84](https://github.com/aws-amplify/amplify-js/commit/01a5b84ea010f7fb66c4e19e73301cce82fc7370))
+
+
+
+
# [6.1.0](https://github.com/aws-amplify/amplify-js/compare/amazon-cognito-identity-js@6.0.1...amazon-cognito-identity-js@6.1.0) (2022-12-15)
+
### Bug Fixes
-- **core:** add cache-control header to cognito identity client ([#10753](https://github.com/aws-amplify/amplify-js/issues/10753)) ([dfbabaf](https://github.com/aws-amplify/amplify-js/commit/dfbabaf54dda902f1f77c4501e78f49e6a9397af))
+* **core:** add cache-control header to cognito identity client ([#10753](https://github.com/aws-amplify/amplify-js/issues/10753)) ([dfbabaf](https://github.com/aws-amplify/amplify-js/commit/dfbabaf54dda902f1f77c4501e78f49e6a9397af))
+
### Features
-- **auth,cognito-identity-js:** returning code delivery details as part of callback for updateAttributes, adds hub event to Auth.updateUserAttributes ([#10731](https://github.com/aws-amplify/amplify-js/issues/10731)) ([fc4940b](https://github.com/aws-amplify/amplify-js/commit/fc4940bc17e0deeb9e9ca2a00bed101e8ff7d3df))
+* **auth,cognito-identity-js:** returning code delivery details as part of callback for updateAttributes, adds hub event to Auth.updateUserAttributes ([#10731](https://github.com/aws-amplify/amplify-js/issues/10731)) ([fc4940b](https://github.com/aws-amplify/amplify-js/commit/fc4940bc17e0deeb9e9ca2a00bed101e8ff7d3df))
+
+
+
+
## [6.0.1](https://github.com/aws-amplify/amplify-js/compare/amazon-cognito-identity-js@6.0.0...amazon-cognito-identity-js@6.0.1) (2022-11-11)
**Note:** Version bump only for package amazon-cognito-identity-js
+
+
+
+
# [6.0.0](https://github.com/aws-amplify/amplify-js/compare/amazon-cognito-identity-js@5.2.12...amazon-cognito-identity-js@6.0.0) (2022-11-09)
### Features
diff --git a/packages/amazon-cognito-identity-js/README.md b/packages/amazon-cognito-identity-js/README.md
index 9f79bbc389a..df742d39c9f 100644
--- a/packages/amazon-cognito-identity-js/README.md
+++ b/packages/amazon-cognito-identity-js/README.md
@@ -219,20 +219,17 @@ var attributePhoneNumber = new AmazonCognitoIdentity.CognitoUserAttribute(
attributeList.push(attributeEmail);
attributeList.push(attributePhoneNumber);
-userPool.signUp(
- 'username',
- 'password',
- attributeList,
- null,
- function (err, result) {
- if (err) {
- alert(err.message || JSON.stringify(err));
- return;
- }
- var cognitoUser = result.user;
- console.log('user name is ' + cognitoUser.getUsername());
+userPool.signUp('username', 'password', attributeList, null, function(
+ err,
+ result
+) {
+ if (err) {
+ alert(err.message || JSON.stringify(err));
+ return;
}
-);
+ var cognitoUser = result.user;
+ console.log('user name is ' + cognitoUser.getUsername());
+});
```
**Use case 2.** Confirming a registered, unauthenticated user using a confirmation code received via SMS.
@@ -250,7 +247,7 @@ var userData = {
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
-cognitoUser.confirmRegistration('123456', true, function (err, result) {
+cognitoUser.confirmRegistration('123456', true, function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
@@ -262,7 +259,7 @@ cognitoUser.confirmRegistration('123456', true, function (err, result) {
**Use case 3.** Resending a confirmation code via SMS for confirming registration for a unauthenticated user.
```javascript
-cognitoUser.resendConfirmationCode(function (err, result) {
+cognitoUser.resendConfirmationCode(function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
@@ -294,7 +291,7 @@ var userData = {
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
- onSuccess: function (result) {
+ onSuccess: function(result) {
var accessToken = result.getAccessToken().getJwtToken();
//POTENTIAL: Region needs to be set if not already set previously elsewhere.
@@ -322,7 +319,7 @@ cognitoUser.authenticateUser(authenticationDetails, {
});
},
- onFailure: function (err) {
+ onFailure: function(err) {
alert(err.message || JSON.stringify(err));
},
});
@@ -335,7 +332,7 @@ Note also that if CognitoUser.authenticateUser throws ReferenceError: navigator
**Use case 5.** Retrieve user attributes for an authenticated user.
```javascript
-cognitoUser.getUserAttributes(function (err, result) {
+cognitoUser.getUserAttributes(function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
@@ -354,13 +351,13 @@ Note that the inputVerificationCode method needs to be defined but does not need
```javascript
cognitoUser.getAttributeVerificationCode('email', {
- onSuccess: function (result) {
+ onSuccess: function(result) {
console.log('call result: ' + result);
},
- onFailure: function (err) {
+ onFailure: function(err) {
alert(err.message || JSON.stringify(err));
},
- inputVerificationCode: function () {
+ inputVerificationCode: function() {
var verificationCode = prompt('Please input verification code: ', '');
cognitoUser.verifyAttribute('email', verificationCode, this);
},
@@ -373,7 +370,7 @@ cognitoUser.getAttributeVerificationCode('email', {
var attributeList = [];
attributeList.push('nickname');
-cognitoUser.deleteAttributes(attributeList, function (err, result) {
+cognitoUser.deleteAttributes(attributeList, function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
@@ -393,7 +390,7 @@ var attribute = {
var attribute = new AmazonCognitoIdentity.CognitoUserAttribute(attribute);
attributeList.push(attribute);
-cognitoUser.updateAttributes(attributeList, function (err, result) {
+cognitoUser.updateAttributes(attributeList, function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
@@ -407,7 +404,7 @@ cognitoUser.updateAttributes(attributeList, function (err, result) {
Note: this method is now deprecated. Please use `setUserMfaPreference` instead.
```javascript
-cognitoUser.enableMFA(function (err, result) {
+cognitoUser.enableMFA(function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
@@ -421,7 +418,7 @@ cognitoUser.enableMFA(function (err, result) {
Note: this method is now deprecated. Please use `setUserMfaPreference` instead.
```javascript
-cognitoUser.disableMFA(function (err, result) {
+cognitoUser.disableMFA(function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
@@ -433,17 +430,13 @@ cognitoUser.disableMFA(function (err, result) {
**Use case 11.** Changing the current password for an authenticated user.
```javascript
-cognitoUser.changePassword(
- 'oldPassword',
- 'newPassword',
- function (err, result) {
- if (err) {
- alert(err.message || JSON.stringify(err));
- return;
- }
- console.log('call result: ' + result);
+cognitoUser.changePassword('oldPassword', 'newPassword', function(err, result) {
+ if (err) {
+ alert(err.message || JSON.stringify(err));
+ return;
}
-);
+ console.log('call result: ' + result);
+});
```
**Use case 12.** Starting and completing a forgot password flow for an unauthenticated user.
@@ -463,15 +456,15 @@ For example:
```javascript
cognitoUser.forgotPassword({
- onSuccess: function (data) {
+ onSuccess: function(data) {
// successfully initiated reset password request
console.log('CodeDeliveryData from forgotPassword: ' + data);
},
- onFailure: function (err) {
+ onFailure: function(err) {
alert(err.message || JSON.stringify(err));
},
//Optional automatic callback
- inputVerificationCode: function (data) {
+ inputVerificationCode: function(data) {
console.log('Code sent to: ' + data);
var verificationCode = document.getElementById('code').value;
var newPassword = document.getElementById('new_password').value;
@@ -490,7 +483,7 @@ cognitoUser.forgotPassword({
**Use case 13.** Deleting an authenticated user.
```javascript
-cognitoUser.deleteUser(function (err, result) {
+cognitoUser.deleteUser(function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
@@ -522,7 +515,7 @@ var poolData = {
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
-userPool.storage.sync(function (err, result) {
+userPool.storage.sync(function(err, result) {
if (err) {
} else if (result === 'SUCCESS') {
var cognitoUser = userPool.getCurrentUser();
@@ -542,7 +535,7 @@ var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var cognitoUser = userPool.getCurrentUser();
if (cognitoUser != null) {
- cognitoUser.getSession(function (err, session) {
+ cognitoUser.getSession(function(err, session) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
@@ -550,7 +543,7 @@ if (cognitoUser != null) {
console.log('session validity: ' + session.isValid());
// NOTE: getSession must be called to authenticate user before calling getUserAttributes
- cognitoUser.getUserAttributes(function (err, attributes) {
+ cognitoUser.getUserAttributes(function(err, attributes) {
if (err) {
// Handle error
} else {
@@ -580,7 +573,7 @@ if (cognitoUser != null) {
var cognitoUser = userPool.getCurrentUser();
if (cognitoUser != null) {
- cognitoUser.getSession(function (err, result) {
+ cognitoUser.getSession(function(err, result) {
if (result) {
console.log('You are now logged in.');
@@ -615,10 +608,10 @@ _note that you can not replace the login key with a variable because it will be
```javascript
cognitoUser.listDevices(limit, paginationToken, {
- onSuccess: function (result) {
+ onSuccess: function(result) {
console.log('call result: ' + result);
},
- onFailure: function (err) {
+ onFailure: function(err) {
alert(err.message);
},
});
@@ -628,10 +621,10 @@ cognitoUser.listDevices(limit, paginationToken, {
```javascript
cognitoUser.getDevice({
- onSuccess: function (result) {
+ onSuccess: function(result) {
console.log('call result: ' + result);
},
- onFailure: function (err) {
+ onFailure: function(err) {
alert(err.message || JSON.stringify(err));
},
});
@@ -641,10 +634,10 @@ cognitoUser.getDevice({
```javascript
cognitoUser.setDeviceStatusRemembered({
- onSuccess: function (result) {
+ onSuccess: function(result) {
console.log('call result: ' + result);
},
- onFailure: function (err) {
+ onFailure: function(err) {
alert(err.message || JSON.stringify(err));
},
});
@@ -654,10 +647,10 @@ cognitoUser.setDeviceStatusRemembered({
```javascript
cognitoUser.setDeviceStatusNotRemembered({
- onSuccess: function (result) {
+ onSuccess: function(result) {
console.log('call result: ' + result);
},
- onFailure: function (err) {
+ onFailure: function(err) {
alert(err.message || JSON.stringify(err));
},
});
@@ -667,10 +660,10 @@ cognitoUser.setDeviceStatusNotRemembered({
```javascript
cognitoUser.forgetDevice({
- onSuccess: function (result) {
+ onSuccess: function(result) {
console.log('call result: ' + result);
},
- onFailure: function (err) {
+ onFailure: function(err) {
alert(err.message || JSON.stringify(err));
},
});
@@ -749,13 +742,13 @@ E.g.
cognitoUser.setAuthenticationFlowType('CUSTOM_AUTH');
cognitoUser.initiateAuth(authenticationDetails, {
- onSuccess: function (result) {
+ onSuccess: function(result) {
// User authentication was successful
},
- onFailure: function (err) {
+ onFailure: function(err) {
// User authentication was not successful
},
- customChallenge: function (challengeParameters) {
+ customChallenge: function(challengeParameters) {
// User authentication depends on challenge response
var challengeResponses = 'challenge-answer';
cognitoUser.sendCustomChallengeAnswer(challengeResponses, this);
@@ -813,34 +806,34 @@ var userData = {
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
- onSuccess: function (result) {
+ onSuccess: function(result) {
var accessToken = result.getAccessToken().getJwtToken();
},
- onFailure: function (err) {
+ onFailure: function(err) {
alert(err.message || JSON.stringify(err));
},
- mfaSetup: function (challengeName, challengeParameters) {
+ mfaSetup: function(challengeName, challengeParameters) {
cognitoUser.associateSoftwareToken(this);
},
- associateSecretCode: function (secretCode) {
+ associateSecretCode: function(secretCode) {
var challengeAnswer = prompt('Please input the TOTP code.', '');
cognitoUser.verifySoftwareToken(challengeAnswer, 'My TOTP device', this);
},
- selectMFAType: function (challengeName, challengeParameters) {
+ selectMFAType: function(challengeName, challengeParameters) {
var mfaType = prompt('Please select the MFA method.', ''); // valid values for mfaType is "SMS_MFA", "SOFTWARE_TOKEN_MFA"
cognitoUser.sendMFASelectionAnswer(mfaType, this);
},
- totpRequired: function (secretCode) {
+ totpRequired: function(secretCode) {
var challengeAnswer = prompt('Please input the TOTP code.', '');
cognitoUser.sendMFACode(challengeAnswer, this, 'SOFTWARE_TOKEN_MFA');
},
- mfaRequired: function (codeDeliveryDetails) {
+ mfaRequired: function(codeDeliveryDetails) {
var verificationCode = prompt('Please input verification code', '');
cognitoUser.sendMFACode(verificationCode, this);
},
@@ -854,7 +847,7 @@ var smsMfaSettings = {
PreferredMfa: true,
Enabled: true,
};
-cognitoUser.setUserMfaPreference(smsMfaSettings, null, function (err, result) {
+cognitoUser.setUserMfaPreference(smsMfaSettings, null, function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
}
@@ -869,7 +862,7 @@ var totpMfaSettings = {
PreferredMfa: true,
Enabled: true,
};
-cognitoUser.setUserMfaPreference(null, totpMfaSettings, function (err, result) {
+cognitoUser.setUserMfaPreference(null, totpMfaSettings, function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
}
@@ -883,13 +876,13 @@ cognitoUser.setUserMfaPreference(null, totpMfaSettings, function (err, result) {
cognitoUser.setAuthenticationFlowType('USER_PASSWORD_AUTH');
cognitoUser.authenticateUser(authenticationDetails, {
- onSuccess: function (result) {
+ onSuccess: function(result) {
// User authentication was successful
},
- onFailure: function (err) {
+ onFailure: function(err) {
// User authentication was not successful
},
- mfaRequired: function (codeDeliveryDetails) {
+ mfaRequired: function(codeDeliveryDetails) {
// MFA is required to complete user authentication.
// Get the code from user and call
cognitoUser.sendMFACode(verificationCode, this);
@@ -900,7 +893,7 @@ cognitoUser.authenticateUser(authenticationDetails, {
**Use case 31.** Retrieve the user data for an authenticated user.
```js
-cognitoUser.getUserData(function (err, userData) {
+cognitoUser.getUserData(function(err, userData) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
@@ -911,7 +904,7 @@ cognitoUser.getUserData(function (err, userData) {
// If you want to force to get the user data from backend,
// you can set the bypassCache to true
cognitoUser.getUserData(
- function (err, userData) {
+ function(err, userData) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
diff --git a/packages/amazon-cognito-identity-js/index.d.ts b/packages/amazon-cognito-identity-js/index.d.ts
index 0a32f2c9fc5..5a0d26fba0a 100644
--- a/packages/amazon-cognito-identity-js/index.d.ts
+++ b/packages/amazon-cognito-identity-js/index.d.ts
@@ -2,11 +2,7 @@ declare module 'amazon-cognito-identity-js' {
//import * as AWS from "aws-sdk";
export type NodeCallback = (err?: E, result?: T) => void;
- export type UpdateAttributesNodeCallback = (
- err?: E,
- result?: T,
- details?: K
- ) => void;
+ export type UpdateAttributesNodeCallback = (err?: E, result?: T, details?: K) => void;
export namespace NodeCallback {
export type Any = NodeCallback;
}
diff --git a/packages/api-graphql/package.json b/packages/api-graphql/package.json
index 847f5b51799..08d0bd62673 100644
--- a/packages/api-graphql/package.json
+++ b/packages/api-graphql/package.json
@@ -64,7 +64,7 @@
"name": "API (GraphQL client)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, GraphQLAPI }",
- "limit": "87.1 kB"
+ "limit": "104.6 kB"
}
],
"jest": {
diff --git a/packages/api-rest/package.json b/packages/api-rest/package.json
index c0c6d9b816d..8485497875f 100644
--- a/packages/api-rest/package.json
+++ b/packages/api-rest/package.json
@@ -55,7 +55,7 @@
"name": "API (rest client)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, RestAPI }",
- "limit": "29.75 kB"
+ "limit": "47.9 kB"
}
],
"jest": {
diff --git a/packages/api/package.json b/packages/api/package.json
index b716540ff1f..13149d58fc8 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -66,7 +66,7 @@
"name": "API (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, API }",
- "limit": "88.35 kB"
+ "limit": "106.1 kB"
}
],
"jest": {
diff --git a/packages/datastore/package.json b/packages/datastore/package.json
index 84c360ad389..1c06ded72e6 100644
--- a/packages/datastore/package.json
+++ b/packages/datastore/package.json
@@ -72,7 +72,7 @@
"name": "DataStore (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, DataStore }",
- "limit": "136.5 kB"
+ "limit": "155 kB"
}
],
"jest": {
From 2d6eecfa4763a6cfb6aeaabedd49a530c6420dcd Mon Sep 17 00:00:00 2001
From: Allan Zheng
Date: Thu, 1 Jun 2023 15:29:10 -0700
Subject: [PATCH 34/41] fix: address feedbacks
---
packages/api-graphql/package.json | 2 +-
packages/api-rest/package.json | 2 +-
packages/api/package.json | 2 +-
packages/core/src/Credentials.ts | 12 ++++++------
packages/core/src/clients/handlers/fetch.ts | 19 ++++++++++++-------
.../middleware/retry/jitteredBackoff.ts | 10 ++++++----
.../clients/middleware/signing/middleware.ts | 2 +-
.../utils/extendedEncodeURIComponent.ts | 1 +
.../middleware/signing/utils/isClockSkewed.ts | 6 +++---
.../middleware/userAgent/middleware.ts | 7 ++++++-
packages/core/src/clients/serde/json.ts | 2 --
packages/datastore/package.json | 2 +-
packages/geo/package.json | 2 +-
.../Providers/AWSPinpointProvider/index.ts | 1 +
.../common/AWSPinpointProviderCommon/index.ts | 6 +++---
15 files changed, 44 insertions(+), 32 deletions(-)
diff --git a/packages/api-graphql/package.json b/packages/api-graphql/package.json
index 08d0bd62673..847f5b51799 100644
--- a/packages/api-graphql/package.json
+++ b/packages/api-graphql/package.json
@@ -64,7 +64,7 @@
"name": "API (GraphQL client)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, GraphQLAPI }",
- "limit": "104.6 kB"
+ "limit": "87.1 kB"
}
],
"jest": {
diff --git a/packages/api-rest/package.json b/packages/api-rest/package.json
index 8485497875f..c0c6d9b816d 100644
--- a/packages/api-rest/package.json
+++ b/packages/api-rest/package.json
@@ -55,7 +55,7 @@
"name": "API (rest client)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, RestAPI }",
- "limit": "47.9 kB"
+ "limit": "29.75 kB"
}
],
"jest": {
diff --git a/packages/api/package.json b/packages/api/package.json
index 13149d58fc8..b716540ff1f 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -66,7 +66,7 @@
"name": "API (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, API }",
- "limit": "106.1 kB"
+ "limit": "88.35 kB"
}
],
"jest": {
diff --git a/packages/core/src/Credentials.ts b/packages/core/src/Credentials.ts
index 32f4b08d1ff..5262ec8098a 100644
--- a/packages/core/src/Credentials.ts
+++ b/packages/core/src/Credentials.ts
@@ -282,7 +282,7 @@ export class CredentialsClass {
const cognitoConfig = { region: identityPoolRegion ?? region };
- const credentialsProvider = async () => {
+ const guestCredentialsProvider = async () => {
if (!identityId) {
const { IdentityId } = await getId(cognitoConfig, {
IdentityPoolId: identityPoolId,
@@ -300,7 +300,7 @@ export class CredentialsClass {
expiration: Credentials.Expiration,
};
};
- let credentials = credentialsProvider().catch(async err => {
+ let credentials = guestCredentialsProvider().catch(async err => {
throw err;
});
@@ -318,7 +318,7 @@ export class CredentialsClass {
logger.debug('Failed to load guest credentials');
await this._removeGuestIdentityId();
- const credentialsProvider = async () => {
+ const guestCredentialsProvider = async () => {
const { IdentityId } = await getId(cognitoConfig, {
IdentityPoolId: identityPoolId,
});
@@ -339,7 +339,7 @@ export class CredentialsClass {
};
};
- credentials = credentialsProvider().catch(async err => {
+ credentials = guestCredentialsProvider().catch(async err => {
throw err;
});
@@ -383,7 +383,7 @@ export class CredentialsClass {
const cognitoConfig = { region: identityPoolRegion ?? region };
- const credentialsProvider = async () => {
+ const authenticatedCredentialsProvider = async () => {
if (!identity_id) {
const { IdentityId } = await getId(cognitoConfig, {
IdentityPoolId: identityPoolId,
@@ -404,7 +404,7 @@ export class CredentialsClass {
};
};
- const credentials = credentialsProvider().catch(async err => {
+ const credentials = authenticatedCredentialsProvider().catch(async err => {
throw err;
});
diff --git a/packages/core/src/clients/handlers/fetch.ts b/packages/core/src/clients/handlers/fetch.ts
index 99684b6ee98..545230e9c15 100644
--- a/packages/core/src/clients/handlers/fetch.ts
+++ b/packages/core/src/clients/handlers/fetch.ts
@@ -23,6 +23,8 @@ export const fetchTransferHandler: TransferHandler<
});
} catch (e) {
// TODO: needs to revise error handling in v6
+ // For now this is a thin wrapper over original fetch error similar to cognito-identity-js package.
+ // Ref: https://github.com/aws-amplify/amplify-js/blob/4fbc8c0a2be7526aab723579b4c95b552195a80b/packages/amazon-cognito-identity-js/src/Client.js#L103-L108
if (e instanceof TypeError) {
throw new Error('Network error');
}
@@ -42,9 +44,9 @@ export const fetchTransferHandler: TransferHandler<
// resp.body is a ReadableStream according to Fetch API spec, but React Native
// does not implement it.
const bodyWithMixin = Object.assign(resp.body ?? {}, {
- text: withPayloadCaching(() => resp.text()),
- blob: withPayloadCaching(() => resp.blob()),
- json: withPayloadCaching(() => resp.json()),
+ text: withMemoization(() => resp.text()),
+ blob: withMemoization(() => resp.blob()),
+ json: withMemoization(() => resp.json()),
});
return {
@@ -59,11 +61,14 @@ export const fetchTransferHandler: TransferHandler<
* Caching body is allowed here because we call the body accessor(blob(), json(),
* etc.) when body is small or streaming implementation is not available(RN).
*/
-const withPayloadCaching = (payloadAccessor: () => Promise) => {
- let cached: T;
- return async () => {
+const withMemoization = (payloadAccessor: () => Promise) => {
+ let cached: Promise;
+ return () => {
if (!cached) {
- cached = await payloadAccessor();
+ // Explicitly not awaiting. Intermediate await would add overhead and
+ // introduce a possible race in the event that this wrapper is called
+ // again before the first `payloadAccessor` call resolves.
+ cached = payloadAccessor();
}
return cached;
};
diff --git a/packages/core/src/clients/middleware/retry/jitteredBackoff.ts b/packages/core/src/clients/middleware/retry/jitteredBackoff.ts
index e26c7eebe22..ebf54e68430 100644
--- a/packages/core/src/clients/middleware/retry/jitteredBackoff.ts
+++ b/packages/core/src/clients/middleware/retry/jitteredBackoff.ts
@@ -2,13 +2,15 @@
// SPDX-License-Identifier: Apache-2.0
import type { RetryOptions } from './middleware';
-// TODO: remove this dependency in v6
+// TODO: [v6] The separate retry utility is used by Data packages now and will replaced by retry middleware.
import { jitteredBackoff as jitteredBackoffUtil } from '../../../Util/Retry';
-const MAX_DELAY_MS = 5 * 60 * 1000;
+const DEFAULT_MAX_DELAY_MS = 5 * 60 * 1000;
export const jitteredBackoff: RetryOptions['computeDelay'] = attempt => {
- const delayFunction = jitteredBackoffUtil();
+ const delayFunction = jitteredBackoffUtil(DEFAULT_MAX_DELAY_MS);
const delay = delayFunction(attempt);
- return delay === false ? MAX_DELAY_MS : delay;
+ // The delayFunction returns false when the delay is greater than the max delay(5 mins).
+ // In this case, the retry middleware will delay 5 mins instead, as a ceiling of the delay.
+ return delay === false ? DEFAULT_MAX_DELAY_MS : delay;
};
diff --git a/packages/core/src/clients/middleware/signing/middleware.ts b/packages/core/src/clients/middleware/signing/middleware.ts
index 3499a2c2a39..f5a8caf5c97 100644
--- a/packages/core/src/clients/middleware/signing/middleware.ts
+++ b/packages/core/src/clients/middleware/signing/middleware.ts
@@ -22,7 +22,7 @@ export interface SigningOptions {
}
/**
- * Signing middleware
+ * Middleware that SigV4 signs request with AWS credentials, and correct system clock offset.
*/
export const signingMiddleware = ({
credentials,
diff --git a/packages/core/src/clients/middleware/signing/utils/extendedEncodeURIComponent.ts b/packages/core/src/clients/middleware/signing/utils/extendedEncodeURIComponent.ts
index a90499e7936..487307cc198 100644
--- a/packages/core/src/clients/middleware/signing/utils/extendedEncodeURIComponent.ts
+++ b/packages/core/src/clients/middleware/signing/utils/extendedEncodeURIComponent.ts
@@ -3,6 +3,7 @@
/**
* Wraps encodeURIComponent to encode additional characters to fully adhere to RFC 3986.
+ * @see https://github.com/aws/aws-sdk-js-v3/blob/86b432c464150069678b25ff88d57c2ca26e75a2/packages/smithy-client/src/extended-encode-uri-component.ts#L7
*
* @param uri URI string to encode
* @returns RFC 3986 encoded string
diff --git a/packages/core/src/clients/middleware/signing/utils/isClockSkewed.ts b/packages/core/src/clients/middleware/signing/utils/isClockSkewed.ts
index 4e9c5ab93a7..da796cb0a56 100644
--- a/packages/core/src/clients/middleware/signing/utils/isClockSkewed.ts
+++ b/packages/core/src/clients/middleware/signing/utils/isClockSkewed.ts
@@ -3,7 +3,8 @@
import { getSkewCorrectedDate } from './getSkewCorrectedDate';
-const SKEW_WINDOW = 5 * 60 * 1000; // 5 minutes
+// 5 mins in milliseconds. Ref: https://github.com/aws/aws-sdk-js-v3/blob/6c0f44fab30a1bb2134af47362a31332abc3666b/packages/middleware-signing/src/utils/isClockSkewed.ts#L10
+const SKEW_WINDOW = 5 * 60 * 1000;
/**
* Checks if the provided date is within the skew window of 5 minutes.
@@ -11,8 +12,7 @@ const SKEW_WINDOW = 5 * 60 * 1000; // 5 minutes
* @param clockTimeInMilliseconds Time to check for skew in milliseconds.
* @param clockOffsetInMilliseconds Offset to check clock against in milliseconds.
*
- * @returns True if the difference in time between the checked time and offset is greater than the 5 minute skew window
- * and false otherwise.
+ * @returns True if skewed. False otherwise.
*
* @internal
*/
diff --git a/packages/core/src/clients/middleware/userAgent/middleware.ts b/packages/core/src/clients/middleware/userAgent/middleware.ts
index 1e5adb16a5f..794d94d8e6c 100644
--- a/packages/core/src/clients/middleware/userAgent/middleware.ts
+++ b/packages/core/src/clients/middleware/userAgent/middleware.ts
@@ -9,7 +9,12 @@ export interface UserAgentOptions {
userAgentValue?: string;
}
-// TODO: incorporate new user agent design
+/**
+ * Middleware injects user agent string to specified header(default to 'x-amz-user-agent'),
+ * if the header is not set already.
+ *
+ * TODO: incorporate new user agent design
+ */
export const userAgentMiddleware: Middleware<
HttpRequest,
HttpResponse,
diff --git a/packages/core/src/clients/serde/json.ts b/packages/core/src/clients/serde/json.ts
index cea8db0efe8..c9bbae3c52b 100644
--- a/packages/core/src/clients/serde/json.ts
+++ b/packages/core/src/clients/serde/json.ts
@@ -6,8 +6,6 @@ import { parseMetadata } from './responseInfo';
/**
* Utility functions for serializing and deserializing of JSON protocol in general(including: REST-JSON, JSON-RPC, etc.)
- * The utility functions here must be mindful of only reading the response body once for each response in any
- * deserializer code path.
*/
/**
diff --git a/packages/datastore/package.json b/packages/datastore/package.json
index 1c06ded72e6..84c360ad389 100644
--- a/packages/datastore/package.json
+++ b/packages/datastore/package.json
@@ -72,7 +72,7 @@
"name": "DataStore (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, DataStore }",
- "limit": "155 kB"
+ "limit": "136.5 kB"
}
],
"jest": {
diff --git a/packages/geo/package.json b/packages/geo/package.json
index e408c97d2cf..dd96c3013db 100644
--- a/packages/geo/package.json
+++ b/packages/geo/package.json
@@ -57,7 +57,7 @@
"name": "Geo (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Geo }",
- "limit": "50.5 kB"
+ "limit": "65 kB"
}
],
"jest": {
diff --git a/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts b/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts
index e00becb7ff1..d6b8c3d5ea8 100644
--- a/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts
+++ b/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts
@@ -121,6 +121,7 @@ export default class AWSPinpointProvider
clearMemo();
try {
await this.updateEndpoint();
+ // The credentials exists assuming `updateEndpoint()` is always called before.
const { appId, credentials, endpointId, region } = this.config;
const input: GetInAppMessagesInput = {
ApplicationId: appId,
diff --git a/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts b/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts
index a1b45d9d63b..3bfc7780aee 100644
--- a/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts
+++ b/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts
@@ -108,7 +108,7 @@ export default abstract class AWSPinpointProviderCommon
// Update credentials
this.config.credentials = await this.getCredentials();
// Assert required configuration properties to make `putEvents` request are present
- this.assertValidConfiguration();
+ this.assertNotEmptyConfiguration();
const { appId, credentials, endpointId, region } = this.config;
try {
@@ -157,7 +157,7 @@ export default abstract class AWSPinpointProviderCommon
// Update credentials
this.config.credentials = credentials;
// Assert required configuration properties to make `updateEndpoint` request are present
- this.assertValidConfiguration();
+ this.assertNotEmptyConfiguration();
const { appId, endpointId, endpointInfo = {}, region } = this.config;
try {
const { address, attributes, demographic, location, metrics, optOut } =
@@ -252,7 +252,7 @@ export default abstract class AWSPinpointProviderCommon
}
};
- private assertValidConfiguration = () => {
+ private assertNotEmptyConfiguration = () => {
const { appId, credentials, region } = this.config;
if (!appId || !credentials || !region) {
throw new Error(
From 6612e1f44325c8a88e6b6a62080e94bf499f2178 Mon Sep 17 00:00:00 2001
From: Allan Zheng
Date: Fri, 2 Jun 2023 15:14:27 -0700
Subject: [PATCH 35/41] fix(analytics): apply useragent enhancement to pinpoint
client
---
.../Providers/AWSPinpointProvider.test.ts | 41 ++++++++++++++++---
.../Providers/CustomUserAgent.test.ts | 17 --------
.../src/Providers/AWSPinpointProvider.ts | 6 +--
packages/analytics/src/utils/UserAgent.ts | 15 +++++--
4 files changed, 49 insertions(+), 30 deletions(-)
diff --git a/packages/analytics/__tests__/Providers/AWSPinpointProvider.test.ts b/packages/analytics/__tests__/Providers/AWSPinpointProvider.test.ts
index 2c9cd1646c0..a92ca078925 100644
--- a/packages/analytics/__tests__/Providers/AWSPinpointProvider.test.ts
+++ b/packages/analytics/__tests__/Providers/AWSPinpointProvider.test.ts
@@ -145,6 +145,10 @@ const optionsWithClientContext = {
let resolve = null;
let reject = null;
+// Example: aws-amplify/5.2.4 analytics/1 framework/0
+const expectedUserAgentRegex =
+ /^aws-amplify\/[\d\.]+ analytics\/1 framework\/0/;
+
jest.mock('uuid', () => {
return { v1: () => 'uuid' };
});
@@ -285,7 +289,12 @@ describe('AnalyticsProvider test', () => {
const params = { event: { name: 'custom event', immediate: true } };
await analytics.record(params, { resolve, reject });
expect(mockPutEvents).toBeCalledWith(
- { credentials, region: 'region' },
+ expect.objectContaining({
+ credentials,
+ region: 'region',
+ userAgentValue: expect.stringMatching(expectedUserAgentRegex),
+ // userAgentValue: expect.stringMatching(new RegExp('^aws-amplify')),
+ }),
{
ApplicationId: 'appId',
EventsRequest: {
@@ -344,7 +353,11 @@ describe('AnalyticsProvider test', () => {
await analytics.record(params, { resolve, reject });
expect(mockPutEvents).toBeCalledWith(
- { credentials, region: 'region' },
+ expect.objectContaining({
+ credentials,
+ region: 'region',
+ userAgentValue: expect.stringMatching(expectedUserAgentRegex),
+ }),
{
ApplicationId: 'appId',
EventsRequest: {
@@ -465,7 +478,11 @@ describe('AnalyticsProvider test', () => {
const params = { event: { name: '_update_endpoint', immediate: true } };
await analytics.record(params, { resolve, reject });
expect(mockUpdateEndpoint).toBeCalledWith(
- { credentials, region: 'region' },
+ expect.objectContaining({
+ credentials,
+ region: 'region',
+ userAgentValue: expect.stringMatching(expectedUserAgentRegex),
+ }),
{
ApplicationId: 'appId',
EndpointId: 'endpointId',
@@ -504,7 +521,11 @@ describe('AnalyticsProvider test', () => {
const params = { event: { name: '_update_endpoint', immediate: true } };
await analytics.record(params, { resolve, reject });
expect(mockUpdateEndpoint).toBeCalledWith(
- { credentials, region: 'region' },
+ expect.objectContaining({
+ credentials,
+ region: 'region',
+ userAgentValue: expect.stringMatching(expectedUserAgentRegex),
+ }),
{
ApplicationId: 'appId',
EndpointId: 'endpointId',
@@ -545,7 +566,11 @@ describe('AnalyticsProvider test', () => {
await analytics.record(params, { resolve, reject });
expect(mockUpdateEndpoint).toBeCalledWith(
- { credentials, region: 'region' },
+ expect.objectContaining({
+ credentials,
+ region: 'region',
+ userAgentValue: expect.stringMatching(expectedUserAgentRegex),
+ }),
{
ApplicationId: 'appId',
EndpointId: 'endpointId',
@@ -606,7 +631,11 @@ describe('AnalyticsProvider test', () => {
await analytics.record(params, { resolve, reject });
expect(mockUpdateEndpoint).toBeCalledWith(
- { credentials, region: 'region' },
+ expect.objectContaining({
+ credentials,
+ region: 'region',
+ userAgentValue: expect.stringMatching(expectedUserAgentRegex),
+ }),
{
ApplicationId: 'appId',
EndpointId: 'endpointId',
diff --git a/packages/analytics/__tests__/Providers/CustomUserAgent.test.ts b/packages/analytics/__tests__/Providers/CustomUserAgent.test.ts
index bdd140d8730..17f976c70fa 100644
--- a/packages/analytics/__tests__/Providers/CustomUserAgent.test.ts
+++ b/packages/analytics/__tests__/Providers/CustomUserAgent.test.ts
@@ -2,7 +2,6 @@ import {
AmazonPersonalizeProvider,
AWSKinesisFirehoseProvider,
AWSKinesisProvider,
- AWSPinpointProvider,
} from '../../src/Providers';
describe('Each provider client is configured with the custom user client', () => {
@@ -51,20 +50,4 @@ describe('Each provider client is configured with the custom user client', () =>
]);
});
});
-
- describe('AWSPinpointProvider', () => {
- test('received the custom user client', () => {
- const provider = new AWSPinpointProvider({ region: 'us-east-1' });
- // Run init to setup the client
- provider['_initClients']({});
-
- expect(
- provider['pinpointClient']['config']['customUserAgent']
- ).toMatchObject([
- ['aws-amplify', expect.any(String)],
- ['analytics', '1'],
- ['framework', '0'],
- ]);
- });
- });
});
diff --git a/packages/analytics/src/Providers/AWSPinpointProvider.ts b/packages/analytics/src/Providers/AWSPinpointProvider.ts
index 18ac08d58f5..06e8c1d5ffe 100644
--- a/packages/analytics/src/Providers/AWSPinpointProvider.ts
+++ b/packages/analytics/src/Providers/AWSPinpointProvider.ts
@@ -29,7 +29,7 @@ import {
EndpointFailureData,
} from '../types';
import { v1 as uuid } from 'uuid';
-import { getAnalyticsUserAgent } from '../utils/UserAgent';
+import { getAnalyticsUserAgentString } from '../utils/UserAgent';
import EventBuffer from './EventBuffer';
const AMPLIFY_SYMBOL = (
@@ -286,7 +286,7 @@ export class AWSPinpointProvider implements AnalyticsProvider {
try {
const { credentials, region } = this._config;
const data: PutEventsOutput = await putEvents(
- { credentials, region, userAgentValue: getAnalyticsUserAgent() },
+ { credentials, region, userAgentValue: getAnalyticsUserAgentString() },
eventParams
);
@@ -391,7 +391,7 @@ export class AWSPinpointProvider implements AnalyticsProvider {
try {
const { credentials, region } = this._config;
const data: UpdateEndpointOutput = await updateEndpoint(
- { credentials, region, userAgentValue: getAnalyticsUserAgent() },
+ { credentials, region, userAgentValue: getAnalyticsUserAgentString() },
update_params
);
logger.debug('updateEndpoint success', data);
diff --git a/packages/analytics/src/utils/UserAgent.ts b/packages/analytics/src/utils/UserAgent.ts
index cc9d0e8b12b..ff23723c5a8 100644
--- a/packages/analytics/src/utils/UserAgent.ts
+++ b/packages/analytics/src/utils/UserAgent.ts
@@ -2,11 +2,18 @@ import {
AnalyticsAction,
Category,
getAmplifyUserAgent,
+ getAmplifyUserAgentString,
} from '@aws-amplify/core';
+const userAgentDetails = {
+ category: Category.Analytics,
+ action: AnalyticsAction.Record,
+} as const;
+
export function getAnalyticsUserAgent() {
- return getAmplifyUserAgent({
- category: Category.Analytics,
- action: AnalyticsAction.Record,
- });
+ return getAmplifyUserAgent(userAgentDetails);
+}
+
+export function getAnalyticsUserAgentString() {
+ return getAmplifyUserAgentString(userAgentDetails);
}
From d3df79c0cee795f557c4f29b413f76e380073922 Mon Sep 17 00:00:00 2001
From: Allan Zheng
Date: Fri, 2 Jun 2023 16:26:38 -0700
Subject: [PATCH 36/41] chore: update size limit
---
packages/analytics/package.json | 4 ++--
packages/api-graphql/package.json | 2 +-
packages/api-rest/package.json | 2 +-
packages/api/package.json | 2 +-
packages/auth/package.json | 2 +-
packages/core/package.json | 2 +-
packages/datastore/package.json | 2 +-
packages/interactions/package.json | 2 +-
packages/notifications/package.json | 4 ++--
packages/pubsub/package.json | 4 ++--
10 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/packages/analytics/package.json b/packages/analytics/package.json
index a30ce03cee0..bd6d9d06b88 100644
--- a/packages/analytics/package.json
+++ b/packages/analytics/package.json
@@ -63,13 +63,13 @@
"name": "Analytics (Pinpoint)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Analytics, AWSPinpointProvider }",
- "limit": "29.7 kB"
+ "limit": "30.85 kB"
},
{
"name": "Analytics (Kinesis)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Analytics, AWSKinesisProvider }",
- "limit": "58.2 kB"
+ "limit": "59.3 kB"
}
],
"jest": {
diff --git a/packages/api-graphql/package.json b/packages/api-graphql/package.json
index 32549e79d4d..c998a4d20fb 100644
--- a/packages/api-graphql/package.json
+++ b/packages/api-graphql/package.json
@@ -65,7 +65,7 @@
"name": "API (GraphQL client)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, GraphQLAPI }",
- "limit": "87.1 kB"
+ "limit": "87.95 kB"
}
],
"jest": {
diff --git a/packages/api-rest/package.json b/packages/api-rest/package.json
index c0c6d9b816d..27870de87aa 100644
--- a/packages/api-rest/package.json
+++ b/packages/api-rest/package.json
@@ -55,7 +55,7 @@
"name": "API (rest client)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, RestAPI }",
- "limit": "29.75 kB"
+ "limit": "30.75 kB"
}
],
"jest": {
diff --git a/packages/api/package.json b/packages/api/package.json
index da8f46c19b1..cd5fa200f5c 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -67,7 +67,7 @@
"name": "API (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, API }",
- "limit": "88.35 kB"
+ "limit": "88.6 kB"
}
],
"jest": {
diff --git a/packages/auth/package.json b/packages/auth/package.json
index 8fc3e4158bc..0d13ef8c981 100644
--- a/packages/auth/package.json
+++ b/packages/auth/package.json
@@ -58,7 +58,7 @@
"name": "Auth (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Auth }",
- "limit": "53.8 kB"
+ "limit": "54.7 kB"
}
],
"jest": {
diff --git a/packages/core/package.json b/packages/core/package.json
index 8c834485e9f..106c8879605 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -105,7 +105,7 @@
"name": "Core (Credentials)",
"path": "./lib-esm/index.js",
"import": "{ Credentials }",
- "limit": "12.05 kB"
+ "limit": "13.15 kB"
},
{
"name": "Core (Signer)",
diff --git a/packages/datastore/package.json b/packages/datastore/package.json
index 84c360ad389..c422f4cd886 100644
--- a/packages/datastore/package.json
+++ b/packages/datastore/package.json
@@ -72,7 +72,7 @@
"name": "DataStore (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, DataStore }",
- "limit": "136.5 kB"
+ "limit": "136.65 kB"
}
],
"jest": {
diff --git a/packages/interactions/package.json b/packages/interactions/package.json
index 2cb5a35a264..554691dcd3c 100644
--- a/packages/interactions/package.json
+++ b/packages/interactions/package.json
@@ -59,7 +59,7 @@
"name": "Interactions (top-level class with Lex v2)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Interactions, AWSLexV2Provider }",
- "limit": "74.5 kB"
+ "limit": "75.5 kB"
}
],
"jest": {
diff --git a/packages/notifications/package.json b/packages/notifications/package.json
index b861c09663e..c7cb91626d7 100644
--- a/packages/notifications/package.json
+++ b/packages/notifications/package.json
@@ -65,13 +65,13 @@
"name": "Notifications (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Notifications }",
- "limit": "28.75 kB"
+ "limit": "29.7 kB"
},
{
"name": "Notifications (with Analytics)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Notifications, Analytics }",
- "limit": "28.76 kB"
+ "limit": "29.7 kB"
}
]
}
diff --git a/packages/pubsub/package.json b/packages/pubsub/package.json
index b0448879a36..c3f78132079 100644
--- a/packages/pubsub/package.json
+++ b/packages/pubsub/package.json
@@ -64,13 +64,13 @@
"name": "PubSub (IoT provider)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, PubSub, AWSIoTProvider }",
- "limit": "79.15 kB"
+ "limit": "79.85 kB"
},
{
"name": "PubSub (Mqtt provider)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, PubSub, MqttOverWSProvider }",
- "limit": "79 kB"
+ "limit": "79.7 kB"
}
],
"jest": {
From 6329a64bf7a1d9d4d4c312e230166b15d393fe3a Mon Sep 17 00:00:00 2001
From: Aaron S <94858815+stocaaro@users.noreply.github.com>
Date: Tue, 6 Jun 2023 10:12:50 -0500
Subject: [PATCH 37/41] fix: Auth bundle size test
---
packages/auth/package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/auth/package.json b/packages/auth/package.json
index 0d13ef8c981..40be3775c0f 100644
--- a/packages/auth/package.json
+++ b/packages/auth/package.json
@@ -58,7 +58,7 @@
"name": "Auth (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, Auth }",
- "limit": "54.7 kB"
+ "limit": "55 kB"
}
],
"jest": {
From dea19d81be294446db10532b49292decbbcc10f6 Mon Sep 17 00:00:00 2001
From: Aaron S <94858815+stocaaro@users.noreply.github.com>
Date: Tue, 6 Jun 2023 10:34:08 -0500
Subject: [PATCH 38/41] fix: Update bundle size for pubsub
---
packages/pubsub/package.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/pubsub/package.json b/packages/pubsub/package.json
index c3f78132079..0a665c7f570 100644
--- a/packages/pubsub/package.json
+++ b/packages/pubsub/package.json
@@ -64,13 +64,13 @@
"name": "PubSub (IoT provider)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, PubSub, AWSIoTProvider }",
- "limit": "79.85 kB"
+ "limit": "80 kB"
},
{
"name": "PubSub (Mqtt provider)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, PubSub, MqttOverWSProvider }",
- "limit": "79.7 kB"
+ "limit": "80 kB"
}
],
"jest": {
From ee71d9b38da4262d85935f7083a092af47e434ca Mon Sep 17 00:00:00 2001
From: Aaron S <94858815+stocaaro@users.noreply.github.com>
Date: Tue, 6 Jun 2023 10:44:09 -0500
Subject: [PATCH 39/41] fix: api-graphql bundle size update
---
packages/api-graphql/package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/api-graphql/package.json b/packages/api-graphql/package.json
index c998a4d20fb..daf95771279 100644
--- a/packages/api-graphql/package.json
+++ b/packages/api-graphql/package.json
@@ -65,7 +65,7 @@
"name": "API (GraphQL client)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, GraphQLAPI }",
- "limit": "87.95 kB"
+ "limit": "88.5 kB"
}
],
"jest": {
From 5da82e60a3dd3405ad68cf6264ceb4a95783eb65 Mon Sep 17 00:00:00 2001
From: Aaron S <94858815+stocaaro@users.noreply.github.com>
Date: Tue, 6 Jun 2023 11:04:27 -0500
Subject: [PATCH 40/41] fix: Update api bundle sizes
---
packages/api/package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/api/package.json b/packages/api/package.json
index cd5fa200f5c..1039362cb2f 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -67,7 +67,7 @@
"name": "API (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, API }",
- "limit": "88.6 kB"
+ "limit": "89 kB"
}
],
"jest": {
From f66c4dbd1548d586c0f6d0612192608d93883ffb Mon Sep 17 00:00:00 2001
From: Aaron S <94858815+stocaaro@users.noreply.github.com>
Date: Tue, 6 Jun 2023 11:20:11 -0500
Subject: [PATCH 41/41] fix: DataStore bundle size test
---
packages/datastore/package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/datastore/package.json b/packages/datastore/package.json
index c422f4cd886..b0ad2a6d5c0 100644
--- a/packages/datastore/package.json
+++ b/packages/datastore/package.json
@@ -72,7 +72,7 @@
"name": "DataStore (top-level class)",
"path": "./lib-esm/index.js",
"import": "{ Amplify, DataStore }",
- "limit": "136.65 kB"
+ "limit": "137 kB"
}
],
"jest": {