-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(api): add custom endpoint support to API #14086
Changes from all commits
3274385
ce857c9
4f8e7a9
93b5136
2b2c30f
c65e2b9
ba4c61d
7f6a612
e61d28d
27123f5
332580f
a6c912c
9614453
ff64e6b
9302b10
3549f4a
a89d135
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,7 +52,7 @@ export class InternalGraphQLAPIClass { | |
/** | ||
* @private | ||
*/ | ||
private appSyncRealTime = new AWSAppSyncRealTimeProvider(); | ||
private appSyncRealTime = new Map<string, AWSAppSyncRealTimeProvider>(); | ||
|
||
private _api = { | ||
post, | ||
|
@@ -88,7 +88,14 @@ export class InternalGraphQLAPIClass { | |
amplify: | ||
| AmplifyClassV6 | ||
| ((fn: (amplify: any) => Promise<any>) => Promise<AmplifyClassV6>), | ||
{ query: paramQuery, variables = {}, authMode, authToken }: GraphQLOptions, | ||
{ | ||
query: paramQuery, | ||
variables = {}, | ||
authMode, | ||
authToken, | ||
endpoint, | ||
apiKey, | ||
}: GraphQLOptions, | ||
additionalHeaders?: CustomHeaders, | ||
customUserAgentDetails?: CustomUserAgentDetails, | ||
): Observable<GraphQLResult<T>> | Promise<GraphQLResult<T>> { | ||
|
@@ -115,7 +122,7 @@ export class InternalGraphQLAPIClass { | |
if (isAmplifyInstance(amplify)) { | ||
responsePromise = this._graphql<T>( | ||
amplify, | ||
{ query, variables, authMode }, | ||
{ query, variables, authMode, apiKey, endpoint }, | ||
headers, | ||
abortController, | ||
customUserAgentDetails, | ||
|
@@ -127,7 +134,7 @@ export class InternalGraphQLAPIClass { | |
const wrapper = async (amplifyInstance: AmplifyClassV6) => { | ||
const result = await this._graphql<T>( | ||
amplifyInstance, | ||
{ query, variables, authMode }, | ||
{ query, variables, authMode, apiKey, endpoint }, | ||
headers, | ||
abortController, | ||
customUserAgentDetails, | ||
|
@@ -152,7 +159,7 @@ export class InternalGraphQLAPIClass { | |
case 'subscription': | ||
return this._graphqlSubscribe( | ||
amplify as AmplifyClassV6, | ||
{ query, variables, authMode }, | ||
{ query, variables, authMode, apiKey, endpoint }, | ||
headers, | ||
customUserAgentDetails, | ||
authToken, | ||
|
@@ -164,7 +171,13 @@ export class InternalGraphQLAPIClass { | |
|
||
private async _graphql<T = any>( | ||
amplify: AmplifyClassV6, | ||
{ query, variables, authMode: explicitAuthMode }: GraphQLOptions, | ||
{ | ||
query, | ||
variables, | ||
authMode: authModeOverride, | ||
endpoint: endpointOverride, | ||
apiKey: apiKeyOverride, | ||
}: GraphQLOptions, | ||
additionalHeaders: CustomHeaders = {}, | ||
abortController: AbortController, | ||
customUserAgentDetails?: CustomUserAgentDetails, | ||
|
@@ -179,7 +192,7 @@ export class InternalGraphQLAPIClass { | |
defaultAuthMode, | ||
} = resolveConfig(amplify); | ||
|
||
const initialAuthMode = explicitAuthMode || defaultAuthMode || 'iam'; | ||
const initialAuthMode = authModeOverride || defaultAuthMode || 'iam'; | ||
// identityPool is an alias for iam. TODO: remove 'iam' in v7 | ||
const authMode = | ||
initialAuthMode === 'identityPool' ? 'iam' : initialAuthMode; | ||
|
@@ -205,7 +218,7 @@ export class InternalGraphQLAPIClass { | |
const requestOptions: RequestOptions = { | ||
method: 'POST', | ||
url: new AmplifyUrl( | ||
customEndpoint || appSyncGraphqlEndpoint || '', | ||
endpointOverride || customEndpoint || appSyncGraphqlEndpoint || '', | ||
).toString(), | ||
queryString: print(query as DocumentNode), | ||
}; | ||
|
@@ -226,7 +239,7 @@ export class InternalGraphQLAPIClass { | |
const authHeaders = await headerBasedAuth( | ||
amplify, | ||
authMode, | ||
apiKey, | ||
apiKeyOverride ?? apiKey, | ||
additionalCustomHeaders, | ||
); | ||
|
||
|
@@ -282,7 +295,8 @@ export class InternalGraphQLAPIClass { | |
}; | ||
} | ||
|
||
const endpoint = customEndpoint || appSyncGraphqlEndpoint; | ||
const endpoint = | ||
endpointOverride || customEndpoint || appSyncGraphqlEndpoint; | ||
|
||
if (!endpoint) { | ||
throw createGraphQLResultWithError<T>(new GraphQLApiError(NO_ENDPOINT)); | ||
|
@@ -341,15 +355,21 @@ export class InternalGraphQLAPIClass { | |
|
||
private _graphqlSubscribe( | ||
amplify: AmplifyClassV6, | ||
{ query, variables, authMode: explicitAuthMode }: GraphQLOptions, | ||
{ | ||
query, | ||
variables, | ||
authMode: authModeOverride, | ||
apiKey: apiKeyOverride, | ||
endpoint, | ||
}: GraphQLOptions, | ||
additionalHeaders: CustomHeaders = {}, | ||
customUserAgentDetails?: CustomUserAgentDetails, | ||
authToken?: string, | ||
): Observable<any> { | ||
const config = resolveConfig(amplify); | ||
|
||
const initialAuthMode = | ||
explicitAuthMode || config?.defaultAuthMode || 'iam'; | ||
authModeOverride || config?.defaultAuthMode || 'iam'; | ||
// identityPool is an alias for iam. TODO: remove 'iam' in v7 | ||
const authMode = | ||
initialAuthMode === 'identityPool' ? 'iam' : initialAuthMode; | ||
|
@@ -364,15 +384,26 @@ export class InternalGraphQLAPIClass { | |
*/ | ||
const { headers: libraryConfigHeaders } = resolveLibraryOptions(amplify); | ||
|
||
return this.appSyncRealTime | ||
const appSyncGraphqlEndpoint = endpoint ?? config?.endpoint; | ||
|
||
// TODO: This could probably be an exception. But, lots of tests rely on | ||
// attempting to connect to nowhere. So, I'm treating as the opposite of | ||
// a Chesterton's fence for now. (A fence I shouldn't build, because I don't | ||
// know why somethings depends on its absence!) | ||
const memoKey = appSyncGraphqlEndpoint ?? 'none'; | ||
const realtimeProvider = | ||
this.appSyncRealTime.get(memoKey) ?? new AWSAppSyncRealTimeProvider(); | ||
this.appSyncRealTime.set(memoKey, realtimeProvider); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This overwrites the memoKey, even when we just retrieved the provider from the set doesn't it? It's probably not doing harm, but seems like an unnecessary operation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah. I weighed it against a conditional check to do the same. This seemed simpler to read. But, I don't mind changing it if you disagree. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The conditional check seems like fewer moving parts or things to go wrong, but I also don't think I can come up with an example of when this would fail us, so this is a nit at best. |
||
|
||
return realtimeProvider | ||
.subscribe( | ||
{ | ||
query: print(query as DocumentNode), | ||
variables, | ||
appSyncGraphqlEndpoint: config?.endpoint, | ||
appSyncGraphqlEndpoint, | ||
region: config?.region, | ||
authenticationType: authMode, | ||
apiKey: config?.apiKey, | ||
apiKey: apiKeyOverride ?? config?.apiKey, | ||
additionalHeaders, | ||
authToken, | ||
libraryConfigHeaders, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this memoKey sufficient? Wondering if a websocket might ever be accessed with different authtypes to achieve different permissions where both would have the same endpoint (or something similar)
I think in all case, overriding a graphql calls authtype works, but maybe a client with authtype A will be recreated with authtype B and the dev will be surprised that they get the original instance with type A...
Any other config that might be needed in the memoKey to avoid surprises?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be OK. We have normally used a single socket for all connections regardless of the authmode needed by the subscription itself. In chatting with Ivan about how to do this, I think he leaned towards keeping connections pooled by client — in most cases, I think that's slightly better for customers. Fewer local resources consumed at the very least, etc.. It's marginal. I wouldn't die on that hill.
If it causes issues, I think this behavior is pretty OK for us to change later.