Skip to content

Commit

Permalink
In browser extension, make all GraphQL api requests from background page
Browse files Browse the repository at this point in the history
Fixes #1945

Makes sure all GraphQL API requests are sent from the background page, so as to bypass CORS restrictions when running on private code hosts with the public Sourcegraph instance. This allows us to run extensions on private code hosts without needing a private Sourcegraph instance.
  • Loading branch information
lguychard committed Jan 18, 2019
1 parent 6061311 commit 52624d6
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 5 deletions.
1 change: 1 addition & 0 deletions client/browser/src/browser/runtime.ts
Expand Up @@ -18,6 +18,7 @@ export interface Message {
| 'fetched-files'
| 'repo-closed'
| 'createBlobURL'
| 'requestGraphQL'
payload?: any
}

Expand Down
7 changes: 7 additions & 0 deletions client/browser/src/extension/scripts/background.tsx
Expand Up @@ -15,6 +15,7 @@ import { featureFlagDefaults, FeatureFlags } from '../../browser/types'
import initializeCli from '../../libs/cli'
import { initSentry } from '../../libs/sentry'
import { createBlobURLForBundle, createExtensionHostWorker } from '../../platform/worker'
import { requestGraphQL } from '../../shared/backend/graphql'
import { resolveClientConfiguration } from '../../shared/backend/server'
import { DEFAULT_SOURCEGRAPH_URL, setSourcegraphUrl } from '../../shared/util/context'
import { assertEnv } from '../envAssertion'
Expand Down Expand Up @@ -289,6 +290,12 @@ runtime.onMessage((message, _, cb) => {
throw new Error(`Unable to create blob url for bundle ${message.payload} error: ${err}`)
})
return true
case 'requestGraphQL':
requestGraphQL(message.payload)
.toPromise()
.then(result => cb && cb({ result }))
.catch(err => cb && cb({ err }))
return true
}

return
Expand Down
61 changes: 56 additions & 5 deletions client/browser/src/shared/backend/graphql.tsx
@@ -1,8 +1,9 @@
import { Observable, throwError } from 'rxjs'
import { from, Observable, throwError } from 'rxjs'
import { ajax } from 'rxjs/ajax'
import { catchError, map, switchMap } from 'rxjs/operators'
import { GraphQLResult } from '../../../../../shared/src/graphql/graphql'
import * as GQL from '../../../../../shared/src/graphql/schema'
import { isBackground, isInPage } from '../../context'
import { removeAccessToken } from '../auth/access_token'
import { DEFAULT_SOURCEGRAPH_URL, isPrivateRepository, repoUrlCache, sourcegraphUrl } from '../util/context'
import { RequestContext } from './context'
Expand Down Expand Up @@ -35,7 +36,7 @@ export interface GraphQLRequestArgs {
* @param options configuration options for the request
* @return Observable That emits the result or errors if the HTTP request failed
*/
export function requestGraphQL<T extends GQL.IGraphQLResponseRoot>({
export const requestGraphQL: typeof performRequest = ({
ctx,
request,
variables = {},
Expand All @@ -44,15 +45,65 @@ export function requestGraphQL<T extends GQL.IGraphQLResponseRoot>({
useAccessToken = true,
authError,
requestMightContainPrivateInfo = true,
}: GraphQLRequestArgs): Observable<T> {
const nameMatch = request.match(/^\s*(?:query|mutation)\s+(\w+)/)
const queryName = nameMatch ? '?' + nameMatch[1] : ''
}: GraphQLRequestArgs) => {
// #1945: make sure all GraphQL API requests are sent
// from the background page, so as to bypass CORS restrictions
// when running on private code hosts with the public Sourcegraph instance.
// This allows us to run extensions on private code hosts without
// needing a private Sourcegraph instance.
if (isBackground || isInPage) {
return performRequest({
ctx,
request,
variables,
url,
retry,
useAccessToken,
authError,
requestMightContainPrivateInfo,
})
}

// Check if it's a private repo - if so don't make a request to Sourcegraph.com.
const nameMatch = request.match(/^\s*(?:query|mutation)\s+(\w+)/)
if (isPrivateRepository() && url === DEFAULT_SOURCEGRAPH_URL && requestMightContainPrivateInfo) {
return throwError(new PrivateRepoPublicSourcegraphComError(nameMatch ? nameMatch[1] : '<unnamed>'))
}

return from(
new Promise<any>((resolve, reject) => {
chrome.runtime.sendMessage(
{
type: 'requestGraphQL',
payload: {
ctx,
request,
variables,
url,
retry,
useAccessToken,
authError,
requestMightContainPrivateInfo,
},
},
({ err, result }) => (err ? reject(err) : resolve(result))
)
})
)
}

function performRequest<T extends GQL.IGraphQLResponseRoot>({
ctx,
request,
variables,
url = sourcegraphUrl,
retry,
useAccessToken,
authError,
requestMightContainPrivateInfo,
}: GraphQLRequestArgs): Observable<T> {
const nameMatch = request.match(/^\s*(?:query|mutation)\s+(\w+)/)
const queryName = nameMatch ? '?' + nameMatch[1] : ''
return getHeaders(url, useAccessToken).pipe(
switchMap(headers =>
ajax({
Expand Down

0 comments on commit 52624d6

Please sign in to comment.