diff --git a/package-lock.json b/package-lock.json index 3e8194a3..f4bf1328 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1551,9 +1551,9 @@ } }, "@sourcegraph/extensions-client-common": { - "version": "8.1.4", - "resolved": "https://registry.npmjs.org/@sourcegraph/extensions-client-common/-/extensions-client-common-8.1.4.tgz", - "integrity": "sha512-jBtsR1p6zMBoD2a0ja6fc9zd6Qzno1Np8SLqkiFXzfMLQuWQLDE9Z+DzgBDUPh2aR6hjTC2mMdlS8Pyi6Tjk2Q==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@sourcegraph/extensions-client-common/-/extensions-client-common-8.2.0.tgz", + "integrity": "sha512-PG5D+2I06tVWURgiAONOLOIpItQcz1rNV0DKmYE9RmMt/2BzUFUzSrGcgMtO9Q2WrZdHy0UWV3Ng8JVuTaEIWw==", "requires": { "bootstrap": "^4.1.3", "lodash-es": "^4.17.10", @@ -8257,14 +8257,12 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "optional": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8279,20 +8277,17 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "optional": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "optional": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "optional": true + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "core-util-is": { "version": "1.0.2", @@ -8409,8 +8404,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "optional": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", @@ -8422,7 +8416,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -8437,7 +8430,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8445,14 +8437,12 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "optional": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minipass": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.4.tgz", "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -8471,7 +8461,6 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "optional": true, "requires": { "minimist": "0.0.8" } @@ -8558,8 +8547,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "optional": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "object-assign": { "version": "4.1.1", @@ -8571,7 +8559,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "optional": true, "requires": { "wrappy": "1" } @@ -8693,7 +8680,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/package.json b/package.json index 79755591..fc97460f 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ }, "dependencies": { "@sourcegraph/codeintellify": "^3.8.3", - "@sourcegraph/extensions-client-common": "^8.1.4", + "@sourcegraph/extensions-client-common": "^8.2.0", "@sourcegraph/react-loading-spinner": "0.0.6", "@sqs/jsonc-parser": "^1.0.3", "bootstrap": "^4.0.0", diff --git a/src/libs/sourcegraph/inject.tsx b/src/libs/sourcegraph/inject.tsx index 843c74b3..96945b1a 100644 --- a/src/libs/sourcegraph/inject.tsx +++ b/src/libs/sourcegraph/inject.tsx @@ -1,20 +1,44 @@ +import { connectAsClient } from '@sourcegraph/extensions-client-common/lib/messaging' +import storage from '../../browser/storage' +import { updateExtensionSettings } from '../../shared/backend/extensions' + export function injectSourcegraphApp(marker: HTMLElement): void { if (document.getElementById(marker.id)) { return } + // Generate and insert DOM element, in case this code executes first. + document.body.appendChild(marker) + + connectAsClient() + .then(connection => { + storage.observeSync('clientSettings').subscribe(settings => { + connection.sendSettings(settings) + }) + + connection.onEditSetting(edit => updateExtensionSettings('Client', edit).toPromise()) + + connection.onGetSettings( + () => + new Promise(resolve => { + storage.getSync(storageItems => { + resolve(storageItems.clientSettings) + }) + }) + ) + }) + .catch(error => console.error(error)) + window.addEventListener('load', () => { - dispatchSourcegraphEvents(marker) + dispatchSourcegraphEvents() }) if (document.readyState === 'complete' || document.readyState === 'interactive') { - dispatchSourcegraphEvents(marker) + dispatchSourcegraphEvents() } } -function dispatchSourcegraphEvents(marker: HTMLElement): void { - // Generate and insert DOM element, in case this code executes first. - document.body.appendChild(marker) +function dispatchSourcegraphEvents(): void { // Send custom webapp <-> extension registration event in case webapp listener is attached first. document.dispatchEvent(new CustomEvent<{}>('sourcegraph:browser-extension-registration')) } diff --git a/src/shared/backend/extensions.ts b/src/shared/backend/extensions.ts index 8bf3bb8f..9a552bfb 100644 --- a/src/shared/backend/extensions.ts +++ b/src/shared/backend/extensions.ts @@ -1,3 +1,4 @@ +import { UpdateExtensionSettingsArgs } from '@sourcegraph/extensions-client-common/lib/context' import { Controller as ExtensionsContextController } from '@sourcegraph/extensions-client-common/lib/controller' import { ConfiguredExtension } from '@sourcegraph/extensions-client-common/lib/extensions/extension' import { gql, graphQLContent } from '@sourcegraph/extensions-client-common/lib/graphql' @@ -17,15 +18,14 @@ import { isEqual } from 'lodash' import Alert from 'mdi-react/AlertIcon' import MenuDown from 'mdi-react/MenuDownIcon' import Menu from 'mdi-react/MenuIcon' -import { combineLatest, Observable, Subject, throwError } from 'rxjs' +import { combineLatest, from, Observable, throwError } from 'rxjs' import { distinctUntilChanged, map, mergeMap, switchMap, take } from 'rxjs/operators' import { ClientOptions } from 'sourcegraph/module/client/client' import { MessageTransports } from 'sourcegraph/module/jsonrpc2/connection' import { TextDocumentDecoration } from 'sourcegraph/module/protocol' -import { ConfigurationUpdateParams } from 'sourcegraph/module/protocol' import uuid from 'uuid' import { Disposable } from 'vscode-languageserver' -import storage from '../../browser/storage' +import storage, { StorageItems } from '../../browser/storage' import { ExtensionConnectionInfo } from '../../extension/scripts/background' import { onFirstMessage } from '../../extension/scripts/background' import { getContext } from './context' @@ -255,45 +255,7 @@ export function createExtensionsContextController( map(([gqlCascade, storageCascade]) => mergeCascades(gqlToCascade(gqlCascade), storageCascade)), distinctUntilChanged((a, b) => isEqual(a, b)) ), - updateExtensionSettings: ( - subjectID, - args: { extensionID: string; edit?: ConfigurationUpdateParams; enabled?: boolean; remove?: boolean } - ) => { - if (subjectID !== 'Client') { - return throwError('Cannot update settings for ' + subjectID + '.') - } - const update = new Subject() - storage.getSync(storageItems => { - let clientSettings = storageItems.clientSettings - - const format = { tabSize: 2, insertSpaces: true, eol: '\n' } - - if (args.edit) { - clientSettings = applyEdits( - clientSettings, - // TODO(chris): remove `.slice()` (which guards against - // mutation) once - // https://github.com/Microsoft/node-jsonc-parser/pull/12 - // is merged in. - setProperty(clientSettings, args.edit.path.slice(), args.edit.value, format) - ) - } else if (typeof args.enabled === 'boolean') { - clientSettings = applyEdits( - clientSettings, - setProperty(clientSettings, ['extensions', args.extensionID], args.enabled, format) - ) - } else if (args.remove) { - clientSettings = applyEdits( - clientSettings, - removeProperty(clientSettings, ['extensions', args.extensionID], format) - ) - } - storage.setSync({ clientSettings }, () => { - update.next(undefined) - }) - }) - return update - }, + updateExtensionSettings, queryGraphQL: (request, variables) => storage.observeSync('sourcegraphURL').pipe( take(1), @@ -315,3 +277,41 @@ export function createExtensionsContextController( }, }) } + +export const updateExtensionSettings = (subjectID, args: UpdateExtensionSettingsArgs): Observable => { + if (subjectID !== 'Client') { + return throwError('Cannot update settings for ' + subjectID + '.') + } + return from( + new Promise(resolve => storage.getSync(storageItems => resolve(storageItems))).then( + storageItems => { + let clientSettings = storageItems.clientSettings + + const format = { tabSize: 2, insertSpaces: true, eol: '\n' } + + if ('edit' in args && args.edit) { + clientSettings = applyEdits( + clientSettings, + // TODO(chris): remove `.slice()` (which guards against + // mutation) once + // https://github.com/Microsoft/node-jsonc-parser/pull/12 + // is merged in. + setProperty(clientSettings, args.edit.path.slice(), args.edit.value, format) + ) + } else if ('extensionID' in args) { + clientSettings = applyEdits( + clientSettings, + typeof args.enabled === 'boolean' + ? setProperty(clientSettings, ['extensions', args.extensionID], args.enabled, format) + : removeProperty(clientSettings, ['extensions', args.extensionID], format) + ) + } + return new Promise(resolve => + storage.setSync({ clientSettings }, () => { + resolve(undefined) + }) + ) + } + ) + ) +} diff --git a/src/shared/components/options/ConnectionCard.tsx b/src/shared/components/options/ConnectionCard.tsx index 5adc4f30..94018615 100644 --- a/src/shared/components/options/ConnectionCard.tsx +++ b/src/shared/components/options/ConnectionCard.tsx @@ -45,13 +45,17 @@ export class ConnectionCard extends React.Component { } } + private setContentScriptUrls(props: Props): void { + this.contentScriptUrls = [...props.storage.clientConfiguration.contentScriptUrls, props.storage.sourcegraphURL] + } + public componentDidMount(): void { - this.contentScriptUrls = this.props.storage.clientConfiguration.contentScriptUrls + this.setContentScriptUrls(this.props) this.checkConnection() } public componentWillReceiveProps(nextProps: Props): void { - this.contentScriptUrls = nextProps.storage.clientConfiguration.contentScriptUrls + this.setContentScriptUrls(nextProps) } private sourcegraphServerAlert = (): JSX.Element => { @@ -74,12 +78,14 @@ export class ConnectionCard extends React.Component { ) } - const hasPermissions = this.contentScriptUrls.every(val => permissionOrigins.indexOf(`${val}/*`) >= 0) - if (!hasPermissions && !permissionOrigins.includes('')) { + const forbiddenUrls = permissionOrigins.includes('') + ? [] + : this.contentScriptUrls.filter(url => !permissionOrigins.includes(`${url}/*`)) + if (forbiddenUrls.length !== 0) { return (
- {`Missing content script permissions: ${this.contentScriptUrls.join(', ')}.`} + {`Missing content script permissions: ${forbiddenUrls.join(', ')}.`}