From e7a8d891e5db6ce4f7a5f30d582881c9b329d072 Mon Sep 17 00:00:00 2001 From: Akash Satheesan Date: Fri, 22 Jan 2021 22:35:04 +0530 Subject: [PATCH] Allow use of multiple extension galleries --- .../common/extensionGalleryService.ts | 69 +++++++++++-------- .../platform/product/common/productService.ts | 2 +- lib/vscode/src/vs/server/ipc.d.ts | 2 +- lib/vscode/src/vs/server/node/marketplace.ts | 2 +- 4 files changed, 45 insertions(+), 30 deletions(-) diff --git a/lib/vscode/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/lib/vscode/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index b9e1f47e86cc..c3eec0ddc6a2 100644 --- a/lib/vscode/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/lib/vscode/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -343,7 +343,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { declare readonly _serviceBrand: undefined; - private extensionsGalleryUrl: string | undefined; + private extensionsGalleryUrl: string[] | undefined; private extensionsControlUrl: string | undefined; private readonly commonHeadersPromise: Promise<{ [key: string]: string; }>; @@ -363,8 +363,13 @@ export class ExtensionGalleryService implements IExtensionGalleryService { this.commonHeadersPromise = resolveMarketplaceHeaders(productService.version, this.environmentService, this.fileService, storageService); } - private api(path = ''): string { - return `${this.extensionsGalleryUrl}${path}`; + private api(path = ''): string[] { + if (!!this.extensionsGalleryUrl) { + return this.extensionsGalleryUrl?.map( + (galleryUrl) => `${galleryUrl}${path}` + ); + } + return []; } isEnabled(): boolean { @@ -543,43 +548,53 @@ export class ExtensionGalleryService implements IExtensionGalleryService { 'Content-Length': String(data.length) }; - const context = await this.requestService.request({ - type: 'POST', - url: this.api('/extensionquery'), - data, - headers - }, token); + const galleryExtensions: IRawGalleryExtension[] = []; + const seenExtensions: Set = new Set(); - if (context.res.statusCode && context.res.statusCode >= 400 && context.res.statusCode < 500) { - return { galleryExtensions: [], total: 0 }; - } + for (const url of this.api('/extensionquery')) { + const context = await this.requestService.request({ + type: 'POST', + url: url, + data, + headers + }, token); - const result = await asJson(context); - if (result) { - const r = result.results[0]; - const galleryExtensions = r.extensions; - const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0]; - const total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0; + if (context.res.statusCode && context.res.statusCode >= 400 && context.res.statusCode < 500) { + return { galleryExtensions: [], total: 0 }; + } - return { galleryExtensions, total }; + const result = await asJson(context); + if (result) { + const r = result.results[0]; + for (const extension of r.extensions) { + if (!seenExtensions.has(extension.extensionId)) { + galleryExtensions.push(extension); + seenExtensions.add(extension.extensionId); + } + } + } } - return { galleryExtensions: [], total: 0 }; + return { galleryExtensions, total: galleryExtensions.length }; } async reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise { + // TODO: investigate further - currently we just send stats everywhere + // this is only used in one place - uninstall tracking - but if (!this.isEnabled()) { return undefined; } const commonHeaders = await this.commonHeadersPromise; const headers = { ...commonHeaders, Accept: '*/*;api-version=4.0-preview.1' }; - try { - await this.requestService.request({ - type: 'POST', - url: this.api(`/publishers/${publisher}/extensions/${name}/${version}/stats?statType=${type}`), - headers - }, CancellationToken.None); - } catch (error) { /* Ignore */ } + for (const url of this.api(`/publishers/${publisher}/extensions/${name}/${version}/stats?statType=${type}`)) { + try { + await this.requestService.request({ + type: 'POST', + url: url, + headers + }, CancellationToken.None); + } catch (error) { /* Ignore */ } + } } async download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise { diff --git a/lib/vscode/src/vs/platform/product/common/productService.ts b/lib/vscode/src/vs/platform/product/common/productService.ts index b13572327a6e..3e96900a28c0 100644 --- a/lib/vscode/src/vs/platform/product/common/productService.ts +++ b/lib/vscode/src/vs/platform/product/common/productService.ts @@ -69,7 +69,7 @@ export interface IProductConfiguration { readonly experimentsUrl?: string; readonly extensionsGallery?: { - readonly serviceUrl: string; + readonly serviceUrl: string[]; readonly itemUrl: string; readonly controlUrl: string; readonly recommendationsUrl: string; diff --git a/lib/vscode/src/vs/server/ipc.d.ts b/lib/vscode/src/vs/server/ipc.d.ts index bc605f038811..99ee4017e2ec 100644 --- a/lib/vscode/src/vs/server/ipc.d.ts +++ b/lib/vscode/src/vs/server/ipc.d.ts @@ -125,7 +125,7 @@ export interface WorkbenchOptions { readonly productConfiguration: { codeServerVersion?: string; readonly extensionsGallery?: { - readonly serviceUrl: string; + readonly serviceUrl: string[]; readonly itemUrl: string; readonly controlUrl: string; readonly recommendationsUrl: string; diff --git a/lib/vscode/src/vs/server/node/marketplace.ts b/lib/vscode/src/vs/server/node/marketplace.ts index 8956fc40d484..df172e00a5ad 100644 --- a/lib/vscode/src/vs/server/node/marketplace.ts +++ b/lib/vscode/src/vs/server/node/marketplace.ts @@ -160,7 +160,7 @@ const extractTar = async (tarPath: string, targetPath: string, options: IExtract */ export const enableCustomMarketplace = (): void => { (product).extensionsGallery = { // Use `any` to override readonly. - serviceUrl: process.env.SERVICE_URL || 'https://extensions.coder.com/api', + serviceUrl: process.env.SERVICE_URL ? [process.env.SERVICE_URL] : ['https://open-vsx.org/vscode/gallery', 'https://extensions.coder.com/api'], itemUrl: process.env.ITEM_URL || '', controlUrl: '', recommendationsUrl: '',