@aws-amplify/api-rest 0.0.0-unstable-1ca644b-20260128091132
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/cjs/apis/common/baseHandlers/authenticatedHandler.js +17 -0
- package/dist/cjs/apis/common/baseHandlers/authenticatedHandler.js.map +1 -0
- package/dist/cjs/apis/common/baseHandlers/unauthenticatedHandler.js +13 -0
- package/dist/cjs/apis/common/baseHandlers/unauthenticatedHandler.js.map +1 -0
- package/dist/cjs/apis/common/internalPost.js +98 -0
- package/dist/cjs/apis/common/internalPost.js.map +1 -0
- package/dist/cjs/apis/common/publicApis.js +57 -0
- package/dist/cjs/apis/common/publicApis.js.map +1 -0
- package/dist/cjs/apis/common/transferHandler.js +92 -0
- package/dist/cjs/apis/common/transferHandler.js.map +1 -0
- package/dist/cjs/apis/index.js +217 -0
- package/dist/cjs/apis/index.js.map +1 -0
- package/dist/cjs/apis/server.js +163 -0
- package/dist/cjs/apis/server.js.map +1 -0
- package/dist/cjs/errors/CanceledError.js +37 -0
- package/dist/cjs/errors/CanceledError.js.map +1 -0
- package/dist/cjs/errors/RestApiError.js +17 -0
- package/dist/cjs/errors/RestApiError.js.map +1 -0
- package/dist/cjs/errors/assertValidatonError.js +18 -0
- package/dist/cjs/errors/assertValidatonError.js.map +1 -0
- package/dist/cjs/errors/index.js +17 -0
- package/dist/cjs/errors/index.js.map +1 -0
- package/dist/cjs/errors/validation.js +17 -0
- package/dist/cjs/errors/validation.js.map +1 -0
- package/dist/cjs/index.js +16 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/internals/index.js +12 -0
- package/dist/cjs/internals/index.js.map +1 -0
- package/dist/cjs/internals/server.js +32 -0
- package/dist/cjs/internals/server.js.map +1 -0
- package/dist/cjs/server.js +16 -0
- package/dist/cjs/server.js.map +1 -0
- package/dist/cjs/types/index.js +4 -0
- package/dist/cjs/types/index.js.map +1 -0
- package/dist/cjs/utils/constants.js +15 -0
- package/dist/cjs/utils/constants.js.map +1 -0
- package/dist/cjs/utils/createCancellableOperation.js +76 -0
- package/dist/cjs/utils/createCancellableOperation.js.map +1 -0
- package/dist/cjs/utils/index.js +19 -0
- package/dist/cjs/utils/index.js.map +1 -0
- package/dist/cjs/utils/isIamAuthApplicable.js +39 -0
- package/dist/cjs/utils/isIamAuthApplicable.js.map +1 -0
- package/dist/cjs/utils/logger.js +9 -0
- package/dist/cjs/utils/logger.js.map +1 -0
- package/dist/cjs/utils/parseSigningInfo.js +43 -0
- package/dist/cjs/utils/parseSigningInfo.js.map +1 -0
- package/dist/cjs/utils/resolveApiUrl.js +48 -0
- package/dist/cjs/utils/resolveApiUrl.js.map +1 -0
- package/dist/cjs/utils/resolveHeaders.js +34 -0
- package/dist/cjs/utils/resolveHeaders.js.map +1 -0
- package/dist/cjs/utils/resolveLibraryOptions.js +16 -0
- package/dist/cjs/utils/resolveLibraryOptions.js.map +1 -0
- package/dist/cjs/utils/serviceError.js +98 -0
- package/dist/cjs/utils/serviceError.js.map +1 -0
- package/dist/esm/apis/common/baseHandlers/authenticatedHandler.d.ts +5 -0
- package/dist/esm/apis/common/baseHandlers/authenticatedHandler.mjs +16 -0
- package/dist/esm/apis/common/baseHandlers/authenticatedHandler.mjs.map +1 -0
- package/dist/esm/apis/common/baseHandlers/unauthenticatedHandler.d.ts +5 -0
- package/dist/esm/apis/common/baseHandlers/unauthenticatedHandler.mjs +12 -0
- package/dist/esm/apis/common/baseHandlers/unauthenticatedHandler.mjs.map +1 -0
- package/dist/esm/apis/common/internalPost.d.ts +43 -0
- package/dist/esm/apis/common/internalPost.mjs +98 -0
- package/dist/esm/apis/common/internalPost.mjs.map +1 -0
- package/dist/esm/apis/common/publicApis.d.ts +8 -0
- package/dist/esm/apis/common/publicApis.mjs +56 -0
- package/dist/esm/apis/common/publicApis.mjs.map +1 -0
- package/dist/esm/apis/common/transferHandler.d.ts +26 -0
- package/dist/esm/apis/common/transferHandler.mjs +95 -0
- package/dist/esm/apis/common/transferHandler.mjs.map +1 -0
- package/dist/esm/apis/index.d.ts +203 -0
- package/dist/esm/apis/index.mjs +210 -0
- package/dist/esm/apis/index.mjs.map +1 -0
- package/dist/esm/apis/server.d.ts +150 -0
- package/dist/esm/apis/server.mjs +156 -0
- package/dist/esm/apis/server.mjs.map +1 -0
- package/dist/esm/errors/CanceledError.d.ts +20 -0
- package/dist/esm/errors/CanceledError.mjs +34 -0
- package/dist/esm/errors/CanceledError.mjs.map +1 -0
- package/dist/esm/errors/RestApiError.d.ts +4 -0
- package/dist/esm/errors/RestApiError.mjs +15 -0
- package/dist/esm/errors/RestApiError.mjs.map +1 -0
- package/dist/esm/errors/assertValidatonError.d.ts +5 -0
- package/dist/esm/errors/assertValidatonError.mjs +17 -0
- package/dist/esm/errors/assertValidatonError.mjs.map +1 -0
- package/dist/esm/errors/index.d.ts +4 -0
- package/dist/esm/errors/index.mjs +5 -0
- package/dist/esm/errors/index.mjs.map +1 -0
- package/dist/esm/errors/validation.d.ts +5 -0
- package/dist/esm/errors/validation.mjs +15 -0
- package/dist/esm/errors/validation.mjs.map +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.mjs +3 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/internals/index.d.ts +2 -0
- package/dist/esm/internals/index.mjs +2 -0
- package/dist/esm/internals/index.mjs.map +1 -0
- package/dist/esm/internals/server.d.ts +20 -0
- package/dist/esm/internals/server.mjs +28 -0
- package/dist/esm/internals/server.mjs.map +1 -0
- package/dist/esm/server.d.ts +2 -0
- package/dist/esm/server.mjs +3 -0
- package/dist/esm/server.mjs.map +1 -0
- package/dist/esm/types/index.d.ts +122 -0
- package/dist/esm/types/index.mjs +2 -0
- package/dist/esm/types/index.mjs.map +1 -0
- package/dist/esm/utils/constants.d.ts +8 -0
- package/dist/esm/utils/constants.mjs +13 -0
- package/dist/esm/utils/constants.mjs.map +1 -0
- package/dist/esm/utils/createCancellableOperation.d.ts +12 -0
- package/dist/esm/utils/createCancellableOperation.mjs +77 -0
- package/dist/esm/utils/createCancellableOperation.mjs.map +1 -0
- package/dist/esm/utils/index.d.ts +6 -0
- package/dist/esm/utils/index.mjs +7 -0
- package/dist/esm/utils/index.mjs.map +1 -0
- package/dist/esm/utils/isIamAuthApplicable.d.ts +32 -0
- package/dist/esm/utils/isIamAuthApplicable.mjs +35 -0
- package/dist/esm/utils/isIamAuthApplicable.mjs.map +1 -0
- package/dist/esm/utils/logger.d.ts +2 -0
- package/dist/esm/utils/logger.mjs +8 -0
- package/dist/esm/utils/logger.mjs.map +1 -0
- package/dist/esm/utils/parseSigningInfo.d.ts +14 -0
- package/dist/esm/utils/parseSigningInfo.mjs +41 -0
- package/dist/esm/utils/parseSigningInfo.mjs.map +1 -0
- package/dist/esm/utils/resolveApiUrl.d.ts +12 -0
- package/dist/esm/utils/resolveApiUrl.mjs +48 -0
- package/dist/esm/utils/resolveApiUrl.mjs.map +1 -0
- package/dist/esm/utils/resolveHeaders.d.ts +1 -0
- package/dist/esm/utils/resolveHeaders.mjs +31 -0
- package/dist/esm/utils/resolveHeaders.mjs.map +1 -0
- package/dist/esm/utils/resolveLibraryOptions.d.ts +8 -0
- package/dist/esm/utils/resolveLibraryOptions.mjs +13 -0
- package/dist/esm/utils/resolveLibraryOptions.mjs.map +1 -0
- package/dist/esm/utils/serviceError.d.ts +13 -0
- package/dist/esm/utils/serviceError.mjs +97 -0
- package/dist/esm/utils/serviceError.mjs.map +1 -0
- package/internals/package.json +8 -0
- package/internals/server/package.json +7 -0
- package/package.json +94 -0
- package/server/package.json +7 -0
- package/src/apis/common/baseHandlers/authenticatedHandler.ts +28 -0
- package/src/apis/common/baseHandlers/unauthenticatedHandler.ts +22 -0
- package/src/apis/common/internalPost.ts +123 -0
- package/src/apis/common/publicApis.ts +118 -0
- package/src/apis/common/transferHandler.ts +154 -0
- package/src/apis/index.ts +241 -0
- package/src/apis/server.ts +210 -0
- package/src/errors/CanceledError.ts +37 -0
- package/src/errors/RestApiError.ts +14 -0
- package/src/errors/assertValidatonError.ts +19 -0
- package/src/errors/index.ts +7 -0
- package/src/errors/validation.ts +16 -0
- package/src/index.ts +5 -0
- package/src/internals/index.ts +8 -0
- package/src/internals/server.ts +37 -0
- package/src/server.ts +5 -0
- package/src/types/index.ts +138 -0
- package/src/utils/constants.ts +15 -0
- package/src/utils/createCancellableOperation.ts +108 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/isIamAuthApplicable.ts +44 -0
- package/src/utils/logger.ts +6 -0
- package/src/utils/parseSigningInfo.ts +53 -0
- package/src/utils/resolveApiUrl.ts +59 -0
- package/src/utils/resolveHeaders.ts +36 -0
- package/src/utils/resolveLibraryOptions.ts +14 -0
- package/src/utils/serviceError.ts +109 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import {
|
|
4
|
+
DocumentType,
|
|
5
|
+
RESTAuthMode,
|
|
6
|
+
RetryStrategy,
|
|
7
|
+
} from '@aws-amplify/core/internals/utils';
|
|
8
|
+
|
|
9
|
+
export type GetInput = ApiInput<RestApiOptionsBase>;
|
|
10
|
+
export type PostInput = ApiInput<RestApiOptionsBase>;
|
|
11
|
+
export type PutInput = ApiInput<RestApiOptionsBase>;
|
|
12
|
+
export type PatchInput = ApiInput<RestApiOptionsBase>;
|
|
13
|
+
export type DeleteInput = ApiInput<RestApiOptionsBase>;
|
|
14
|
+
export type HeadInput = ApiInput<Omit<RestApiOptionsBase, 'body'>>;
|
|
15
|
+
|
|
16
|
+
export type GetOperation = Operation<RestApiResponse>;
|
|
17
|
+
export type PostOperation = Operation<RestApiResponse>;
|
|
18
|
+
export type PutOperation = Operation<RestApiResponse>;
|
|
19
|
+
export type PatchOperation = Operation<RestApiResponse>;
|
|
20
|
+
export type DeleteOperation = Operation<RestApiResponse>;
|
|
21
|
+
export type HeadOperation = Operation<Omit<RestApiResponse, 'body'>>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export interface RestApiOptionsBase {
|
|
27
|
+
headers?: Headers;
|
|
28
|
+
queryParams?: Record<string, string>;
|
|
29
|
+
body?: DocumentType | FormData;
|
|
30
|
+
/**
|
|
31
|
+
* Option controls whether or not cross-site Access-Control requests should be made using credentials
|
|
32
|
+
* such as cookies, authorization headers or TLS client certificates. It has no effect on same-origin requests.
|
|
33
|
+
* If set to `true`, the request will include credentials such as cookies, authorization headers, TLS
|
|
34
|
+
* client certificates, and so on. Moreover the response cookies will also be set.
|
|
35
|
+
* If set to `false`, the cross-site request will not include credentials, and the response cookies from a different
|
|
36
|
+
* domain will be ignored.
|
|
37
|
+
*
|
|
38
|
+
* @default false
|
|
39
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials}
|
|
40
|
+
*/
|
|
41
|
+
withCredentials?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Retry strategy for the REST API calls. It will take precedence over REST `retryStrategy` in Amplify configuration libraryOptions.
|
|
44
|
+
*
|
|
45
|
+
* @default ` { strategy: 'jittered-exponential-backoff' } `
|
|
46
|
+
*/
|
|
47
|
+
retryStrategy?: RetryStrategy;
|
|
48
|
+
defaultAuthMode?: RESTAuthMode;
|
|
49
|
+
/**
|
|
50
|
+
* custom timeout in milliseconds.
|
|
51
|
+
*/
|
|
52
|
+
timeout?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type Headers = Record<string, string>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Type representing an operation that can be canceled.
|
|
59
|
+
*
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
export interface Operation<Response> {
|
|
63
|
+
response: Promise<Response>;
|
|
64
|
+
cancel(abortMessage?: string): void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface ResponsePayload {
|
|
68
|
+
blob(): Promise<Blob>;
|
|
69
|
+
json(): Promise<DocumentType>;
|
|
70
|
+
text(): Promise<string>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Basic response type of REST API.
|
|
75
|
+
*
|
|
76
|
+
* @internal
|
|
77
|
+
*/
|
|
78
|
+
export interface RestApiResponse {
|
|
79
|
+
body: ResponsePayload;
|
|
80
|
+
statusCode: number;
|
|
81
|
+
headers: Headers;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @internal
|
|
86
|
+
*/
|
|
87
|
+
export interface ApiInput<Options> {
|
|
88
|
+
/**
|
|
89
|
+
* Name of the REST API configured in Amplify singleton.
|
|
90
|
+
*/
|
|
91
|
+
apiName: string;
|
|
92
|
+
/**
|
|
93
|
+
* Path of the REST API.
|
|
94
|
+
*/
|
|
95
|
+
path: string;
|
|
96
|
+
/**
|
|
97
|
+
* Options to overwrite the REST API call behavior.
|
|
98
|
+
*/
|
|
99
|
+
options?: Options;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Input type to invoke REST POST API from GraphQl client.
|
|
104
|
+
* @internal
|
|
105
|
+
*/
|
|
106
|
+
export interface InternalPostInput {
|
|
107
|
+
// Resolved GraphQl endpoint url
|
|
108
|
+
url: URL;
|
|
109
|
+
options?: Omit<RestApiOptionsBase, 'retryStrategy'> & {
|
|
110
|
+
/**
|
|
111
|
+
* Internal-only option for GraphQL client to provide the IAM signing service and region.
|
|
112
|
+
* * If auth mode is 'iam', you MUST set this value.
|
|
113
|
+
* * If auth mode is 'none', you MUST NOT set this value;
|
|
114
|
+
* * If auth mode is 'apiKey' or 'oidc' or 'lambda' or 'userPool' because associated
|
|
115
|
+
* headers are provided, this value is ignored.
|
|
116
|
+
*/
|
|
117
|
+
signingServiceInfo?: {
|
|
118
|
+
service?: string;
|
|
119
|
+
region?: string;
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* The abort controller to cancel the in-flight POST request.
|
|
124
|
+
* Required if you want to make the internal post request cancellable. To make the internal post cancellable, you
|
|
125
|
+
* must also call `updateRequestToBeCancellable()` with the promise from internal post call and the abort
|
|
126
|
+
* controller.
|
|
127
|
+
*/
|
|
128
|
+
abortController?: AbortController;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Type for signingServiceInfo which enable IAM auth as well as overwrite the IAM signing info.
|
|
133
|
+
* @internal
|
|
134
|
+
*/
|
|
135
|
+
export interface SigningServiceInfo {
|
|
136
|
+
service?: string;
|
|
137
|
+
region?: string;
|
|
138
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_REST_IAM_SIGNING_SERVICE = 'execute-api';
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_APPSYNC_API_SERVICE = 'appsync-api';
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_IAM_SIGNING_REGION = 'us-east-1';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The REST endpoints generated by API Gateway
|
|
12
|
+
* @see {@link https://docs.aws.amazon.com/general/latest/gr/apigateway.html#apigateway_region_data_plane}
|
|
13
|
+
*/
|
|
14
|
+
export const APIG_HOSTNAME_PATTERN =
|
|
15
|
+
/^.+\.([a-z0-9-]+)\.([a-z0-9-]+)\.amazonaws\.com/;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { HttpResponse } from '@aws-amplify/core/internals/aws-client-utils';
|
|
5
|
+
|
|
6
|
+
import { CanceledError } from '../errors';
|
|
7
|
+
import { Operation } from '../types';
|
|
8
|
+
|
|
9
|
+
import { parseRestApiServiceError } from './serviceError';
|
|
10
|
+
import { logger } from './logger';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create a cancellable operation conforming to the internal POST API interface.
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export function createCancellableOperation(
|
|
17
|
+
handler: () => Promise<HttpResponse>,
|
|
18
|
+
abortController: AbortController,
|
|
19
|
+
operationType: 'internal',
|
|
20
|
+
timeout?: number,
|
|
21
|
+
): Promise<HttpResponse>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create a cancellable operation conforming to the external REST API interface.
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export function createCancellableOperation(
|
|
28
|
+
handler: () => Promise<HttpResponse>,
|
|
29
|
+
abortController: AbortController,
|
|
30
|
+
operationType: 'public',
|
|
31
|
+
timeout?: number,
|
|
32
|
+
): Operation<HttpResponse>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export function createCancellableOperation(
|
|
38
|
+
handler: () => Promise<HttpResponse>,
|
|
39
|
+
abortController: AbortController,
|
|
40
|
+
operationType: 'public' | 'internal',
|
|
41
|
+
timeout?: number,
|
|
42
|
+
): Operation<HttpResponse> | Promise<HttpResponse> {
|
|
43
|
+
const abortSignal = abortController.signal;
|
|
44
|
+
let abortReason: string;
|
|
45
|
+
if (timeout != null) {
|
|
46
|
+
if (timeout < 0) {
|
|
47
|
+
throw new Error('Timeout must be a non-negative number');
|
|
48
|
+
}
|
|
49
|
+
setTimeout(() => {
|
|
50
|
+
abortReason = 'TimeoutError';
|
|
51
|
+
abortController.abort(abortReason);
|
|
52
|
+
}, timeout);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const job = async () => {
|
|
56
|
+
try {
|
|
57
|
+
const response = await handler();
|
|
58
|
+
|
|
59
|
+
if (response.statusCode >= 300) {
|
|
60
|
+
throw await parseRestApiServiceError(response)!;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return response;
|
|
64
|
+
} catch (error: any) {
|
|
65
|
+
if (error.name === 'AbortError' || abortSignal?.aborted === true) {
|
|
66
|
+
// Check if timeout caused the abort
|
|
67
|
+
const isTimeout = abortReason && abortReason === 'TimeoutError';
|
|
68
|
+
|
|
69
|
+
if (isTimeout) {
|
|
70
|
+
const timeoutError = new Error(`Request timeout after ${timeout}ms`);
|
|
71
|
+
timeoutError.name = 'TimeoutError';
|
|
72
|
+
logger.debug(timeoutError);
|
|
73
|
+
throw timeoutError;
|
|
74
|
+
} else {
|
|
75
|
+
const message = abortReason ?? abortSignal.reason;
|
|
76
|
+
const canceledError = new CanceledError({
|
|
77
|
+
...(message && { message }),
|
|
78
|
+
underlyingError: error,
|
|
79
|
+
recoverySuggestion:
|
|
80
|
+
'The API request was explicitly canceled. If this is not intended, validate if you called the `cancel()` function on the API request erroneously.',
|
|
81
|
+
});
|
|
82
|
+
logger.debug(canceledError);
|
|
83
|
+
throw canceledError;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
logger.debug(error);
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
if (operationType === 'internal') {
|
|
92
|
+
return job();
|
|
93
|
+
} else {
|
|
94
|
+
const cancel = (abortMessage?: string) => {
|
|
95
|
+
if (abortSignal.aborted === true) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
abortController.abort(abortMessage);
|
|
99
|
+
// If abort reason is not supported, set a scoped reasons instead. The reason property inside an
|
|
100
|
+
// AbortSignal is a readonly property and trying to set it would throw an error.
|
|
101
|
+
if (abortMessage && abortSignal.reason !== abortMessage) {
|
|
102
|
+
abortReason = abortMessage;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return { response: job(), cancel };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
export { createCancellableOperation } from './createCancellableOperation';
|
|
5
|
+
export { parseSigningInfo } from './parseSigningInfo';
|
|
6
|
+
export { parseRestApiServiceError } from './serviceError';
|
|
7
|
+
export { resolveApiUrl } from './resolveApiUrl';
|
|
8
|
+
export { resolveLibraryOptions } from './resolveLibraryOptions';
|
|
9
|
+
export { logger } from './logger';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { HttpRequest } from '@aws-amplify/core/internals/aws-client-utils';
|
|
5
|
+
|
|
6
|
+
import { SigningServiceInfo } from '../types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Determines if IAM authentication should be applied for a GraphQL request.
|
|
10
|
+
*
|
|
11
|
+
* This function checks the `headers` of the HTTP request to determine if IAM authentication
|
|
12
|
+
* is applicable. IAM authentication is considered applicable if there is no `authorization`
|
|
13
|
+
* header, no `x-api-key` header, and `signingServiceInfo` is provided.
|
|
14
|
+
*
|
|
15
|
+
* @param request - The HTTP request object containing headers.
|
|
16
|
+
* @param signingServiceInfo - Optional signing service information,
|
|
17
|
+
* including service and region.
|
|
18
|
+
* @returns A boolean `true` if IAM authentication should be applied.
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export const isIamAuthApplicableForGraphQL = (
|
|
23
|
+
{ headers }: HttpRequest,
|
|
24
|
+
signingServiceInfo?: SigningServiceInfo,
|
|
25
|
+
) => !headers.authorization && !headers['x-api-key'] && !!signingServiceInfo;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Determines if IAM authentication should be applied for a REST request.
|
|
29
|
+
*
|
|
30
|
+
* This function checks the `headers` of the HTTP request to determine if IAM authentication
|
|
31
|
+
* is applicable. IAM authentication is considered applicable if there is no `authorization`
|
|
32
|
+
* header and `signingServiceInfo` is provided.
|
|
33
|
+
*
|
|
34
|
+
* @param request - The HTTP request object containing headers.
|
|
35
|
+
* @param signingServiceInfo - Optional signing service information,
|
|
36
|
+
* including service and region.
|
|
37
|
+
* @returns A boolean `true` if IAM authentication should be applied.
|
|
38
|
+
*
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
export const isIamAuthApplicableForRest = (
|
|
42
|
+
{ headers }: HttpRequest,
|
|
43
|
+
signingServiceInfo?: SigningServiceInfo,
|
|
44
|
+
) => !headers.authorization && !!signingServiceInfo;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { AmplifyClassV6 } from '@aws-amplify/core';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
APIG_HOSTNAME_PATTERN,
|
|
8
|
+
DEFAULT_IAM_SIGNING_REGION,
|
|
9
|
+
DEFAULT_REST_IAM_SIGNING_SERVICE,
|
|
10
|
+
} from './constants';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Infer the signing service and region from the given URL, and for REST API only, from the Amplify configuration.
|
|
14
|
+
* It supports raw API Gateway endpoint and AppSync endpoint.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export const parseSigningInfo = (
|
|
19
|
+
url: URL,
|
|
20
|
+
restApiOptions?: {
|
|
21
|
+
amplify: AmplifyClassV6;
|
|
22
|
+
apiName: string;
|
|
23
|
+
},
|
|
24
|
+
) => {
|
|
25
|
+
const {
|
|
26
|
+
service: signingService = DEFAULT_REST_IAM_SIGNING_SERVICE,
|
|
27
|
+
region: signingRegion = DEFAULT_IAM_SIGNING_REGION,
|
|
28
|
+
} =
|
|
29
|
+
restApiOptions?.amplify.getConfig()?.API?.REST?.[restApiOptions?.apiName] ??
|
|
30
|
+
{};
|
|
31
|
+
const { hostname } = url;
|
|
32
|
+
const [, service, region] = APIG_HOSTNAME_PATTERN.exec(hostname) ?? [];
|
|
33
|
+
if (service === DEFAULT_REST_IAM_SIGNING_SERVICE) {
|
|
34
|
+
// The configured endpoint is an API Gateway endpoint
|
|
35
|
+
// @see: https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-call-api.html
|
|
36
|
+
return {
|
|
37
|
+
service,
|
|
38
|
+
region: region ?? signingRegion,
|
|
39
|
+
};
|
|
40
|
+
} else if (service === 'appsync-api') {
|
|
41
|
+
// AppSync endpoint is internally supported because GraphQL operation will send request using POST handler.
|
|
42
|
+
// example: https://xxxx.appsync-api.us-east-1.amazonaws.com/graphql
|
|
43
|
+
return {
|
|
44
|
+
service: 'appsync',
|
|
45
|
+
region: region ?? signingRegion,
|
|
46
|
+
};
|
|
47
|
+
} else {
|
|
48
|
+
return {
|
|
49
|
+
service: signingService,
|
|
50
|
+
region: signingRegion,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { AmplifyClassV6 } from '@aws-amplify/core';
|
|
5
|
+
import {
|
|
6
|
+
AmplifyUrl,
|
|
7
|
+
AmplifyUrlSearchParams,
|
|
8
|
+
} from '@aws-amplify/core/internals/utils';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
RestApiError,
|
|
12
|
+
RestApiValidationErrorCode,
|
|
13
|
+
assertValidationError,
|
|
14
|
+
validationErrorMap,
|
|
15
|
+
} from '../errors';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the REST API request URL by:
|
|
19
|
+
* 1. Loading the REST API endpoint from the Amplify configuration with corresponding API name.
|
|
20
|
+
* 2. Appending the path to the endpoint.
|
|
21
|
+
* 3. Merge the query parameters from path and the queryParameter argument which is taken from the public REST API
|
|
22
|
+
* options.
|
|
23
|
+
* 4. Validating the resulting URL string.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export const resolveApiUrl = (
|
|
28
|
+
amplify: AmplifyClassV6,
|
|
29
|
+
apiName: string,
|
|
30
|
+
path: string,
|
|
31
|
+
queryParams?: Record<string, string>,
|
|
32
|
+
): URL => {
|
|
33
|
+
const urlStr = amplify.getConfig()?.API?.REST?.[apiName]?.endpoint;
|
|
34
|
+
assertValidationError(!!urlStr, RestApiValidationErrorCode.InvalidApiName);
|
|
35
|
+
try {
|
|
36
|
+
let url: URL;
|
|
37
|
+
if (AmplifyUrl.canParse(urlStr + path)) {
|
|
38
|
+
url = new AmplifyUrl(urlStr + path);
|
|
39
|
+
} else {
|
|
40
|
+
url = new AmplifyUrl(urlStr + path, location?.origin);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (queryParams) {
|
|
44
|
+
const mergedQueryParams = new AmplifyUrlSearchParams(url.searchParams);
|
|
45
|
+
Object.entries(queryParams).forEach(([key, value]) => {
|
|
46
|
+
mergedQueryParams.set(key, value);
|
|
47
|
+
});
|
|
48
|
+
url.search = new AmplifyUrlSearchParams(mergedQueryParams).toString();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return url;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new RestApiError({
|
|
54
|
+
name: RestApiValidationErrorCode.InvalidApiName,
|
|
55
|
+
...validationErrorMap[RestApiValidationErrorCode.InvalidApiName],
|
|
56
|
+
recoverySuggestion: `Please make sure the REST endpoint URL is a valid URL string. Got ${urlStr}`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
export const resolveHeaders = (
|
|
5
|
+
headers?: Record<string, string>,
|
|
6
|
+
body?: unknown,
|
|
7
|
+
) => {
|
|
8
|
+
const normalizedHeaders: Record<string, string> = {};
|
|
9
|
+
for (const key in headers) {
|
|
10
|
+
normalizedHeaders[key.toLowerCase()] = headers[key];
|
|
11
|
+
}
|
|
12
|
+
if (body) {
|
|
13
|
+
const contentType = normalizedHeaders['content-type'];
|
|
14
|
+
const isJsonCompatible =
|
|
15
|
+
contentType &&
|
|
16
|
+
(contentType.startsWith('application/json') ||
|
|
17
|
+
(contentType.startsWith('application/') &&
|
|
18
|
+
contentType.includes('+json')));
|
|
19
|
+
|
|
20
|
+
if (!isJsonCompatible) {
|
|
21
|
+
normalizedHeaders['content-type'] = 'application/json; charset=UTF-8';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (body instanceof FormData) {
|
|
25
|
+
/**
|
|
26
|
+
* If body is a FormData we should not allow setting content-type.
|
|
27
|
+
* It's because runtime HTTP handlers(xhr, fetch, undici, node-fetch,
|
|
28
|
+
* etc.) will modify the content-type value when setting multipart
|
|
29
|
+
* boundary.
|
|
30
|
+
*/
|
|
31
|
+
delete normalizedHeaders['content-type'];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return normalizedHeaders;
|
|
36
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { AmplifyClassV6 } from '@aws-amplify/core';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export const resolveLibraryOptions = (amplify: AmplifyClassV6) => {
|
|
10
|
+
const retryStrategy = amplify.libraryOptions?.API?.REST?.retryStrategy;
|
|
11
|
+
const defaultAuthMode = amplify.libraryOptions?.API?.REST?.defaultAuthMode;
|
|
12
|
+
|
|
13
|
+
return { retryStrategy, defaultAuthMode };
|
|
14
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { MetadataBearer } from '@aws-sdk/types';
|
|
4
|
+
import {
|
|
5
|
+
HttpResponse,
|
|
6
|
+
parseJsonError as parseAwsJsonError,
|
|
7
|
+
} from '@aws-amplify/core/internals/aws-client-utils';
|
|
8
|
+
import { ApiErrorResponse } from '@aws-amplify/core/internals/utils';
|
|
9
|
+
|
|
10
|
+
import { RestApiError } from '../errors';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Parses both AWS and non-AWS error responses coming from the users' backend code.
|
|
14
|
+
* * AWS errors generated by the AWS services(e.g. API Gateway, Bedrock). They can be Signature errors,
|
|
15
|
+
* ClockSkew errors, etc. These responses will be parsed to errors with proper name and message from the AWS
|
|
16
|
+
* services.
|
|
17
|
+
* * non-AWS errors thrown by the user code. They can contain any headers or body. Users need to access the
|
|
18
|
+
* error.response to get the headers and body and parse them accordingly. The JS error name and message will
|
|
19
|
+
* be `UnknownError` and `Unknown error` respectively.
|
|
20
|
+
*/
|
|
21
|
+
export const parseRestApiServiceError = async (
|
|
22
|
+
response?: HttpResponse,
|
|
23
|
+
): Promise<(RestApiError & MetadataBearer) | undefined> => {
|
|
24
|
+
if (!response) {
|
|
25
|
+
// Response is not considered an error.
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const parsedAwsError = await parseAwsJsonError(stubErrorResponse(response));
|
|
29
|
+
if (!parsedAwsError) {
|
|
30
|
+
// Response is not considered an error.
|
|
31
|
+
} else {
|
|
32
|
+
const bodyText = await response.body?.text();
|
|
33
|
+
|
|
34
|
+
return buildRestApiError(parsedAwsError, {
|
|
35
|
+
statusCode: response.statusCode,
|
|
36
|
+
headers: response.headers,
|
|
37
|
+
body: bodyText,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The response object needs to be stub here because the parseAwsJsonError assumes the response body to be valid JSON.
|
|
44
|
+
* Although this is true for AWS services, it is not true for responses from user's code. Once the response body is
|
|
45
|
+
* unwrapped as JSON(and fail), it cannot be read as text again. Therefore, we need to stub the response body here to
|
|
46
|
+
* make sure we can read the error response body as a JSON, and may fall back to read as text if it is not a valid JSON.
|
|
47
|
+
*/
|
|
48
|
+
const stubErrorResponse = (response: HttpResponse): HttpResponse => {
|
|
49
|
+
let bodyTextPromise: Promise<string> | undefined;
|
|
50
|
+
const bodyProxy = new Proxy(response.body, {
|
|
51
|
+
get(target, prop, receiver) {
|
|
52
|
+
if (prop === 'json') {
|
|
53
|
+
// For potential AWS errors, error parser will try to parse the body as JSON first.
|
|
54
|
+
return async () => {
|
|
55
|
+
if (!bodyTextPromise) {
|
|
56
|
+
bodyTextPromise = target.text();
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(await bodyTextPromise!);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
// If response body is not a valid JSON, we stub it to be an empty object and eventually parsed
|
|
62
|
+
// as an unknown error
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
} else if (prop === 'text') {
|
|
67
|
+
// For non-AWS errors, users can access the body as a string as a fallback.
|
|
68
|
+
return async () => {
|
|
69
|
+
if (!bodyTextPromise) {
|
|
70
|
+
bodyTextPromise = target.text();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return bodyTextPromise;
|
|
74
|
+
};
|
|
75
|
+
} else {
|
|
76
|
+
return Reflect.get(target, prop, receiver);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
const responseProxy = new Proxy(response, {
|
|
81
|
+
get(target, prop, receiver) {
|
|
82
|
+
if (prop === 'body') {
|
|
83
|
+
return bodyProxy;
|
|
84
|
+
} else {
|
|
85
|
+
return Reflect.get(target, prop, receiver);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return responseProxy;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Utility to create a new RestApiError from a service error.
|
|
95
|
+
*/
|
|
96
|
+
const buildRestApiError = (
|
|
97
|
+
error: Error & MetadataBearer,
|
|
98
|
+
response?: ApiErrorResponse,
|
|
99
|
+
): RestApiError & MetadataBearer => {
|
|
100
|
+
const restApiError = new RestApiError({
|
|
101
|
+
name: error?.name,
|
|
102
|
+
message: error.message,
|
|
103
|
+
underlyingError: error,
|
|
104
|
+
response,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// $metadata is only required for backwards compatibility.
|
|
108
|
+
return Object.assign(restApiError, { $metadata: error.$metadata });
|
|
109
|
+
};
|