@aws-amplify/api-graphql 3.3.2-unstable.f47d472.1 → 3.4.0

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.
@@ -0,0 +1,449 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import {
4
+ DocumentNode,
5
+ OperationDefinitionNode,
6
+ print,
7
+ parse,
8
+ GraphQLError,
9
+ OperationTypeNode,
10
+ } from 'graphql';
11
+ import Observable from 'zen-observable-ts';
12
+ import {
13
+ Amplify,
14
+ ConsoleLogger as Logger,
15
+ Credentials,
16
+ CustomUserAgentDetails,
17
+ getAmplifyUserAgentString,
18
+ INTERNAL_AWS_APPSYNC_REALTIME_PUBSUB_PROVIDER,
19
+ } from '@aws-amplify/core';
20
+ import { InternalPubSub } from '@aws-amplify/pubsub/internals';
21
+ import { Auth } from '@aws-amplify/auth';
22
+ import { Cache } from '@aws-amplify/cache';
23
+ import {
24
+ GraphQLAuthError,
25
+ GraphQLOptions,
26
+ GraphQLResult,
27
+ GraphQLOperation,
28
+ } from '../types';
29
+ import { RestClient } from '@aws-amplify/api-rest';
30
+ const USER_AGENT_HEADER = 'x-amz-user-agent';
31
+
32
+ const logger = new Logger('GraphQLAPI');
33
+
34
+ export const graphqlOperation = (
35
+ query,
36
+ variables = {},
37
+ authToken?: string
38
+ ) => ({
39
+ query,
40
+ variables,
41
+ authToken,
42
+ });
43
+
44
+ /**
45
+ * Export Cloud Logic APIs
46
+ */
47
+ export class InternalGraphQLAPIClass {
48
+ /**
49
+ * @private
50
+ */
51
+ private _options;
52
+ private _api = null;
53
+
54
+ Auth = Auth;
55
+ Cache = Cache;
56
+ Credentials = Credentials;
57
+
58
+ /**
59
+ * Initialize GraphQL API with AWS configuration
60
+ * @param {Object} options - Configuration object for API
61
+ */
62
+ constructor(options) {
63
+ this._options = options;
64
+ logger.debug('API Options', this._options);
65
+ }
66
+
67
+ public getModuleName() {
68
+ return 'InternalGraphQLAPI';
69
+ }
70
+
71
+ /**
72
+ * Configure API
73
+ * @param {Object} config - Configuration of the API
74
+ * @return {Object} - The current configuration
75
+ */
76
+ configure(options) {
77
+ const { API = {}, ...otherOptions } = options || {};
78
+ let opt = { ...otherOptions, ...API };
79
+ logger.debug('configure GraphQL API', { opt });
80
+
81
+ if (opt['aws_project_region']) {
82
+ opt = Object.assign({}, opt, {
83
+ region: opt['aws_project_region'],
84
+ header: {},
85
+ });
86
+ }
87
+
88
+ if (
89
+ typeof opt.graphql_headers !== 'undefined' &&
90
+ typeof opt.graphql_headers !== 'function'
91
+ ) {
92
+ logger.warn('graphql_headers should be a function');
93
+ opt.graphql_headers = undefined;
94
+ }
95
+
96
+ this._options = Object.assign({}, this._options, opt);
97
+
98
+ this.createInstance();
99
+
100
+ return this._options;
101
+ }
102
+
103
+ /**
104
+ * Create an instance of API for the library
105
+ * @return - A promise of true if Success
106
+ */
107
+ createInstance() {
108
+ logger.debug('create Rest instance');
109
+ if (this._options) {
110
+ this._api = new RestClient(this._options);
111
+ // Share instance Credentials with client for SSR
112
+ this._api.Credentials = this.Credentials;
113
+
114
+ return true;
115
+ } else {
116
+ return Promise.reject('API not configured');
117
+ }
118
+ }
119
+
120
+ private async _headerBasedAuth(
121
+ defaultAuthenticationType?,
122
+ additionalHeaders: { [key: string]: string } = {}
123
+ ) {
124
+ const { aws_appsync_authenticationType, aws_appsync_apiKey: apiKey } =
125
+ this._options;
126
+ const authenticationType =
127
+ defaultAuthenticationType || aws_appsync_authenticationType || 'AWS_IAM';
128
+ let headers = {};
129
+
130
+ switch (authenticationType) {
131
+ case 'API_KEY':
132
+ if (!apiKey) {
133
+ throw new Error(GraphQLAuthError.NO_API_KEY);
134
+ }
135
+ headers = {
136
+ Authorization: null,
137
+ 'X-Api-Key': apiKey,
138
+ };
139
+ break;
140
+ case 'AWS_IAM':
141
+ const credentialsOK = await this._ensureCredentials();
142
+ if (!credentialsOK) {
143
+ throw new Error(GraphQLAuthError.NO_CREDENTIALS);
144
+ }
145
+ break;
146
+ case 'OPENID_CONNECT':
147
+ try {
148
+ let token;
149
+ // backwards compatibility
150
+ const federatedInfo = await Cache.getItem('federatedInfo');
151
+ if (federatedInfo) {
152
+ token = federatedInfo.token;
153
+ } else {
154
+ const currentUser = await Auth.currentAuthenticatedUser();
155
+ if (currentUser) {
156
+ token = currentUser.token;
157
+ }
158
+ }
159
+ if (!token) {
160
+ throw new Error(GraphQLAuthError.NO_FEDERATED_JWT);
161
+ }
162
+ headers = {
163
+ Authorization: token,
164
+ };
165
+ } catch (e) {
166
+ throw new Error(GraphQLAuthError.NO_CURRENT_USER);
167
+ }
168
+ break;
169
+ case 'AMAZON_COGNITO_USER_POOLS':
170
+ try {
171
+ const session = await this.Auth.currentSession();
172
+ headers = {
173
+ Authorization: session.getAccessToken().getJwtToken(),
174
+ };
175
+ } catch (e) {
176
+ throw new Error(GraphQLAuthError.NO_CURRENT_USER);
177
+ }
178
+ break;
179
+ case 'AWS_LAMBDA':
180
+ if (!additionalHeaders.Authorization) {
181
+ throw new Error(GraphQLAuthError.NO_AUTH_TOKEN);
182
+ }
183
+ headers = {
184
+ Authorization: additionalHeaders.Authorization,
185
+ };
186
+ break;
187
+ default:
188
+ headers = {
189
+ Authorization: null,
190
+ };
191
+ break;
192
+ }
193
+
194
+ return headers;
195
+ }
196
+
197
+ /**
198
+ * to get the operation type
199
+ * @param operation
200
+ */
201
+ getGraphqlOperationType(operation: GraphQLOperation): OperationTypeNode {
202
+ const doc = parse(operation);
203
+ const definitions =
204
+ doc.definitions as ReadonlyArray<OperationDefinitionNode>;
205
+ const [{ operation: operationType }] = definitions;
206
+
207
+ return operationType;
208
+ }
209
+
210
+ /**
211
+ * Executes a GraphQL operation
212
+ *
213
+ * @param options - GraphQL Options
214
+ * @param [additionalHeaders] - headers to merge in after any `graphql_headers` set in the config
215
+ * @returns An Observable if the query is a subscription query, else a promise of the graphql result.
216
+ */
217
+ graphql<T = any>(
218
+ { query: paramQuery, variables = {}, authMode, authToken }: GraphQLOptions,
219
+ additionalHeaders?: { [key: string]: string },
220
+ customUserAgentDetails?: CustomUserAgentDetails
221
+ ): Observable<GraphQLResult<T>> | Promise<GraphQLResult<T>> {
222
+ const query =
223
+ typeof paramQuery === 'string'
224
+ ? parse(paramQuery)
225
+ : parse(print(paramQuery));
226
+
227
+ const [operationDef = {}] = query.definitions.filter(
228
+ def => def.kind === 'OperationDefinition'
229
+ );
230
+ const { operation: operationType } =
231
+ operationDef as OperationDefinitionNode;
232
+
233
+ const headers = additionalHeaders || {};
234
+
235
+ // if an authorization header is set, have the explicit authToken take precedence
236
+ if (authToken) {
237
+ headers.Authorization = authToken;
238
+ }
239
+
240
+ switch (operationType) {
241
+ case 'query':
242
+ case 'mutation':
243
+ this.createInstanceIfNotCreated();
244
+ const cancellableToken = this._api.getCancellableToken();
245
+ const initParams = {
246
+ cancellableToken,
247
+ withCredentials: this._options.withCredentials,
248
+ };
249
+ const responsePromise = this._graphql<T>(
250
+ { query, variables, authMode },
251
+ headers,
252
+ initParams,
253
+ customUserAgentDetails
254
+ );
255
+ this._api.updateRequestToBeCancellable(
256
+ responsePromise,
257
+ cancellableToken
258
+ );
259
+ return responsePromise;
260
+ case 'subscription':
261
+ return this._graphqlSubscribe(
262
+ { query, variables, authMode },
263
+ headers,
264
+ customUserAgentDetails
265
+ );
266
+ default:
267
+ throw new Error(`invalid operation type: ${operationType}`);
268
+ }
269
+ }
270
+
271
+ private async _graphql<T = any>(
272
+ { query, variables, authMode }: GraphQLOptions,
273
+ additionalHeaders = {},
274
+ initParams = {},
275
+ customUserAgentDetails?: CustomUserAgentDetails
276
+ ): Promise<GraphQLResult<T>> {
277
+ this.createInstanceIfNotCreated();
278
+ const {
279
+ aws_appsync_region: region,
280
+ aws_appsync_graphqlEndpoint: appSyncGraphqlEndpoint,
281
+ graphql_headers = () => ({}),
282
+ graphql_endpoint: customGraphqlEndpoint,
283
+ graphql_endpoint_iam_region: customEndpointRegion,
284
+ } = this._options;
285
+
286
+ const headers = {
287
+ ...(!customGraphqlEndpoint &&
288
+ (await this._headerBasedAuth(authMode, additionalHeaders))),
289
+ ...(customGraphqlEndpoint &&
290
+ (customEndpointRegion
291
+ ? await this._headerBasedAuth(authMode, additionalHeaders)
292
+ : { Authorization: null })),
293
+ ...(await graphql_headers({ query, variables })),
294
+ ...additionalHeaders,
295
+ ...(!customGraphqlEndpoint && {
296
+ [USER_AGENT_HEADER]: getAmplifyUserAgentString(customUserAgentDetails),
297
+ }),
298
+ };
299
+
300
+ const body = {
301
+ query: print(query as DocumentNode),
302
+ variables,
303
+ };
304
+
305
+ const init = Object.assign(
306
+ {
307
+ headers,
308
+ body,
309
+ signerServiceInfo: {
310
+ service: !customGraphqlEndpoint ? 'appsync' : 'execute-api',
311
+ region: !customGraphqlEndpoint ? region : customEndpointRegion,
312
+ },
313
+ },
314
+ initParams
315
+ );
316
+
317
+ const endpoint = customGraphqlEndpoint || appSyncGraphqlEndpoint;
318
+
319
+ if (!endpoint) {
320
+ const error = new GraphQLError('No graphql endpoint provided.');
321
+
322
+ throw {
323
+ data: {},
324
+ errors: [error],
325
+ };
326
+ }
327
+
328
+ let response;
329
+ try {
330
+ response = await this._api.post(endpoint, init);
331
+ } catch (err) {
332
+ // If the exception is because user intentionally
333
+ // cancelled the request, do not modify the exception
334
+ // so that clients can identify the exception correctly.
335
+ if (this._api.isCancel(err)) {
336
+ throw err;
337
+ }
338
+ response = {
339
+ data: {},
340
+ errors: [new GraphQLError(err.message, null, null, null, null, err)],
341
+ };
342
+ }
343
+
344
+ const { errors } = response;
345
+
346
+ if (errors && errors.length) {
347
+ throw response;
348
+ }
349
+
350
+ return response;
351
+ }
352
+
353
+ async createInstanceIfNotCreated() {
354
+ if (!this._api) {
355
+ await this.createInstance();
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Checks to see if an error thrown is from an api request cancellation
361
+ * @param {any} error - Any error
362
+ * @return {boolean} - A boolean indicating if the error was from an api request cancellation
363
+ */
364
+ isCancel(error) {
365
+ return this._api.isCancel(error);
366
+ }
367
+
368
+ /**
369
+ * Cancels an inflight request. Only applicable for graphql queries and mutations
370
+ * @param {any} request - request to cancel
371
+ * @return {boolean} - A boolean indicating if the request was cancelled
372
+ */
373
+ cancel(request: Promise<any>, message?: string) {
374
+ return this._api.cancel(request, message);
375
+ }
376
+
377
+ /**
378
+ * Check if the request has a corresponding cancel token in the WeakMap.
379
+ * @params request - The request promise
380
+ * @return if the request has a corresponding cancel token.
381
+ */
382
+ hasCancelToken(request: Promise<any>) {
383
+ return this._api.hasCancelToken(request);
384
+ }
385
+
386
+ private _graphqlSubscribe(
387
+ {
388
+ query,
389
+ variables,
390
+ authMode: defaultAuthenticationType,
391
+ authToken,
392
+ }: GraphQLOptions,
393
+ additionalHeaders = {},
394
+ customUserAgentDetails?: CustomUserAgentDetails
395
+ ): Observable<any> {
396
+ const {
397
+ aws_appsync_region: region,
398
+ aws_appsync_graphqlEndpoint: appSyncGraphqlEndpoint,
399
+ aws_appsync_authenticationType,
400
+ aws_appsync_apiKey: apiKey,
401
+ graphql_headers = () => ({}),
402
+ } = this._options;
403
+ const authenticationType =
404
+ defaultAuthenticationType || aws_appsync_authenticationType || 'AWS_IAM';
405
+
406
+ if (InternalPubSub && typeof InternalPubSub.subscribe === 'function') {
407
+ return InternalPubSub.subscribe(
408
+ '',
409
+ {
410
+ provider: INTERNAL_AWS_APPSYNC_REALTIME_PUBSUB_PROVIDER,
411
+ appSyncGraphqlEndpoint,
412
+ authenticationType,
413
+ apiKey,
414
+ query: print(query as DocumentNode),
415
+ region,
416
+ variables,
417
+ graphql_headers,
418
+ additionalHeaders,
419
+ authToken,
420
+ },
421
+ customUserAgentDetails
422
+ );
423
+ } else {
424
+ logger.debug('No pubsub module applied for subscription');
425
+ throw new Error('No pubsub module applied for subscription');
426
+ }
427
+ }
428
+
429
+ /**
430
+ * @private
431
+ */
432
+ _ensureCredentials() {
433
+ return this.Credentials.get()
434
+ .then(credentials => {
435
+ if (!credentials) return false;
436
+ const cred = this.Credentials.shear(credentials);
437
+ logger.debug('set credentials for api', cred);
438
+
439
+ return true;
440
+ })
441
+ .catch(err => {
442
+ logger.warn('ensure credentials error', err);
443
+ return false;
444
+ });
445
+ }
446
+ }
447
+
448
+ export const InternalGraphQLAPI = new InternalGraphQLAPIClass(null);
449
+ Amplify.register(InternalGraphQLAPI);
@@ -0,0 +1,6 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ export {
4
+ InternalGraphQLAPI,
5
+ InternalGraphQLAPIClass,
6
+ } from './InternalGraphQLAPI';
@@ -1,16 +1,20 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- import { Source, DocumentNode, GraphQLError } from 'graphql';
3
+ import { Source, DocumentNode, GraphQLError, OperationTypeNode } from 'graphql';
4
4
  export { OperationTypeNode } from 'graphql';
5
5
  import { GRAPHQL_AUTH_MODE } from '@aws-amplify/auth';
6
6
  export { GRAPHQL_AUTH_MODE };
7
+ import { CustomUserAgentDetails } from '@aws-amplify/core';
7
8
 
8
9
  export interface GraphQLOptions {
9
10
  query: string | DocumentNode;
10
11
  variables?: object;
11
12
  authMode?: keyof typeof GRAPHQL_AUTH_MODE;
12
13
  authToken?: string;
13
- userAgentSuffix?: string;
14
+ /**
15
+ * @deprecated This property should not be used
16
+ */
17
+ userAgentSuffix?: string; // TODO: remove in v6
14
18
  }
15
19
 
16
20
  export interface GraphQLResult<T = object> {