Path: blob/main/src/vs/workbench/services/extensionManagement/electron-browser/extensionGalleryManifestService.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { CancellationToken } from '../../../../base/common/cancellation.js';6import { Emitter } from '../../../../base/common/event.js';7import { IHeaders } from '../../../../base/parts/request/common/request.js';8import { localize } from '../../../../nls.js';9import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';10import { IEnvironmentService } from '../../../../platform/environment/common/environment.js';11import { IExtensionGalleryManifestService, IExtensionGalleryManifest, ExtensionGalleryServiceUrlConfigKey, ExtensionGalleryManifestStatus } from '../../../../platform/extensionManagement/common/extensionGalleryManifest.js';12import { ExtensionGalleryManifestService as ExtensionGalleryManifestService } from '../../../../platform/extensionManagement/common/extensionGalleryManifestService.js';13import { resolveMarketplaceHeaders } from '../../../../platform/externalServices/common/marketplace.js';14import { IFileService } from '../../../../platform/files/common/files.js';15import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';16import { ISharedProcessService } from '../../../../platform/ipc/electron-browser/services.js';17import { ILogService } from '../../../../platform/log/common/log.js';18import { IProductService } from '../../../../platform/product/common/productService.js';19import { asJson, IRequestService } from '../../../../platform/request/common/request.js';20import { IStorageService } from '../../../../platform/storage/common/storage.js';21import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';22import { IDefaultAccountService } from '../../accounts/common/defaultAccount.js';23import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js';24import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';25import { IHostService } from '../../host/browser/host.js';26import { IDefaultAccount } from '../../../../base/common/defaultAccount.js';2728export class WorkbenchExtensionGalleryManifestService extends ExtensionGalleryManifestService implements IExtensionGalleryManifestService {2930private readonly commonHeadersPromise: Promise<IHeaders>;31private extensionGalleryManifest: IExtensionGalleryManifest | null = null;3233private _onDidChangeExtensionGalleryManifest = this._register(new Emitter<IExtensionGalleryManifest | null>());34override readonly onDidChangeExtensionGalleryManifest = this._onDidChangeExtensionGalleryManifest.event;3536private currentStatus: ExtensionGalleryManifestStatus = ExtensionGalleryManifestStatus.Unavailable;37override get extensionGalleryManifestStatus(): ExtensionGalleryManifestStatus { return this.currentStatus; }38private _onDidChangeExtensionGalleryManifestStatus = this._register(new Emitter<ExtensionGalleryManifestStatus>());39override readonly onDidChangeExtensionGalleryManifestStatus = this._onDidChangeExtensionGalleryManifestStatus.event;4041constructor(42@IProductService productService: IProductService,43@IEnvironmentService environmentService: IEnvironmentService,44@IFileService fileService: IFileService,45@ITelemetryService telemetryService: ITelemetryService,46@IStorageService storageService: IStorageService,47@IRemoteAgentService remoteAgentService: IRemoteAgentService,48@ISharedProcessService sharedProcessService: ISharedProcessService,49@IConfigurationService private readonly configurationService: IConfigurationService,50@IRequestService private readonly requestService: IRequestService,51@IDefaultAccountService private readonly defaultAccountService: IDefaultAccountService,52@ILogService private readonly logService: ILogService,53@IDialogService private readonly dialogService: IDialogService,54@IHostService private readonly hostService: IHostService,55) {56super(productService);57this.commonHeadersPromise = resolveMarketplaceHeaders(58productService.version,59productService,60environmentService,61configurationService,62fileService,63storageService,64telemetryService);6566const channels = [sharedProcessService.getChannel('extensionGalleryManifest')];67const remoteConnection = remoteAgentService.getConnection();68if (remoteConnection) {69channels.push(remoteConnection.getChannel('extensionGalleryManifest'));70}71this.getExtensionGalleryManifest().then(manifest => {72channels.forEach(channel => channel.call('setExtensionGalleryManifest', [manifest]));73});74}7576private extensionGalleryManifestPromise: Promise<void> | undefined;77override async getExtensionGalleryManifest(): Promise<IExtensionGalleryManifest | null> {78if (!this.extensionGalleryManifestPromise) {79this.extensionGalleryManifestPromise = this.doGetExtensionGalleryManifest();80}81await this.extensionGalleryManifestPromise;82return this.extensionGalleryManifest;83}8485private async doGetExtensionGalleryManifest(): Promise<void> {86const defaultServiceUrl = this.productService.extensionsGallery?.serviceUrl;87if (!defaultServiceUrl) {88return;89}9091const configuredServiceUrl = this.configurationService.getValue<string>(ExtensionGalleryServiceUrlConfigKey);92if (configuredServiceUrl) {93await this.handleDefaultAccountAccess(configuredServiceUrl);94this._register(this.defaultAccountService.onDidChangeDefaultAccount(() => this.handleDefaultAccountAccess(configuredServiceUrl)));95} else {96const defaultExtensionGalleryManifest = await super.getExtensionGalleryManifest();97this.update(defaultExtensionGalleryManifest);98}99100this._register(this.configurationService.onDidChangeConfiguration(e => {101if (!e.affectsConfiguration(ExtensionGalleryServiceUrlConfigKey)) {102return;103}104this.requestRestart();105}));106}107108private async handleDefaultAccountAccess(configuredServiceUrl: string): Promise<void> {109const account = await this.defaultAccountService.getDefaultAccount();110111if (!account) {112this.logService.debug('[Marketplace] Enterprise marketplace configured but user not signed in');113this.update(null, ExtensionGalleryManifestStatus.RequiresSignIn);114} else if (!this.checkAccess(account)) {115this.logService.debug('[Marketplace] User signed in but lacks access to enterprise marketplace');116this.update(null, ExtensionGalleryManifestStatus.AccessDenied);117} else if (this.currentStatus !== ExtensionGalleryManifestStatus.Available) {118try {119const manifest = await this.getExtensionGalleryManifestFromServiceUrl(configuredServiceUrl);120this.update(manifest);121} catch (error) {122this.logService.error('[Marketplace] Error retrieving enterprise gallery manifest', error);123this.update(null, ExtensionGalleryManifestStatus.AccessDenied);124}125}126}127128private update(manifest: IExtensionGalleryManifest | null, status?: ExtensionGalleryManifestStatus): void {129if (this.extensionGalleryManifest !== manifest) {130this.extensionGalleryManifest = manifest;131this._onDidChangeExtensionGalleryManifest.fire(manifest);132}133this.updateStatus(status ?? (this.extensionGalleryManifest ? ExtensionGalleryManifestStatus.Available : ExtensionGalleryManifestStatus.Unavailable));134}135136private updateStatus(status: ExtensionGalleryManifestStatus): void {137if (this.currentStatus !== status) {138this.currentStatus = status;139this._onDidChangeExtensionGalleryManifestStatus.fire(status);140}141}142143private checkAccess(account: IDefaultAccount): boolean {144this.logService.debug('[Marketplace] Checking Account SKU access for configured gallery', account.access_type_sku);145if (account.access_type_sku && this.productService.extensionsGallery?.accessSKUs?.includes(account.access_type_sku)) {146this.logService.debug('[Marketplace] Account has access to configured gallery');147return true;148}149this.logService.debug('[Marketplace] Checking enterprise account access for configured gallery', account.enterprise);150return account.enterprise;151}152153private async requestRestart(): Promise<void> {154const confirmation = await this.dialogService.confirm({155message: localize('extensionGalleryManifestService.accountChange', "{0} is now configured to a different Marketplace. Please restart to apply the changes.", this.productService.nameLong),156primaryButton: localize({ key: 'restart', comment: ['&& denotes a mnemonic'] }, "&&Restart")157});158if (confirmation.confirmed) {159return this.hostService.restart();160}161}162163private async getExtensionGalleryManifestFromServiceUrl(url: string): Promise<IExtensionGalleryManifest> {164const commonHeaders = await this.commonHeadersPromise;165const headers = {166...commonHeaders,167'Content-Type': 'application/json',168'Accept-Encoding': 'gzip',169};170171try {172const context = await this.requestService.request({173type: 'GET',174url,175headers,176}, CancellationToken.None);177178const extensionGalleryManifest = await asJson<IExtensionGalleryManifest>(context);179180if (!extensionGalleryManifest) {181throw new Error('Unable to retrieve extension gallery manifest.');182}183184return extensionGalleryManifest;185} catch (error) {186this.logService.error('[Marketplace] Error retrieving extension gallery manifest', error);187throw error;188}189}190}191192registerSingleton(IExtensionGalleryManifestService, WorkbenchExtensionGalleryManifestService, InstantiationType.Eager);193194195