@@ -2,8 +2,9 @@ import { QueryResult } from '@sourcegraph/extensions-client-common/lib/graphql'
22import { IQuery } from '@sourcegraph/extensions-client-common/lib/schema/graphqlschema'
33import { Observable , throwError } from 'rxjs'
44import { ajax } from 'rxjs/ajax'
5- import { catchError , map } from 'rxjs/operators'
5+ import { catchError , map , switchMap } from 'rxjs/operators'
66import { GQL } from '../../types/gqlschema'
7+ import { removeAccessToken } from '../auth/access_token'
78import { DEFAULT_SOURCEGRAPH_URL , isPrivateRepository , repoUrlCache , sourcegraphUrl } from '../util/context'
89import { RequestContext } from './context'
910import { AuthRequiredError , createAuthRequiredError , NoSourcegraphURLError } from './errors'
@@ -17,19 +18,33 @@ export interface MutationResult {
1718 errors ?: GQL . IGraphQLResponseError [ ]
1819}
1920
21+ interface RequestGraphQLOptions {
22+ /** Whether we should use the retry logic to fall back to other URLs. */
23+ retry ?: boolean
24+ /**
25+ * Whether or not to use an access token for the request. All requests
26+ * except requests used while creating an access token should use an access
27+ * token. i.e. `createAccessToken` and the `fetchCurrentUser` used to get the
28+ * user ID for `createAccessToken`.
29+ */
30+ useAccessToken ?: boolean
31+ }
32+
2033/**
2134 * Does a GraphQL request to the Sourcegraph GraphQL API running under `/.api/graphql`
2235 *
2336 * @param request The GraphQL request (query or mutation)
2437 * @param variables A key/value object with variable values
38+ * @param url the url the request is going to
39+ * @param options configuration options for the request
2540 * @return Observable That emits the result or errors if the HTTP request failed
2641 */
2742function requestGraphQL (
2843 ctx : RequestContext ,
2944 request : string ,
3045 variables : any = { } ,
3146 url : string = sourcegraphUrl ,
32- retry = true ,
47+ { retry, useAccessToken } : RequestGraphQLOptions = { retry : true , useAccessToken : true } ,
3348 authError ?: AuthRequiredError
3449) : Observable < GQL . IGraphQLResponseRoot > {
3550 // Check if it's a private repo - if so don't make a request to Sourcegraph.com.
@@ -39,44 +54,67 @@ function requestGraphQL(
3954 const nameMatch = request . match ( / ^ \s * (?: q u e r y | m u t a t i o n ) \s + ( \w + ) / )
4055 const queryName = nameMatch ? '?' + nameMatch [ 1 ] : ''
4156
42- return ajax ( {
43- method : 'POST' ,
44- url : `${ url } /.api/graphql` + queryName ,
45- headers : getHeaders ( ) ,
46- crossDomain : true ,
47- withCredentials : true ,
48- body : JSON . stringify ( { query : request , variables } ) ,
49- async : true ,
50- } ) . pipe (
51- map ( ( { response } ) => {
52- if ( shouldResponseTriggerRetryOrError ( response ) ) {
53- delete repoUrlCache [ ctx . repoKey ]
54- throw response
55- }
56- if ( ctx . isRepoSpecific && response . data . repository ) {
57- repoUrlCache [ ctx . repoKey ] = url
58- }
59- return response
60- } ) ,
61- catchError ( err => {
62- if ( err . status === 401 ) {
63- // Ensure all urls are tried and update authError to be the last seen 401.
64- // This ensures that the correct URL is used for sign in and also that all possible
65- // urls were checked.
66- authError = createAuthRequiredError ( url )
67- }
57+ return getHeaders ( url , useAccessToken ) . pipe (
58+ switchMap ( headers =>
59+ ajax ( {
60+ method : 'POST' ,
61+ url : `${ url } /.api/graphql` + queryName ,
62+ headers,
63+ crossDomain : true ,
64+ withCredentials : true ,
65+ body : JSON . stringify ( { query : request , variables } ) ,
66+ async : true ,
67+ } ) . pipe (
68+ map ( ( { response } ) => {
69+ if ( shouldResponseTriggerRetryOrError ( response ) ) {
70+ delete repoUrlCache [ ctx . repoKey ]
71+ throw response
72+ }
73+ if ( ctx . isRepoSpecific && response . data . repository ) {
74+ repoUrlCache [ ctx . repoKey ] = url
75+ }
76+ return response
77+ } ) ,
78+ catchError ( err => {
79+ if ( err . status === 401 ) {
80+ // Ensure all urls are tried and update authError to be the last seen 401.
81+ // This ensures that the correct URL is used for sign in and also that all possible
82+ // urls were checked.
83+ authError = createAuthRequiredError ( url )
84+
85+ if ( headers && headers . authorization ) {
86+ // If we got a 401 with a token, get rid of the and
87+ // try again. The token may be invalid and we just
88+ // need to recreate one.
89+ return removeAccessToken ( url ) . pipe (
90+ switchMap ( ( ) =>
91+ requestGraphQL ( ctx , request , variables , url , { retry, useAccessToken } , authError )
92+ )
93+ )
94+ }
95+ }
96+
97+ if ( ! retry || url === DEFAULT_SOURCEGRAPH_URL ) {
98+ // If there was an auth error and we tried all of the possible URLs throw the auth error.
99+ if ( authError ) {
100+ throw authError
101+ }
102+ delete repoUrlCache [ ctx . repoKey ]
103+ // We just tried the last url
104+ throw err
105+ }
68106
69- if ( ! retry || url === DEFAULT_SOURCEGRAPH_URL ) {
70- // If there was an auth error and we tried all of the possible URLs throw the auth error.
71- if ( authError ) {
72- throw authError
73- }
74- delete repoUrlCache [ ctx . repoKey ]
75- // We just tried the last url
76- throw err
77- }
78- return requestGraphQL ( ctx , request , variables , DEFAULT_SOURCEGRAPH_URL , retry , authError )
79- } )
107+ return requestGraphQL (
108+ ctx ,
109+ request ,
110+ variables ,
111+ DEFAULT_SOURCEGRAPH_URL ,
112+ { retry , useAccessToken : true } ,
113+ authError
114+ )
115+ } )
116+ )
117+ )
80118 )
81119}
82120
@@ -140,9 +178,12 @@ export function queryGraphQLNoRetry(
140178 ctx : RequestContext ,
141179 query : string ,
142180 variables : any = { } ,
143- url : string = sourcegraphUrl
181+ url : string = sourcegraphUrl ,
182+ useAccessToken ?: boolean
144183) : Observable < QueryResult < IQuery > > {
145- return requestGraphQL ( ctx , query , variables , url , false ) as Observable < QueryResult < IQuery > >
184+ return requestGraphQL ( ctx , query , variables , url , { retry : false , useAccessToken } ) as Observable <
185+ QueryResult < IQuery >
186+ >
146187}
147188
148189/**
@@ -167,7 +208,10 @@ export function mutateGraphQL(ctx: RequestContext, mutation: string, variables:
167208export function mutateGraphQLNoRetry (
168209 ctx : RequestContext ,
169210 mutation : string ,
170- variables : any = { }
211+ variables : any = { } ,
212+ useAccessToken ?: boolean
171213) : Observable < MutationResult > {
172- return requestGraphQL ( ctx , mutation , variables , sourcegraphUrl , false ) as Observable < MutationResult >
214+ return requestGraphQL ( ctx , mutation , variables , sourcegraphUrl , { retry : false , useAccessToken } ) as Observable <
215+ MutationResult
216+ >
173217}
0 commit comments