Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/extensionManagement/common/extensionGalleryService.ts
5272 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { distinct } from '../../../base/common/arrays.js';
7
import { CancellationToken } from '../../../base/common/cancellation.js';
8
import * as semver from '../../../base/common/semver/semver.js';
9
import { IStringDictionary } from '../../../base/common/collections.js';
10
import { CancellationError, getErrorMessage, isCancellationError } from '../../../base/common/errors.js';
11
import { IPager } from '../../../base/common/paging.js';
12
import { isWeb, platform } from '../../../base/common/platform.js';
13
import { arch } from '../../../base/common/process.js';
14
import { isBoolean, isNumber, isString } from '../../../base/common/types.js';
15
import { URI } from '../../../base/common/uri.js';
16
import { IHeaders, IRequestContext, IRequestOptions, isOfflineError } from '../../../base/parts/request/common/request.js';
17
import { IConfigurationService } from '../../configuration/common/configuration.js';
18
import { IEnvironmentService } from '../../environment/common/environment.js';
19
import { getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionInfo, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortOrder, StatisticType, toTargetPlatform, WEB_EXTENSION_TAG, IExtensionQueryOptions, IDeprecationInfo, ISearchPrefferedResults, ExtensionGalleryError, ExtensionGalleryErrorCode, IProductVersion, IAllowedExtensionsService, EXTENSION_IDENTIFIER_REGEX, SortBy, FilterType, MaliciousExtensionInfo, ExtensionRequestsTimeoutConfigKey } from './extensionManagement.js';
20
import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from './extensionManagementUtil.js';
21
import { IExtensionManifest, TargetPlatform } from '../../extensions/common/extensions.js';
22
import { areApiProposalsCompatible, isEngineValid } from '../../extensions/common/extensionValidator.js';
23
import { IFileService } from '../../files/common/files.js';
24
import { ILogService } from '../../log/common/log.js';
25
import { IProductService } from '../../product/common/productService.js';
26
import { asJson, asTextOrError, IRequestService, isClientError, isServerError, isSuccess } from '../../request/common/request.js';
27
import { resolveMarketplaceHeaders } from '../../externalServices/common/marketplace.js';
28
import { IStorageService } from '../../storage/common/storage.js';
29
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
30
import { StopWatch } from '../../../base/common/stopwatch.js';
31
import { format2 } from '../../../base/common/strings.js';
32
import { ExtensionGalleryResourceType, Flag, getExtensionGalleryManifestResourceUri, IExtensionGalleryManifest, IExtensionGalleryManifestService, ExtensionGalleryManifestStatus } from './extensionGalleryManifest.js';
33
import { TelemetryTrustedValue } from '../../telemetry/common/telemetryUtils.js';
34
35
const CURRENT_TARGET_PLATFORM = isWeb ? TargetPlatform.WEB : getTargetPlatform(platform, arch);
36
const SEARCH_ACTIVITY_HEADER_NAME = 'X-Market-Search-Activity-Id';
37
const ACTIVITY_HEADER_NAME = 'Activityid';
38
const SERVER_HEADER_NAME = 'Server';
39
const END_END_ID_HEADER_NAME = 'X-Vss-E2eid';
40
41
interface IRawGalleryExtensionFile {
42
readonly assetType: string;
43
readonly source: string;
44
}
45
46
interface IRawGalleryExtensionProperty {
47
readonly key: string;
48
readonly value: string;
49
}
50
51
export interface IRawGalleryExtensionVersion {
52
readonly version: string;
53
readonly lastUpdated: string;
54
readonly assetUri: string;
55
readonly fallbackAssetUri: string;
56
readonly files: IRawGalleryExtensionFile[];
57
properties?: IRawGalleryExtensionProperty[];
58
readonly targetPlatform?: string;
59
}
60
61
interface IRawGalleryExtensionStatistics {
62
readonly statisticName: string;
63
readonly value: number;
64
}
65
66
interface IRawGalleryExtensionPublisher {
67
readonly displayName: string;
68
readonly publisherId: string;
69
readonly publisherName: string;
70
readonly domain?: string | null;
71
readonly isDomainVerified?: boolean;
72
readonly linkType?: string;
73
}
74
75
interface IRawGalleryExtension {
76
readonly extensionId: string;
77
readonly extensionName: string;
78
readonly displayName: string;
79
readonly shortDescription?: string;
80
readonly publisher: IRawGalleryExtensionPublisher;
81
readonly versions: IRawGalleryExtensionVersion[];
82
readonly statistics: IRawGalleryExtensionStatistics[];
83
readonly tags: string[] | undefined;
84
readonly releaseDate: string;
85
readonly publishedDate: string;
86
readonly lastUpdated: string;
87
readonly categories: string[] | undefined;
88
readonly flags: string;
89
readonly linkType?: string;
90
readonly ratingLinkType?: string;
91
}
92
93
interface IRawGalleryExtensionsResult {
94
readonly galleryExtensions: IRawGalleryExtension[];
95
readonly total: number;
96
readonly context?: IStringDictionary<string>;
97
}
98
99
interface IRawGalleryQueryResult {
100
readonly results: {
101
readonly extensions: IRawGalleryExtension[];
102
readonly resultMetadata: {
103
readonly metadataType: string;
104
readonly metadataItems: {
105
readonly name: string;
106
readonly count: number;
107
}[];
108
}[];
109
}[];
110
}
111
112
const AssetType = {
113
Icon: 'Microsoft.VisualStudio.Services.Icons.Default',
114
Details: 'Microsoft.VisualStudio.Services.Content.Details',
115
Changelog: 'Microsoft.VisualStudio.Services.Content.Changelog',
116
Manifest: 'Microsoft.VisualStudio.Code.Manifest',
117
VSIX: 'Microsoft.VisualStudio.Services.VSIXPackage',
118
License: 'Microsoft.VisualStudio.Services.Content.License',
119
Repository: 'Microsoft.VisualStudio.Services.Links.Source',
120
Signature: 'Microsoft.VisualStudio.Services.VsixSignature'
121
};
122
123
const PropertyType = {
124
Dependency: 'Microsoft.VisualStudio.Code.ExtensionDependencies',
125
ExtensionPack: 'Microsoft.VisualStudio.Code.ExtensionPack',
126
Engine: 'Microsoft.VisualStudio.Code.Engine',
127
PreRelease: 'Microsoft.VisualStudio.Code.PreRelease',
128
EnabledApiProposals: 'Microsoft.VisualStudio.Code.EnabledApiProposals',
129
LocalizedLanguages: 'Microsoft.VisualStudio.Code.LocalizedLanguages',
130
WebExtension: 'Microsoft.VisualStudio.Code.WebExtension',
131
SponsorLink: 'Microsoft.VisualStudio.Code.SponsorLink',
132
SupportLink: 'Microsoft.VisualStudio.Services.Links.Support',
133
ExecutesCode: 'Microsoft.VisualStudio.Code.ExecutesCode',
134
Private: 'PrivateMarketplace',
135
};
136
137
interface ICriterium {
138
readonly filterType: FilterType;
139
readonly value?: string;
140
}
141
142
const DefaultPageSize = 10;
143
144
interface IQueryState {
145
readonly pageNumber: number;
146
readonly pageSize: number;
147
readonly sortBy: SortBy;
148
readonly sortOrder: SortOrder;
149
readonly flags: Flag[];
150
readonly criteria: ICriterium[];
151
readonly assetTypes: string[];
152
readonly source?: string;
153
}
154
155
const DefaultQueryState: IQueryState = {
156
pageNumber: 1,
157
pageSize: DefaultPageSize,
158
sortBy: SortBy.NoneOrRelevance,
159
sortOrder: SortOrder.Default,
160
flags: [],
161
criteria: [],
162
assetTypes: []
163
};
164
165
type GalleryServiceQueryClassification = {
166
owner: 'sandy081';
167
comment: 'Information about Marketplace query and its response';
168
readonly filterTypes: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Filter types used in the query.' };
169
readonly flags: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Flags passed in the query.' };
170
readonly sortBy: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'sorted by option passed in the query' };
171
readonly sortOrder: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'sort order option passed in the query' };
172
readonly pageNumber: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'requested page number in the query' };
173
readonly duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; 'isMeasurement': true; comment: 'amount of time taken by the query request' };
174
readonly success: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'whether the query request is success or not' };
175
readonly requestBodySize: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'size of the request body' };
176
readonly responseBodySize?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'size of the response body' };
177
readonly statusCode?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'status code of the response' };
178
readonly errorCode?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'error code of the response' };
179
readonly count?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'total number of extensions matching the query' };
180
readonly source?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'source that requested this query, eg., recommendations, viewlet' };
181
readonly searchTextLength?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'length of the search text in the query' };
182
readonly server?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'server that handled the query' };
183
readonly endToEndId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'end to end operation id' };
184
readonly activityId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'activity id' };
185
};
186
187
type QueryTelemetryData = {
188
readonly filterTypes: string[];
189
readonly flags: string[];
190
readonly sortBy: string;
191
readonly sortOrder: string;
192
readonly pageNumber: string;
193
readonly source?: string;
194
readonly searchTextLength?: number;
195
};
196
197
type GalleryServiceQueryEvent = QueryTelemetryData & {
198
readonly duration: number;
199
readonly success: boolean;
200
readonly requestBodySize: string;
201
readonly responseBodySize?: string;
202
readonly statusCode?: string;
203
readonly errorCode?: string;
204
readonly count?: string;
205
readonly server?: TelemetryTrustedValue<string>;
206
readonly endToEndId?: TelemetryTrustedValue<string>;
207
readonly activityId?: TelemetryTrustedValue<string>;
208
};
209
210
type GalleryServiceAdditionalQueryClassification = {
211
owner: 'sandy081';
212
comment: 'Response information about the additional query to the Marketplace for fetching all versions to get release version';
213
readonly duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; 'isMeasurement': true; comment: 'Amount of time taken by the additional query' };
214
readonly count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Total number of extensions returned by this additional query' };
215
};
216
217
type GalleryServiceAdditionalQueryEvent = {
218
readonly duration: number;
219
readonly count: number;
220
};
221
222
type ExtensionsCriteria = {
223
readonly productVersion: IProductVersion;
224
readonly targetPlatform: TargetPlatform;
225
readonly compatible: boolean;
226
readonly includePreRelease: boolean | (IExtensionIdentifier & { includePreRelease: boolean })[];
227
readonly versions?: (IExtensionIdentifier & { version: string })[];
228
readonly isQueryForReleaseVersionFromPreReleaseVersion?: boolean;
229
};
230
231
const enum VersionKind {
232
Release,
233
Prerelease,
234
Latest
235
}
236
237
type ExtensionVersionCriteria = {
238
readonly productVersion: IProductVersion;
239
readonly targetPlatform: TargetPlatform;
240
readonly compatible: boolean;
241
readonly version: VersionKind | string;
242
};
243
244
class Query {
245
246
constructor(private state = DefaultQueryState) { }
247
248
get pageNumber(): number { return this.state.pageNumber; }
249
get pageSize(): number { return this.state.pageSize; }
250
get sortBy(): SortBy { return this.state.sortBy; }
251
get sortOrder(): number { return this.state.sortOrder; }
252
get flags(): Flag[] { return this.state.flags; }
253
get criteria(): ICriterium[] { return this.state.criteria; }
254
get assetTypes(): string[] { return this.state.assetTypes; }
255
get source(): string | undefined { return this.state.source; }
256
get searchText(): string {
257
const criterium = this.state.criteria.filter(criterium => criterium.filterType === FilterType.SearchText)[0];
258
return criterium && criterium.value ? criterium.value : '';
259
}
260
261
262
withPage(pageNumber: number, pageSize: number = this.state.pageSize): Query {
263
return new Query({ ...this.state, pageNumber, pageSize });
264
}
265
266
withFilter(filterType: FilterType, ...values: string[]): Query {
267
const criteria = [
268
...this.state.criteria,
269
...values.length ? values.map(value => ({ filterType, value })) : [{ filterType }]
270
];
271
272
return new Query({ ...this.state, criteria });
273
}
274
275
withSortBy(sortBy: SortBy): Query {
276
return new Query({ ...this.state, sortBy });
277
}
278
279
withSortOrder(sortOrder: SortOrder): Query {
280
return new Query({ ...this.state, sortOrder });
281
}
282
283
withFlags(...flags: Flag[]): Query {
284
return new Query({ ...this.state, flags: distinct(flags) });
285
}
286
287
withAssetTypes(...assetTypes: string[]): Query {
288
return new Query({ ...this.state, assetTypes });
289
}
290
291
withSource(source: string): Query {
292
return new Query({ ...this.state, source });
293
}
294
}
295
296
function getStatistic(statistics: IRawGalleryExtensionStatistics[], name: string): number {
297
const result = (statistics || []).filter(s => s.statisticName === name)[0];
298
return result ? result.value : 0;
299
}
300
301
function getCoreTranslationAssets(version: IRawGalleryExtensionVersion): [string, IGalleryExtensionAsset][] {
302
const coreTranslationAssetPrefix = 'Microsoft.VisualStudio.Code.Translation.';
303
const result = version.files.filter(f => f.assetType.indexOf(coreTranslationAssetPrefix) === 0);
304
return result.reduce<[string, IGalleryExtensionAsset][]>((result, file) => {
305
const asset = getVersionAsset(version, file.assetType);
306
if (asset) {
307
result.push([file.assetType.substring(coreTranslationAssetPrefix.length), asset]);
308
}
309
return result;
310
}, []);
311
}
312
313
function getRepositoryAsset(version: IRawGalleryExtensionVersion): IGalleryExtensionAsset | null {
314
if (version.properties) {
315
const results = version.properties.filter(p => p.key === AssetType.Repository);
316
const gitRegExp = new RegExp('((git|ssh|http(s)?)|(git@[\\w.]+))(:(//)?)([\\w.@:/\\-~]+)(.git)(/)?');
317
318
const uri = results.filter(r => gitRegExp.test(r.value))[0];
319
return uri ? { uri: uri.value, fallbackUri: uri.value } : null;
320
}
321
return getVersionAsset(version, AssetType.Repository);
322
}
323
324
function getDownloadAsset(version: IRawGalleryExtensionVersion): IGalleryExtensionAsset {
325
return {
326
// always use fallbackAssetUri for download asset to hit the Marketplace API so that downloads are counted
327
uri: `${version.fallbackAssetUri}/${AssetType.VSIX}?redirect=true${version.targetPlatform ? `&targetPlatform=${version.targetPlatform}` : ''}`,
328
fallbackUri: `${version.fallbackAssetUri}/${AssetType.VSIX}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ''}`
329
};
330
}
331
332
function getVersionAsset(version: IRawGalleryExtensionVersion, type: string): IGalleryExtensionAsset | null {
333
const result = version.files.filter(f => f.assetType === type)[0];
334
return result ? {
335
uri: `${version.assetUri}/${type}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ''}`,
336
fallbackUri: `${version.fallbackAssetUri}/${type}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ''}`
337
} : null;
338
}
339
340
function getExtensions(version: IRawGalleryExtensionVersion, property: string): string[] {
341
const values = version.properties ? version.properties.filter(p => p.key === property) : [];
342
const value = values.length > 0 && values[0].value;
343
return value ? value.split(',').map(v => adoptToGalleryExtensionId(v)) : [];
344
}
345
346
function getEngine(version: IRawGalleryExtensionVersion): string {
347
const values = version.properties ? version.properties.filter(p => p.key === PropertyType.Engine) : [];
348
return (values.length > 0 && values[0].value) || '';
349
}
350
351
function setEngine(version: IRawGalleryExtensionVersion, engine: string): void {
352
version.properties = version.properties ?? [];
353
version.properties.push({ key: PropertyType.Engine, value: engine });
354
}
355
356
function isPreReleaseVersion(version: IRawGalleryExtensionVersion): boolean {
357
const values = version.properties ? version.properties.filter(p => p.key === PropertyType.PreRelease) : [];
358
return values.length > 0 && values[0].value === 'true';
359
}
360
361
function hasPreReleaseForExtension(id: string, productService: IProductService): boolean | undefined {
362
return productService.extensionProperties?.[id.toLowerCase()]?.hasPrereleaseVersion;
363
}
364
365
function getExcludeVersionRangeForExtension(id: string, productService: IProductService): string | undefined {
366
return productService.extensionProperties?.[id.toLowerCase()]?.excludeVersionRange;
367
}
368
369
function isPrivateExtension(version: IRawGalleryExtensionVersion): boolean {
370
const values = version.properties ? version.properties.filter(p => p.key === PropertyType.Private) : [];
371
return values.length > 0 && values[0].value === 'true';
372
}
373
374
function executesCode(version: IRawGalleryExtensionVersion): boolean | undefined {
375
const values = version.properties ? version.properties.filter(p => p.key === PropertyType.ExecutesCode) : [];
376
return values.length > 0 ? values[0].value === 'true' : undefined;
377
}
378
379
function getEnabledApiProposals(version: IRawGalleryExtensionVersion): string[] {
380
const values = version.properties ? version.properties.filter(p => p.key === PropertyType.EnabledApiProposals) : [];
381
const value = (values.length > 0 && values[0].value) || '';
382
return value ? value.split(',') : [];
383
}
384
385
function getLocalizedLanguages(version: IRawGalleryExtensionVersion): string[] {
386
const values = version.properties ? version.properties.filter(p => p.key === PropertyType.LocalizedLanguages) : [];
387
const value = (values.length > 0 && values[0].value) || '';
388
return value ? value.split(',') : [];
389
}
390
391
function getSponsorLink(version: IRawGalleryExtensionVersion): string | undefined {
392
return version.properties?.find(p => p.key === PropertyType.SponsorLink)?.value;
393
}
394
395
function getSupportLink(version: IRawGalleryExtensionVersion): string | undefined {
396
return version.properties?.find(p => p.key === PropertyType.SupportLink)?.value;
397
}
398
399
function getIsPreview(flags: string): boolean {
400
return flags.indexOf('preview') !== -1;
401
}
402
403
function getTargetPlatformForExtensionVersion(version: IRawGalleryExtensionVersion): TargetPlatform {
404
return version.targetPlatform ? toTargetPlatform(version.targetPlatform) : TargetPlatform.UNDEFINED;
405
}
406
407
function getAllTargetPlatforms(rawGalleryExtension: IRawGalleryExtension): TargetPlatform[] {
408
const allTargetPlatforms = distinct(rawGalleryExtension.versions.map(getTargetPlatformForExtensionVersion));
409
410
// Is a web extension only if it has WEB_EXTENSION_TAG
411
const isWebExtension = !!rawGalleryExtension.tags?.includes(WEB_EXTENSION_TAG);
412
413
// Include Web Target Platform only if it is a web extension
414
const webTargetPlatformIndex = allTargetPlatforms.indexOf(TargetPlatform.WEB);
415
if (isWebExtension) {
416
if (webTargetPlatformIndex === -1) {
417
// Web extension but does not has web target platform -> add it
418
allTargetPlatforms.push(TargetPlatform.WEB);
419
}
420
} else {
421
if (webTargetPlatformIndex !== -1) {
422
// Not a web extension but has web target platform -> remove it
423
allTargetPlatforms.splice(webTargetPlatformIndex, 1);
424
}
425
}
426
427
return allTargetPlatforms;
428
}
429
430
export function sortExtensionVersions(versions: IRawGalleryExtensionVersion[], preferredTargetPlatform: TargetPlatform): IRawGalleryExtensionVersion[] {
431
/* It is expected that versions from Marketplace are sorted by version. So we are just sorting by preferred targetPlatform */
432
for (let index = 0; index < versions.length; index++) {
433
const version = versions[index];
434
if (version.version === versions[index - 1]?.version) {
435
let insertionIndex = index;
436
const versionTargetPlatform = getTargetPlatformForExtensionVersion(version);
437
/* put it at the beginning */
438
if (versionTargetPlatform === preferredTargetPlatform) {
439
while (insertionIndex > 0 && versions[insertionIndex - 1].version === version.version) { insertionIndex--; }
440
}
441
if (insertionIndex !== index) {
442
versions.splice(index, 1);
443
versions.splice(insertionIndex, 0, version);
444
}
445
}
446
}
447
return versions;
448
}
449
450
/**
451
* Filters extension versions to return only the relevant versions for a given target platform.
452
*
453
* This function processes a list of extension versions (expected to be sorted by version descending)
454
* and returns a filtered list containing:
455
* 1. All versions that are NOT compatible with the target platform (for other platforms)
456
* 2. At most one compatible release version (the first/latest one encountered)
457
* 3. At most one compatible pre-release version (the first/latest one encountered)
458
*
459
* When a platform-specific version (exactly matching targetPlatform) is encountered with the same
460
* version number as a previously stored universal/undefined version, it replaces that version.
461
* This ensures platform-specific builds are preferred over universal builds for the same version.
462
*
463
* @param versions - Array of extension versions, expected to be sorted by version number descending
464
* @param targetPlatform - The target platform to filter for (e.g., LINUX_X64, WIN32_X64)
465
* @param allTargetPlatforms - All target platforms the extension supports
466
* @returns Filtered array of versions relevant for the target platform
467
*/
468
export function filterLatestExtensionVersionsForTargetPlatform(versions: IRawGalleryExtensionVersion[], targetPlatform: TargetPlatform, allTargetPlatforms: TargetPlatform[]): IRawGalleryExtensionVersion[] {
469
const latestVersions: IRawGalleryExtensionVersion[] = [];
470
471
let preReleaseVersionIndex: number = -1;
472
let releaseVersionIndex: number = -1;
473
for (const version of versions) {
474
const versionTargetPlatform = getTargetPlatformForExtensionVersion(version);
475
const isCompatibleWithTargetPlatform = isTargetPlatformCompatible(versionTargetPlatform, allTargetPlatforms, targetPlatform);
476
477
// Always include versions that are NOT compatible with the target platform
478
if (!isCompatibleWithTargetPlatform) {
479
latestVersions.push(version);
480
continue;
481
}
482
483
// For compatible versions, only include the first (latest) of each type
484
// Prefer specific target platform matches over undefined/universal platforms only when version numbers are the same
485
if (isPreReleaseVersion(version)) {
486
if (preReleaseVersionIndex === -1) {
487
preReleaseVersionIndex = latestVersions.length;
488
latestVersions.push(version);
489
} else if (versionTargetPlatform === targetPlatform && latestVersions[preReleaseVersionIndex].version === version.version) {
490
latestVersions[preReleaseVersionIndex] = version;
491
}
492
} else {
493
if (releaseVersionIndex === -1) {
494
releaseVersionIndex = latestVersions.length;
495
latestVersions.push(version);
496
} else if (versionTargetPlatform === targetPlatform && latestVersions[releaseVersionIndex].version === version.version) {
497
latestVersions[releaseVersionIndex] = version;
498
}
499
}
500
}
501
502
return latestVersions;
503
}
504
505
function setTelemetry(extension: IGalleryExtension, index: number, querySource?: string): void {
506
/* __GDPR__FRAGMENT__
507
"GalleryExtensionTelemetryData2" : {
508
"index" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
509
"querySource": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
510
"queryActivityId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
511
}
512
*/
513
extension.telemetryData = { index, querySource, queryActivityId: extension.queryContext?.[SEARCH_ACTIVITY_HEADER_NAME] };
514
}
515
516
function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, allTargetPlatforms: TargetPlatform[], extensionGalleryManifest: IExtensionGalleryManifest, productService: IProductService, queryContext?: IStringDictionary<unknown>): IGalleryExtension {
517
const latestVersion = galleryExtension.versions[0];
518
const assets: IGalleryExtensionAssets = {
519
manifest: getVersionAsset(version, AssetType.Manifest),
520
readme: getVersionAsset(version, AssetType.Details),
521
changelog: getVersionAsset(version, AssetType.Changelog),
522
license: getVersionAsset(version, AssetType.License),
523
repository: getRepositoryAsset(version),
524
download: getDownloadAsset(version),
525
icon: getVersionAsset(version, AssetType.Icon),
526
signature: getVersionAsset(version, AssetType.Signature),
527
coreTranslations: getCoreTranslationAssets(version)
528
};
529
530
const detailsViewUri = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, galleryExtension.linkType ?? ExtensionGalleryResourceType.ExtensionDetailsViewUri);
531
const publisherViewUri = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, galleryExtension.publisher.linkType ?? ExtensionGalleryResourceType.PublisherViewUri);
532
const ratingViewUri = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, galleryExtension.ratingLinkType ?? ExtensionGalleryResourceType.ExtensionRatingViewUri);
533
const id = getGalleryExtensionId(galleryExtension.publisher.publisherName, galleryExtension.extensionName);
534
535
return {
536
type: 'gallery',
537
identifier: {
538
id,
539
uuid: galleryExtension.extensionId
540
},
541
name: galleryExtension.extensionName,
542
version: version.version,
543
displayName: galleryExtension.displayName,
544
publisherId: galleryExtension.publisher.publisherId,
545
publisher: galleryExtension.publisher.publisherName,
546
publisherDisplayName: galleryExtension.publisher.displayName,
547
publisherDomain: galleryExtension.publisher.domain ? { link: galleryExtension.publisher.domain, verified: !!galleryExtension.publisher.isDomainVerified } : undefined,
548
publisherSponsorLink: getSponsorLink(latestVersion),
549
description: galleryExtension.shortDescription ?? '',
550
installCount: getStatistic(galleryExtension.statistics, 'install'),
551
rating: getStatistic(galleryExtension.statistics, 'averagerating'),
552
ratingCount: getStatistic(galleryExtension.statistics, 'ratingcount'),
553
categories: galleryExtension.categories || [],
554
tags: galleryExtension.tags || [],
555
releaseDate: Date.parse(galleryExtension.releaseDate),
556
lastUpdated: Date.parse(galleryExtension.lastUpdated),
557
allTargetPlatforms,
558
assets,
559
properties: {
560
dependencies: getExtensions(version, PropertyType.Dependency),
561
extensionPack: getExtensions(version, PropertyType.ExtensionPack),
562
engine: getEngine(version),
563
enabledApiProposals: getEnabledApiProposals(version),
564
localizedLanguages: getLocalizedLanguages(version),
565
targetPlatform: getTargetPlatformForExtensionVersion(version),
566
isPreReleaseVersion: isPreReleaseVersion(version),
567
executesCode: executesCode(version)
568
},
569
hasPreReleaseVersion: hasPreReleaseForExtension(id, productService) ?? isPreReleaseVersion(latestVersion),
570
hasReleaseVersion: true,
571
private: isPrivateExtension(latestVersion),
572
preview: getIsPreview(galleryExtension.flags),
573
isSigned: !!assets.signature,
574
queryContext,
575
supportLink: getSupportLink(latestVersion),
576
detailsLink: detailsViewUri ? format2(detailsViewUri, { publisher: galleryExtension.publisher.publisherName, name: galleryExtension.extensionName }) : undefined,
577
publisherLink: publisherViewUri ? format2(publisherViewUri, { publisher: galleryExtension.publisher.publisherName }) : undefined,
578
ratingLink: ratingViewUri ? format2(ratingViewUri, { publisher: galleryExtension.publisher.publisherName, name: galleryExtension.extensionName }) : undefined,
579
};
580
}
581
582
interface IRawExtensionsControlManifest {
583
malicious: string[];
584
learnMoreLinks?: IStringDictionary<string>;
585
migrateToPreRelease?: IStringDictionary<{
586
id: string;
587
displayName: string;
588
migrateStorage?: boolean;
589
engine?: string;
590
}>;
591
deprecated?: IStringDictionary<boolean | {
592
disallowInstall?: boolean;
593
extension?: {
594
id: string;
595
displayName: string;
596
};
597
settings?: string[];
598
additionalInfo?: string;
599
}>;
600
search?: ISearchPrefferedResults[];
601
autoUpdate?: IStringDictionary<string>;
602
}
603
604
export abstract class AbstractExtensionGalleryService implements IExtensionGalleryService {
605
606
declare readonly _serviceBrand: undefined;
607
608
private readonly extensionsControlUrl: string | undefined;
609
private readonly unpkgResourceApi: string | undefined;
610
611
private readonly commonHeadersPromise: Promise<IHeaders>;
612
private readonly extensionsEnabledWithApiProposalVersion: string[];
613
614
constructor(
615
storageService: IStorageService | undefined,
616
@IRequestService private readonly requestService: IRequestService,
617
@ILogService private readonly logService: ILogService,
618
@IEnvironmentService private readonly environmentService: IEnvironmentService,
619
@ITelemetryService private readonly telemetryService: ITelemetryService,
620
@IFileService private readonly fileService: IFileService,
621
@IProductService private readonly productService: IProductService,
622
@IConfigurationService private readonly configurationService: IConfigurationService,
623
@IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService,
624
@IExtensionGalleryManifestService private readonly extensionGalleryManifestService: IExtensionGalleryManifestService,
625
) {
626
this.extensionsControlUrl = productService.extensionsGallery?.controlUrl;
627
this.unpkgResourceApi = productService.extensionsGallery?.extensionUrlTemplate;
628
this.extensionsEnabledWithApiProposalVersion = productService.extensionsEnabledWithApiProposalVersion?.map(id => id.toLowerCase()) ?? [];
629
this.commonHeadersPromise = resolveMarketplaceHeaders(
630
productService.version,
631
productService,
632
this.environmentService,
633
this.configurationService,
634
this.fileService,
635
storageService,
636
this.telemetryService);
637
}
638
639
isEnabled(): boolean {
640
return this.extensionGalleryManifestService.extensionGalleryManifestStatus === ExtensionGalleryManifestStatus.Available;
641
}
642
643
getExtensions(extensionInfos: ReadonlyArray<IExtensionInfo>, token: CancellationToken): Promise<IGalleryExtension[]>;
644
getExtensions(extensionInfos: ReadonlyArray<IExtensionInfo>, options: IExtensionQueryOptions, token: CancellationToken): Promise<IGalleryExtension[]>;
645
async getExtensions(extensionInfos: ReadonlyArray<IExtensionInfo>, arg1: CancellationToken | IExtensionQueryOptions, arg2?: CancellationToken): Promise<IGalleryExtension[]> {
646
const extensionGalleryManifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();
647
if (!extensionGalleryManifest) {
648
throw new Error('No extension gallery service configured.');
649
}
650
651
const options = CancellationToken.isCancellationToken(arg1) ? {} : arg1 as IExtensionQueryOptions;
652
const token = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2 as CancellationToken;
653
654
const resourceApi = this.getResourceApi(extensionGalleryManifest);
655
const result = resourceApi
656
? await this.getExtensionsUsingResourceApi(extensionInfos, options, resourceApi, extensionGalleryManifest, token)
657
: await this.getExtensionsUsingQueryApi(extensionInfos, options, extensionGalleryManifest, token);
658
659
const uuids = result.map(r => r.identifier.uuid);
660
const extensionInfosByName: IExtensionInfo[] = [];
661
for (const e of extensionInfos) {
662
if (e.uuid && !uuids.includes(e.uuid)) {
663
extensionInfosByName.push({ ...e, uuid: undefined });
664
}
665
}
666
667
if (extensionInfosByName.length) {
668
// report telemetry data for additional query
669
this.telemetryService.publicLog2<
670
{ count: number },
671
{
672
owner: 'sandy081';
673
comment: 'Report the query to the Marketplace for fetching extensions by name';
674
readonly count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of extensions to fetch' };
675
}>('galleryService:additionalQueryByName', {
676
count: extensionInfosByName.length
677
});
678
679
const extensions = await this.getExtensionsUsingQueryApi(extensionInfosByName, options, extensionGalleryManifest, token);
680
result.push(...extensions);
681
}
682
683
return result;
684
}
685
686
private getResourceApi(extensionGalleryManifest: IExtensionGalleryManifest): { uri: string; fallback?: string } | undefined {
687
const latestVersionResource = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, ExtensionGalleryResourceType.ExtensionLatestVersionUri);
688
if (latestVersionResource) {
689
return {
690
uri: latestVersionResource,
691
fallback: this.unpkgResourceApi
692
};
693
}
694
return undefined;
695
}
696
697
private async getExtensionsUsingQueryApi(extensionInfos: ReadonlyArray<IExtensionInfo>, options: IExtensionQueryOptions, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IGalleryExtension[]> {
698
const names: string[] = [],
699
ids: string[] = [],
700
includePreRelease: (IExtensionIdentifier & { includePreRelease: boolean })[] = [],
701
versions: (IExtensionIdentifier & { version: string })[] = [];
702
let isQueryForReleaseVersionFromPreReleaseVersion = true;
703
704
for (const extensionInfo of extensionInfos) {
705
if (extensionInfo.uuid) {
706
ids.push(extensionInfo.uuid);
707
} else {
708
names.push(extensionInfo.id);
709
}
710
if (extensionInfo.version) {
711
versions.push({ id: extensionInfo.id, uuid: extensionInfo.uuid, version: extensionInfo.version });
712
} else {
713
includePreRelease.push({ id: extensionInfo.id, uuid: extensionInfo.uuid, includePreRelease: !!extensionInfo.preRelease });
714
}
715
isQueryForReleaseVersionFromPreReleaseVersion = isQueryForReleaseVersionFromPreReleaseVersion && (!!extensionInfo.hasPreRelease && !extensionInfo.preRelease);
716
}
717
718
if (!ids.length && !names.length) {
719
return [];
720
}
721
722
let query = new Query().withPage(1, extensionInfos.length);
723
if (ids.length) {
724
query = query.withFilter(FilterType.ExtensionId, ...ids);
725
}
726
if (names.length) {
727
query = query.withFilter(FilterType.ExtensionName, ...names);
728
}
729
if (options.queryAllVersions) {
730
query = query.withFlags(...query.flags, Flag.IncludeVersions);
731
}
732
if (options.source) {
733
query = query.withSource(options.source);
734
}
735
736
const { extensions } = await this.queryGalleryExtensions(
737
query,
738
{
739
targetPlatform: options.targetPlatform ?? CURRENT_TARGET_PLATFORM,
740
includePreRelease,
741
versions,
742
compatible: !!options.compatible,
743
productVersion: options.productVersion ?? { version: this.productService.version, date: this.productService.date },
744
isQueryForReleaseVersionFromPreReleaseVersion
745
},
746
extensionGalleryManifest,
747
token);
748
749
if (options.source) {
750
extensions.forEach((e, index) => setTelemetry(e, index, options.source));
751
}
752
753
return extensions;
754
}
755
756
private async getExtensionsUsingResourceApi(extensionInfos: ReadonlyArray<IExtensionInfo>, options: IExtensionQueryOptions, resourceApi: { uri: string; fallback?: string }, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IGalleryExtension[]> {
757
758
const result: IGalleryExtension[] = [];
759
const toQuery: IExtensionInfo[] = [];
760
const toFetchLatest: IExtensionInfo[] = [];
761
762
for (const extensionInfo of extensionInfos) {
763
if (!EXTENSION_IDENTIFIER_REGEX.test(extensionInfo.id)) {
764
continue;
765
}
766
if (extensionInfo.version) {
767
toQuery.push(extensionInfo);
768
} else {
769
toFetchLatest.push(extensionInfo);
770
}
771
}
772
773
await Promise.all(toFetchLatest.map(async extensionInfo => {
774
let galleryExtension: IGalleryExtension | string;
775
try {
776
galleryExtension = await this.getLatestGalleryExtension(extensionInfo, options, resourceApi, extensionGalleryManifest, token);
777
if (isString(galleryExtension)) {
778
// fallback to query
779
this.telemetryService.publicLog2<
780
{
781
extension: string;
782
preRelease: boolean;
783
compatible: boolean;
784
errorCode: string;
785
},
786
{
787
owner: 'sandy081';
788
comment: 'Report the fallback to the Marketplace query for fetching extensions';
789
extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension id' };
790
preRelease: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get pre-release version' };
791
compatible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get compatible version' };
792
errorCode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Error code or reason' };
793
}>('galleryService:fallbacktoquery', {
794
extension: extensionInfo.id,
795
preRelease: !!extensionInfo.preRelease,
796
compatible: !!options.compatible,
797
errorCode: galleryExtension
798
});
799
toQuery.push(extensionInfo);
800
} else {
801
result.push(galleryExtension);
802
}
803
} catch (error) {
804
if (error instanceof ExtensionGalleryError) {
805
switch (error.code) {
806
case ExtensionGalleryErrorCode.Offline:
807
case ExtensionGalleryErrorCode.Cancelled:
808
case ExtensionGalleryErrorCode.Timeout:
809
throw error;
810
}
811
}
812
813
// fallback to query
814
this.logService.error(`Error while getting the latest version for the extension ${extensionInfo.id}.`, getErrorMessage(error));
815
this.telemetryService.publicLog2<
816
{
817
extension: string;
818
preRelease: boolean;
819
compatible: boolean;
820
errorCode: string;
821
},
822
{
823
owner: 'sandy081';
824
comment: 'Report the fallback to the Marketplace query for fetching extensions';
825
extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension id' };
826
preRelease: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get pre-release version' };
827
compatible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get compatible version' };
828
errorCode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Error code' };
829
}>('galleryService:fallbacktoquery', {
830
extension: extensionInfo.id,
831
preRelease: !!extensionInfo.preRelease,
832
compatible: !!options.compatible,
833
errorCode: error instanceof ExtensionGalleryError ? error.code : 'Unknown'
834
});
835
toQuery.push(extensionInfo);
836
}
837
838
}));
839
840
if (toQuery.length) {
841
const extensions = await this.getExtensionsUsingQueryApi(toQuery, options, extensionGalleryManifest, token);
842
result.push(...extensions);
843
}
844
845
return result;
846
}
847
848
private async getLatestGalleryExtension(extensionInfo: IExtensionInfo, options: IExtensionQueryOptions, resourceApi: { uri: string; fallback?: string }, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IGalleryExtension | string> {
849
const rawGalleryExtension = await this.getLatestRawGalleryExtensionWithFallback(extensionInfo, resourceApi, token);
850
851
if (!rawGalleryExtension) {
852
return 'NOT_FOUND';
853
}
854
855
const allTargetPlatforms = getAllTargetPlatforms(rawGalleryExtension);
856
const rawGalleryExtensionVersion = await this.getValidRawGalleryExtensionVersionFromLatestVersions(rawGalleryExtension, rawGalleryExtension.versions, extensionInfo, options, allTargetPlatforms);
857
858
if (!rawGalleryExtensionVersion) {
859
return 'NOT_COMPATIBLE';
860
}
861
862
return toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, extensionGalleryManifest, this.productService);
863
}
864
865
private async getValidRawGalleryExtensionVersionFromLatestVersions(rawGalleryExtension: IRawGalleryExtension, latestVersions: IRawGalleryExtensionVersion[], extensionInfo: IExtensionInfo, options: IExtensionQueryOptions, allTargetPlatforms: TargetPlatform[]): Promise<IRawGalleryExtensionVersion | null> {
866
const targetPlatform = options.targetPlatform ?? CURRENT_TARGET_PLATFORM;
867
const latestExtensionVersionsForTargetPlatform = filterLatestExtensionVersionsForTargetPlatform(latestVersions, targetPlatform, allTargetPlatforms);
868
869
// First, find a valid version matching the requested type (pre-release or release)
870
const result = await this.getValidRawGalleryExtensionVersion(
871
rawGalleryExtension,
872
latestExtensionVersionsForTargetPlatform,
873
{
874
targetPlatform,
875
compatible: !!options.compatible,
876
productVersion: options.productVersion ?? {
877
version: this.productService.version,
878
date: this.productService.date
879
},
880
version: extensionInfo.preRelease ? VersionKind.Prerelease : VersionKind.Release
881
}, allTargetPlatforms);
882
883
// For release version requests, simply return the found release version
884
if (!extensionInfo.preRelease) {
885
return result;
886
}
887
888
// For pre-release version requests, we need to consider both pre-release and release versions
889
const prereleaseVersion = result;
890
const releaseVersion = await this.getValidRawGalleryExtensionVersion(
891
rawGalleryExtension,
892
latestExtensionVersionsForTargetPlatform,
893
{
894
targetPlatform,
895
compatible: !!options.compatible,
896
productVersion: options.productVersion ?? {
897
version: this.productService.version,
898
date: this.productService.date
899
},
900
version: VersionKind.Release
901
}, allTargetPlatforms);
902
903
// When both versions exist, return whichever has the higher version number
904
if (prereleaseVersion && releaseVersion) {
905
return semver.gt(releaseVersion.version, prereleaseVersion.version) ? releaseVersion : prereleaseVersion;
906
}
907
908
// Special handling for compatible version requests
909
if (options.compatible) {
910
// If we have a compatible release version, check if it's better than any pre-release
911
if (releaseVersion) {
912
// Check if there exists any pre-release version (ignoring compatibility)
913
const anyPrereleaseVersion = await this.getValidRawGalleryExtensionVersion(
914
rawGalleryExtension,
915
latestExtensionVersionsForTargetPlatform,
916
{
917
targetPlatform,
918
compatible: false,
919
productVersion: options.productVersion ?? {
920
version: this.productService.version,
921
date: this.productService.date
922
},
923
version: VersionKind.Prerelease
924
}, allTargetPlatforms);
925
926
// If no pre-release exists or the release version is greater, prefer the compatible release
927
// This ensures users get a stable compatible version when pre-releases aren't newer or compatible
928
if (!anyPrereleaseVersion || semver.gt(releaseVersion.version, anyPrereleaseVersion.version)) {
929
return releaseVersion;
930
}
931
}
932
return prereleaseVersion;
933
}
934
935
// Return pre-release if available, otherwise release, otherwise null
936
return prereleaseVersion ?? releaseVersion ?? null;
937
}
938
939
async getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise<IGalleryExtension | null> {
940
if (isNotWebExtensionInWebTargetPlatform(extension.allTargetPlatforms, targetPlatform)) {
941
return null;
942
}
943
if (await this.isExtensionCompatible(extension, includePreRelease, targetPlatform)) {
944
return extension;
945
}
946
if (this.allowedExtensionsService.isAllowed({ id: extension.identifier.id, publisherDisplayName: extension.publisherDisplayName }) !== true) {
947
return null;
948
}
949
const result = await this.getExtensions([{
950
...extension.identifier,
951
preRelease: includePreRelease,
952
hasPreRelease: extension.hasPreReleaseVersion,
953
}], {
954
compatible: true,
955
productVersion,
956
queryAllVersions: true,
957
targetPlatform,
958
}, CancellationToken.None);
959
960
return result[0] ?? null;
961
}
962
963
async isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise<boolean> {
964
return this.isValidVersion(
965
{
966
id: extension.identifier.id,
967
version: extension.version,
968
isPreReleaseVersion: extension.properties.isPreReleaseVersion,
969
targetPlatform: extension.properties.targetPlatform,
970
manifestAsset: extension.assets.manifest,
971
engine: extension.properties.engine,
972
enabledApiProposals: extension.properties.enabledApiProposals
973
},
974
{
975
targetPlatform,
976
compatible: true,
977
productVersion,
978
version: includePreRelease ? VersionKind.Latest : VersionKind.Release
979
},
980
extension.publisherDisplayName,
981
extension.allTargetPlatforms
982
);
983
}
984
985
private async isValidVersion(
986
extension: { id: string; version: string; isPreReleaseVersion: boolean; targetPlatform: TargetPlatform; manifestAsset: IGalleryExtensionAsset | null; engine: string | undefined; enabledApiProposals: string[] | undefined },
987
{ targetPlatform, compatible, productVersion, version }: Omit<ExtensionVersionCriteria, 'targetPlatform'> & { targetPlatform: TargetPlatform | undefined },
988
publisherDisplayName: string,
989
allTargetPlatforms: TargetPlatform[]
990
): Promise<boolean> {
991
992
const hasPreRelease = hasPreReleaseForExtension(extension.id, this.productService);
993
const excludeVersionRange = getExcludeVersionRangeForExtension(extension.id, this.productService);
994
995
if (extension.isPreReleaseVersion && hasPreRelease === false /* Skip if hasPreRelease is not defined for this extension */) {
996
return false;
997
}
998
999
if (excludeVersionRange && semver.satisfies(extension.version, excludeVersionRange)) {
1000
return false;
1001
}
1002
1003
// Specific version
1004
if (isString(version)) {
1005
if (extension.version !== version) {
1006
return false;
1007
}
1008
}
1009
1010
// Prerelease or release version kind
1011
else if (version === VersionKind.Release || version === VersionKind.Prerelease) {
1012
if (extension.isPreReleaseVersion !== (version === VersionKind.Prerelease)) {
1013
return false;
1014
}
1015
}
1016
1017
if (targetPlatform && !isTargetPlatformCompatible(extension.targetPlatform, allTargetPlatforms, targetPlatform)) {
1018
return false;
1019
}
1020
1021
if (compatible) {
1022
if (this.allowedExtensionsService.isAllowed({ id: extension.id, publisherDisplayName, version: extension.version, prerelease: extension.isPreReleaseVersion, targetPlatform: extension.targetPlatform }) !== true) {
1023
return false;
1024
}
1025
1026
if (!this.areApiProposalsCompatible(extension.id, extension.enabledApiProposals)) {
1027
return false;
1028
}
1029
1030
if (!(await this.isEngineValid(extension.id, extension.version, extension.engine, extension.manifestAsset, productVersion))) {
1031
return false;
1032
}
1033
}
1034
1035
return true;
1036
}
1037
1038
private areApiProposalsCompatible(extensionId: string, enabledApiProposals: string[] | undefined): boolean {
1039
if (!enabledApiProposals) {
1040
return true;
1041
}
1042
if (!this.extensionsEnabledWithApiProposalVersion.includes(extensionId.toLowerCase())) {
1043
return true;
1044
}
1045
return areApiProposalsCompatible(enabledApiProposals);
1046
}
1047
1048
private async isEngineValid(extensionId: string, version: string, engine: string | undefined, manifestAsset: IGalleryExtensionAsset | null, productVersion: IProductVersion): Promise<boolean> {
1049
if (!engine) {
1050
try {
1051
engine = await this.getEngine(extensionId, version, manifestAsset);
1052
} catch (error) {
1053
this.logService.error(`Error while getting the engine for the version ${version}.`, getErrorMessage(error));
1054
return false;
1055
}
1056
}
1057
1058
if (!engine) {
1059
this.logService.error(`Missing engine for the extension ${extensionId} with version ${version}`);
1060
return false;
1061
}
1062
1063
return isEngineValid(engine, productVersion.version, productVersion.date);
1064
}
1065
1066
private async getEngine(extensionId: string, version: string, manifestAsset: IGalleryExtensionAsset | null): Promise<string | undefined> {
1067
if (!manifestAsset) {
1068
this.logService.error(`Missing engine and manifest asset for the extension ${extensionId} with version ${version}`);
1069
return undefined;
1070
}
1071
try {
1072
type GalleryServiceEngineFallbackClassification = {
1073
owner: 'sandy081';
1074
comment: 'Fallback request when engine is not found in properties of an extension version';
1075
extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'extension name' };
1076
extensionVersion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'version' };
1077
};
1078
type GalleryServiceEngineFallbackEvent = {
1079
extension: string;
1080
extensionVersion: string;
1081
};
1082
this.telemetryService.publicLog2<GalleryServiceEngineFallbackEvent, GalleryServiceEngineFallbackClassification>('galleryService:engineFallback', { extension: extensionId, extensionVersion: version });
1083
1084
const headers = { 'Accept-Encoding': 'gzip' };
1085
const context = await this.getAsset(extensionId, manifestAsset, AssetType.Manifest, version, { headers });
1086
const manifest = await asJson<IExtensionManifest>(context);
1087
if (!manifest) {
1088
this.logService.error(`Manifest was not found for the extension ${extensionId} with version ${version}`);
1089
return undefined;
1090
}
1091
return manifest.engines.vscode;
1092
} catch (error) {
1093
this.logService.error(`Error while getting the engine for the version ${version}.`, getErrorMessage(error));
1094
return undefined;
1095
}
1096
}
1097
1098
async query(options: IQueryOptions, token: CancellationToken): Promise<IPager<IGalleryExtension>> {
1099
const extensionGalleryManifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();
1100
1101
if (!extensionGalleryManifest) {
1102
throw new Error('No extension gallery service configured.');
1103
}
1104
1105
let text = options.text || '';
1106
const pageSize = options.pageSize ?? 50;
1107
1108
let query = new Query()
1109
.withPage(1, pageSize);
1110
1111
if (text) {
1112
// Use category filter instead of "category:themes"
1113
text = text.replace(/\bcategory:("([^"]*)"|([^"]\S*))(\s+|\b|$)/g, (_, quotedCategory, category) => {
1114
query = query.withFilter(FilterType.Category, category || quotedCategory);
1115
return '';
1116
});
1117
1118
// Use tag filter instead of "tag:debuggers"
1119
text = text.replace(/\btag:("([^"]*)"|([^"]\S*))(\s+|\b|$)/g, (_, quotedTag, tag) => {
1120
query = query.withFilter(FilterType.Tag, tag || quotedTag);
1121
return '';
1122
});
1123
1124
// Use featured filter
1125
text = text.replace(/\bfeatured(\s+|\b|$)/g, () => {
1126
query = query.withFilter(FilterType.Featured);
1127
return '';
1128
});
1129
1130
text = text.trim();
1131
1132
if (text) {
1133
text = text.length < 200 ? text : text.substring(0, 200);
1134
query = query.withFilter(FilterType.SearchText, text);
1135
}
1136
1137
if (extensionGalleryManifest.capabilities.extensionQuery.sorting?.some(c => c.name === SortBy.NoneOrRelevance)) {
1138
query = query.withSortBy(SortBy.NoneOrRelevance);
1139
}
1140
} else {
1141
if (extensionGalleryManifest.capabilities.extensionQuery.sorting?.some(c => c.name === SortBy.InstallCount)) {
1142
query = query.withSortBy(SortBy.InstallCount);
1143
}
1144
}
1145
1146
if (options.sortBy && extensionGalleryManifest.capabilities.extensionQuery.sorting?.some(c => c.name === options.sortBy)) {
1147
query = query.withSortBy(options.sortBy);
1148
}
1149
1150
if (typeof options.sortOrder === 'number') {
1151
query = query.withSortOrder(options.sortOrder);
1152
}
1153
1154
if (options.source) {
1155
query = query.withSource(options.source);
1156
}
1157
1158
const runQuery = async (query: Query, token: CancellationToken) => {
1159
const { extensions, total } = await this.queryGalleryExtensions(query, { targetPlatform: CURRENT_TARGET_PLATFORM, compatible: false, includePreRelease: !!options.includePreRelease, productVersion: options.productVersion ?? { version: this.productService.version, date: this.productService.date } }, extensionGalleryManifest, token);
1160
1161
const result: IGalleryExtension[] = [];
1162
let defaultChatAgentExtension: IGalleryExtension | undefined;
1163
for (let index = 0; index < extensions.length; index++) {
1164
const extension = extensions[index];
1165
setTelemetry(extension, ((query.pageNumber - 1) * query.pageSize) + index, options.source);
1166
if (areSameExtensions(extension.identifier, { id: this.productService.defaultChatAgent.extensionId, })) {
1167
defaultChatAgentExtension = extension;
1168
} else {
1169
result.push(extension);
1170
}
1171
}
1172
if (defaultChatAgentExtension) {
1173
result.push(defaultChatAgentExtension);
1174
}
1175
1176
return { extensions: result, total };
1177
};
1178
const { extensions, total } = await runQuery(query, token);
1179
const getPage = async (pageIndex: number, ct: CancellationToken) => {
1180
if (ct.isCancellationRequested) {
1181
throw new CancellationError();
1182
}
1183
const { extensions } = await runQuery(query.withPage(pageIndex + 1), ct);
1184
return extensions;
1185
};
1186
1187
return { firstPage: extensions, total, pageSize: query.pageSize, getPage };
1188
}
1189
1190
private async queryGalleryExtensions(query: Query, criteria: ExtensionsCriteria, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<{ extensions: IGalleryExtension[]; total: number }> {
1191
const flags = query.flags;
1192
1193
/**
1194
* If both version flags (IncludeLatestVersionOnly and IncludeVersions) are included, then only include latest versions (IncludeLatestVersionOnly) flag.
1195
*/
1196
if (query.flags.includes(Flag.IncludeLatestVersionOnly) && query.flags.includes(Flag.IncludeVersions)) {
1197
query = query.withFlags(...query.flags.filter(flag => flag !== Flag.IncludeVersions));
1198
}
1199
1200
/**
1201
* If version flags (IncludeLatestVersionOnly and IncludeVersions) are not included, default is to query for latest versions (IncludeLatestVersionOnly).
1202
*/
1203
if (!query.flags.includes(Flag.IncludeLatestVersionOnly) && !query.flags.includes(Flag.IncludeVersions)) {
1204
query = query.withFlags(...query.flags, Flag.IncludeLatestVersionOnly);
1205
}
1206
1207
/**
1208
* If versions criteria exist or every requested extension is for release version and has a pre-release version, then remove latest flags and add all versions flag.
1209
*/
1210
if (criteria.versions?.length || criteria.isQueryForReleaseVersionFromPreReleaseVersion) {
1211
query = query.withFlags(...query.flags.filter(flag => flag !== Flag.IncludeLatestVersionOnly), Flag.IncludeVersions);
1212
}
1213
1214
/**
1215
* Add necessary extension flags
1216
*/
1217
query = query.withFlags(...query.flags, Flag.IncludeAssetUri, Flag.IncludeCategoryAndTags, Flag.IncludeFiles, Flag.IncludeStatistics, Flag.IncludeVersionProperties);
1218
const { galleryExtensions: rawGalleryExtensions, total, context } = await this.queryRawGalleryExtensions(query, extensionGalleryManifest, token);
1219
1220
const hasAllVersions: boolean = !query.flags.includes(Flag.IncludeLatestVersionOnly);
1221
if (hasAllVersions) {
1222
const extensions: IGalleryExtension[] = [];
1223
for (const rawGalleryExtension of rawGalleryExtensions) {
1224
const allTargetPlatforms = getAllTargetPlatforms(rawGalleryExtension);
1225
const extensionIdentifier = { id: getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), uuid: rawGalleryExtension.extensionId };
1226
const includePreRelease = isBoolean(criteria.includePreRelease) ? criteria.includePreRelease : !!criteria.includePreRelease.find(extensionIdentifierWithPreRelease => areSameExtensions(extensionIdentifierWithPreRelease, extensionIdentifier))?.includePreRelease;
1227
const rawGalleryExtensionVersion = await this.getValidRawGalleryExtensionVersion(
1228
rawGalleryExtension,
1229
rawGalleryExtension.versions,
1230
{
1231
compatible: criteria.compatible,
1232
targetPlatform: criteria.targetPlatform,
1233
productVersion: criteria.productVersion,
1234
version: criteria.versions?.find(extensionIdentifierWithVersion => areSameExtensions(extensionIdentifierWithVersion, extensionIdentifier))?.version
1235
?? (includePreRelease ? VersionKind.Latest : VersionKind.Release)
1236
},
1237
allTargetPlatforms
1238
);
1239
if (rawGalleryExtensionVersion) {
1240
extensions.push(toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, extensionGalleryManifest, this.productService, context));
1241
}
1242
}
1243
return { extensions, total };
1244
}
1245
1246
const result: [number, IGalleryExtension][] = [];
1247
const needAllVersions = new Map<string, number>();
1248
for (let index = 0; index < rawGalleryExtensions.length; index++) {
1249
const rawGalleryExtension = rawGalleryExtensions[index];
1250
const extensionIdentifier = { id: getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), uuid: rawGalleryExtension.extensionId };
1251
const includePreRelease = isBoolean(criteria.includePreRelease) ? criteria.includePreRelease : !!criteria.includePreRelease.find(extensionIdentifierWithPreRelease => areSameExtensions(extensionIdentifierWithPreRelease, extensionIdentifier))?.includePreRelease;
1252
const allTargetPlatforms = getAllTargetPlatforms(rawGalleryExtension);
1253
if (criteria.compatible) {
1254
// Skip looking for all versions if requested for a web-compatible extension and it is not a web extension.
1255
if (isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, criteria.targetPlatform)) {
1256
continue;
1257
}
1258
// Skip looking for all versions if the extension is not allowed.
1259
if (this.allowedExtensionsService.isAllowed({ id: extensionIdentifier.id, publisherDisplayName: rawGalleryExtension.publisher.displayName }) !== true) {
1260
continue;
1261
}
1262
}
1263
const rawGalleryExtensionVersion = await this.getValidRawGalleryExtensionVersion(
1264
rawGalleryExtension,
1265
rawGalleryExtension.versions,
1266
{
1267
compatible: criteria.compatible,
1268
targetPlatform: criteria.targetPlatform,
1269
productVersion: criteria.productVersion,
1270
version: criteria.versions?.find(extensionIdentifierWithVersion => areSameExtensions(extensionIdentifierWithVersion, extensionIdentifier))?.version
1271
?? (includePreRelease ? VersionKind.Latest : VersionKind.Release)
1272
},
1273
allTargetPlatforms
1274
);
1275
const extension = rawGalleryExtensionVersion ? toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, extensionGalleryManifest, this.productService, context) : null;
1276
if (!extension
1277
/** Need all versions if the extension is a pre-release version but
1278
* - the query is to look for a release version or
1279
* - the extension has no release version
1280
* Get all versions to get or check the release version
1281
*/
1282
|| (extension.properties.isPreReleaseVersion && (!includePreRelease || !extension.hasReleaseVersion))
1283
/**
1284
* Need all versions if the extension is a release version with a different target platform than requested and also has a pre-release version
1285
* Because, this is a platform specific extension and can have a newer release version supporting this platform.
1286
* See https://github.com/microsoft/vscode/issues/139628
1287
*/
1288
|| (!extension.properties.isPreReleaseVersion && extension.properties.targetPlatform !== criteria.targetPlatform && extension.hasPreReleaseVersion)
1289
) {
1290
needAllVersions.set(rawGalleryExtension.extensionId, index);
1291
} else {
1292
result.push([index, extension]);
1293
}
1294
}
1295
1296
if (needAllVersions.size) {
1297
const stopWatch = new StopWatch();
1298
const query = new Query()
1299
.withFlags(...flags.filter(flag => flag !== Flag.IncludeLatestVersionOnly), Flag.IncludeVersions)
1300
.withPage(1, needAllVersions.size)
1301
.withFilter(FilterType.ExtensionId, ...needAllVersions.keys());
1302
const { extensions } = await this.queryGalleryExtensions(query, criteria, extensionGalleryManifest, token);
1303
this.telemetryService.publicLog2<GalleryServiceAdditionalQueryEvent, GalleryServiceAdditionalQueryClassification>('galleryService:additionalQuery', {
1304
duration: stopWatch.elapsed(),
1305
count: needAllVersions.size
1306
});
1307
for (const extension of extensions) {
1308
const index = needAllVersions.get(extension.identifier.uuid)!;
1309
result.push([index, extension]);
1310
}
1311
}
1312
1313
return { extensions: result.sort((a, b) => a[0] - b[0]).map(([, extension]) => extension), total };
1314
}
1315
1316
private async getValidRawGalleryExtensionVersion(rawGalleryExtension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[], criteria: ExtensionVersionCriteria, allTargetPlatforms: TargetPlatform[]): Promise<IRawGalleryExtensionVersion | null> {
1317
const extensionIdentifier = { id: getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), uuid: rawGalleryExtension.extensionId };
1318
const rawGalleryExtensionVersions = sortExtensionVersions(versions, criteria.targetPlatform);
1319
1320
if (criteria.compatible && isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, criteria.targetPlatform)) {
1321
return null;
1322
}
1323
1324
const version = isString(criteria.version) ? criteria.version : undefined;
1325
1326
for (let index = 0; index < rawGalleryExtensionVersions.length; index++) {
1327
const rawGalleryExtensionVersion = rawGalleryExtensionVersions[index];
1328
if (criteria.compatible) {
1329
await this.setEngineIfNotExists(extensionIdentifier.id, rawGalleryExtensionVersion);
1330
}
1331
if (await this.isValidVersion(
1332
{
1333
id: extensionIdentifier.id,
1334
version: rawGalleryExtensionVersion.version,
1335
isPreReleaseVersion: isPreReleaseVersion(rawGalleryExtensionVersion),
1336
targetPlatform: getTargetPlatformForExtensionVersion(rawGalleryExtensionVersion),
1337
engine: getEngine(rawGalleryExtensionVersion),
1338
manifestAsset: getVersionAsset(rawGalleryExtensionVersion, AssetType.Manifest),
1339
enabledApiProposals: getEnabledApiProposals(rawGalleryExtensionVersion)
1340
},
1341
criteria,
1342
rawGalleryExtension.publisher.displayName,
1343
allTargetPlatforms)
1344
) {
1345
return rawGalleryExtensionVersion;
1346
}
1347
if (version && rawGalleryExtensionVersion.version === version) {
1348
return null;
1349
}
1350
}
1351
1352
if (version || criteria.compatible) {
1353
return null;
1354
}
1355
1356
/**
1357
* Fallback: Return the latest version
1358
* This can happen when the extension does not have a release version or does not have a version compatible with the given target platform.
1359
*/
1360
return rawGalleryExtension.versions[0];
1361
}
1362
1363
private async setEngineIfNotExists(extensionId: string, rawGalleryExtensionVersion: IRawGalleryExtensionVersion): Promise<void> {
1364
if (getEngine(rawGalleryExtensionVersion)) {
1365
return;
1366
}
1367
1368
try {
1369
const engine = await this.getEngine(extensionId, rawGalleryExtensionVersion.version, getVersionAsset(rawGalleryExtensionVersion, AssetType.Manifest));
1370
if (engine) {
1371
setEngine(rawGalleryExtensionVersion, engine);
1372
}
1373
} catch (error) {
1374
this.logService.error(`Error while getting the engine for the version ${rawGalleryExtensionVersion.version}.`, getErrorMessage(error));
1375
}
1376
}
1377
1378
private async queryRawGalleryExtensions(query: Query, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IRawGalleryExtensionsResult> {
1379
const extensionsQueryApi = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, ExtensionGalleryResourceType.ExtensionQueryService);
1380
1381
if (!extensionsQueryApi) {
1382
throw new Error('No extension gallery query service configured.');
1383
}
1384
1385
query = query
1386
/* Always exclude non validated extensions */
1387
.withFlags(...query.flags, Flag.ExcludeNonValidated)
1388
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code');
1389
1390
const unpublishedFlag = extensionGalleryManifest.capabilities.extensionQuery.flags?.find(f => f.name === Flag.Unpublished);
1391
/* Always exclude unpublished extensions */
1392
if (unpublishedFlag) {
1393
query = query.withFilter(FilterType.ExcludeWithFlags, String(unpublishedFlag.value));
1394
}
1395
1396
const data = JSON.stringify({
1397
filters: [
1398
{
1399
criteria: query.criteria.reduce<{ filterType: number; value?: string }[]>((criteria, c) => {
1400
const criterium = extensionGalleryManifest.capabilities.extensionQuery.filtering?.find(f => f.name === c.filterType);
1401
if (criterium) {
1402
criteria.push({
1403
filterType: criterium.value,
1404
value: c.value,
1405
});
1406
}
1407
return criteria;
1408
}, []),
1409
pageNumber: query.pageNumber,
1410
pageSize: query.pageSize,
1411
sortBy: extensionGalleryManifest.capabilities.extensionQuery.sorting?.find(s => s.name === query.sortBy)?.value,
1412
sortOrder: query.sortOrder,
1413
}
1414
],
1415
assetTypes: query.assetTypes,
1416
flags: query.flags.reduce<number>((flags, flag) => {
1417
const flagValue = extensionGalleryManifest.capabilities.extensionQuery.flags?.find(f => f.name === flag);
1418
if (flagValue) {
1419
flags |= flagValue.value;
1420
}
1421
return flags;
1422
}, 0)
1423
});
1424
1425
const commonHeaders = await this.commonHeadersPromise;
1426
const headers = {
1427
...commonHeaders,
1428
'Content-Type': 'application/json',
1429
'Accept': 'application/json;api-version=3.0-preview.1',
1430
'Accept-Encoding': 'gzip',
1431
'Content-Length': String(data.length),
1432
};
1433
1434
const stopWatch = new StopWatch();
1435
let context: IRequestContext | undefined, errorCode: ExtensionGalleryErrorCode | undefined, total: number = 0;
1436
1437
try {
1438
context = await this.requestService.request({
1439
type: 'POST',
1440
url: extensionsQueryApi,
1441
data,
1442
headers
1443
}, token);
1444
1445
if (context.res.statusCode && context.res.statusCode >= 400 && context.res.statusCode < 500) {
1446
return { galleryExtensions: [], total };
1447
}
1448
1449
const result = await asJson<IRawGalleryQueryResult>(context);
1450
if (result) {
1451
const r = result.results[0];
1452
const galleryExtensions = r.extensions;
1453
const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0];
1454
total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0;
1455
1456
return {
1457
galleryExtensions,
1458
total,
1459
context: context.res.headers['activityid'] ? {
1460
[SEARCH_ACTIVITY_HEADER_NAME]: context.res.headers['activityid']
1461
} : {}
1462
};
1463
}
1464
return { galleryExtensions: [], total };
1465
1466
} catch (e) {
1467
if (isCancellationError(e)) {
1468
errorCode = ExtensionGalleryErrorCode.Cancelled;
1469
throw e;
1470
} else {
1471
const errorMessage = getErrorMessage(e);
1472
errorCode = isOfflineError(e)
1473
? ExtensionGalleryErrorCode.Offline
1474
: errorMessage.startsWith('XHR timeout')
1475
? ExtensionGalleryErrorCode.Timeout
1476
: ExtensionGalleryErrorCode.Failed;
1477
throw new ExtensionGalleryError(errorMessage, errorCode);
1478
}
1479
} finally {
1480
this.telemetryService.publicLog2<GalleryServiceQueryEvent, GalleryServiceQueryClassification>('galleryService:query', {
1481
filterTypes: query.criteria.map(criterium => criterium.filterType),
1482
flags: query.flags,
1483
sortBy: query.sortBy,
1484
sortOrder: String(query.sortOrder),
1485
pageNumber: String(query.pageNumber),
1486
source: query.source,
1487
searchTextLength: query.searchText.length,
1488
requestBodySize: String(data.length),
1489
duration: stopWatch.elapsed(),
1490
success: !!context && isSuccess(context),
1491
responseBodySize: context?.res.headers['Content-Length'],
1492
statusCode: context ? String(context.res.statusCode) : undefined,
1493
errorCode,
1494
count: String(total),
1495
server: this.getHeaderValue(context?.res.headers, SERVER_HEADER_NAME),
1496
activityId: this.getHeaderValue(context?.res.headers, ACTIVITY_HEADER_NAME),
1497
endToEndId: this.getHeaderValue(context?.res.headers, END_END_ID_HEADER_NAME),
1498
});
1499
}
1500
}
1501
1502
private getHeaderValue(headers: IHeaders | undefined, name: string): TelemetryTrustedValue<string> | undefined {
1503
const headerValue = headers?.[name.toLowerCase()];
1504
const value = Array.isArray(headerValue) ? headerValue[0] : headerValue;
1505
return value ? new TelemetryTrustedValue(value) : undefined;
1506
}
1507
1508
private async getLatestRawGalleryExtensionWithFallback(extensionInfo: IExtensionInfo, resourceApi: { uri: string; fallback?: string }, token: CancellationToken): Promise<IRawGalleryExtension | null> {
1509
const [publisher, name] = extensionInfo.id.split('.');
1510
let errorCode: string | undefined;
1511
try {
1512
const uri = URI.parse(format2(resourceApi.uri, { publisher, name }));
1513
return await this.getLatestRawGalleryExtension(extensionInfo.id, uri, token);
1514
} catch (error) {
1515
if (error instanceof ExtensionGalleryError) {
1516
errorCode = error.code;
1517
switch (error.code) {
1518
case ExtensionGalleryErrorCode.Offline:
1519
case ExtensionGalleryErrorCode.Cancelled:
1520
case ExtensionGalleryErrorCode.Timeout:
1521
case ExtensionGalleryErrorCode.ClientError:
1522
throw error;
1523
}
1524
} else {
1525
errorCode = 'Unknown';
1526
}
1527
if (!resourceApi.fallback) {
1528
throw error;
1529
}
1530
} finally {
1531
this.telemetryService.publicLog2<
1532
{
1533
extension: string;
1534
errorCode?: string;
1535
},
1536
{
1537
owner: 'sandy081';
1538
comment: 'Report fetching latest version of an extension';
1539
extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the extension' };
1540
errorCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error code in case of error' };
1541
}
1542
>('galleryService:getmarketplacelatest', {
1543
extension: extensionInfo.id,
1544
errorCode,
1545
});
1546
}
1547
1548
this.logService.error(`Error while getting the latest version for the extension ${extensionInfo.id} from ${resourceApi.uri}. Trying the fallback ${resourceApi.fallback}`, errorCode);
1549
try {
1550
const uri = URI.parse(format2(resourceApi.fallback, { publisher, name }));
1551
return await this.getLatestRawGalleryExtension(extensionInfo.id, uri, token);
1552
} catch (error) {
1553
errorCode = error instanceof ExtensionGalleryError ? error.code : 'Unknown';
1554
throw error;
1555
} finally {
1556
this.telemetryService.publicLog2<
1557
{
1558
extension: string;
1559
errorCode?: string;
1560
},
1561
{
1562
owner: 'sandy081';
1563
comment: 'Report the fallback to the unpkg service for getting latest extension';
1564
extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension id' };
1565
errorCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error code in case of error' };
1566
}>('galleryService:fallbacktounpkg', {
1567
extension: extensionInfo.id,
1568
errorCode,
1569
});
1570
}
1571
}
1572
1573
private async getLatestRawGalleryExtension(extension: string, uri: URI, token: CancellationToken): Promise<IRawGalleryExtension | null> {
1574
let context;
1575
let errorCode: string | undefined;
1576
const stopWatch = new StopWatch();
1577
1578
try {
1579
const commonHeaders = await this.commonHeadersPromise;
1580
const headers = {
1581
...commonHeaders,
1582
'Content-Type': 'application/json',
1583
'Accept': 'application/json;api-version=7.2-preview',
1584
'Accept-Encoding': 'gzip',
1585
};
1586
1587
context = await this.requestService.request({
1588
type: 'GET',
1589
url: uri.toString(true),
1590
headers,
1591
timeout: this.getRequestTimeout()
1592
}, token);
1593
1594
if (context.res.statusCode === 404) {
1595
errorCode = 'NotFound';
1596
return null;
1597
}
1598
1599
if (context.res.statusCode && context.res.statusCode !== 200) {
1600
throw new Error('Unexpected HTTP response: ' + context.res.statusCode);
1601
}
1602
1603
const result = await asJson<IRawGalleryExtension>(context);
1604
if (!result) {
1605
errorCode = 'NoData';
1606
}
1607
return result;
1608
}
1609
1610
catch (error) {
1611
let galleryErrorCode: ExtensionGalleryErrorCode;
1612
if (isCancellationError(error)) {
1613
galleryErrorCode = ExtensionGalleryErrorCode.Cancelled;
1614
} else if (isOfflineError(error)) {
1615
galleryErrorCode = ExtensionGalleryErrorCode.Offline;
1616
} else if (getErrorMessage(error).startsWith('XHR timeout')) {
1617
galleryErrorCode = ExtensionGalleryErrorCode.Timeout;
1618
} else if (context && isClientError(context)) {
1619
galleryErrorCode = ExtensionGalleryErrorCode.ClientError;
1620
} else if (context && isServerError(context)) {
1621
galleryErrorCode = ExtensionGalleryErrorCode.ServerError;
1622
} else {
1623
galleryErrorCode = ExtensionGalleryErrorCode.Failed;
1624
}
1625
errorCode = galleryErrorCode;
1626
throw new ExtensionGalleryError(error, galleryErrorCode);
1627
}
1628
1629
finally {
1630
type GalleryServiceGetLatestEventClassification = {
1631
owner: 'sandy081';
1632
comment: 'Report the query to the Marketplace for fetching latest version of an extension';
1633
host: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The host of the end point' };
1634
extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the extension' };
1635
duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Duration in ms for the query' };
1636
errorCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error code in case of error' };
1637
statusCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The status code in case of error' };
1638
server?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The server of the end point' };
1639
activityId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The activity ID of the request' };
1640
endToEndId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The end-to-end ID of the request' };
1641
};
1642
type GalleryServiceGetLatestEvent = {
1643
extension: string;
1644
host: string;
1645
duration: number;
1646
errorCode?: string;
1647
statusCode?: string;
1648
server?: TelemetryTrustedValue<string>;
1649
activityId?: TelemetryTrustedValue<string>;
1650
endToEndId?: TelemetryTrustedValue<string>;
1651
};
1652
this.telemetryService.publicLog2<GalleryServiceGetLatestEvent, GalleryServiceGetLatestEventClassification>('galleryService:getLatest', {
1653
extension,
1654
host: uri.authority,
1655
duration: stopWatch.elapsed(),
1656
errorCode,
1657
statusCode: context?.res.statusCode && context?.res.statusCode !== 200 ? `${context.res.statusCode}` : undefined,
1658
server: this.getHeaderValue(context?.res.headers, SERVER_HEADER_NAME),
1659
activityId: this.getHeaderValue(context?.res.headers, ACTIVITY_HEADER_NAME),
1660
endToEndId: this.getHeaderValue(context?.res.headers, END_END_ID_HEADER_NAME),
1661
});
1662
}
1663
}
1664
1665
async reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise<void> {
1666
if (isWeb) {
1667
this.logService.info('ExtensionGalleryService#reportStatistic: Skipped in web');
1668
return undefined;
1669
}
1670
1671
const manifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();
1672
if (!manifest) {
1673
return undefined;
1674
}
1675
1676
const resource = getExtensionGalleryManifestResourceUri(manifest, ExtensionGalleryResourceType.ExtensionStatisticsUri);
1677
if (!resource) {
1678
return;
1679
}
1680
const url = format2(resource, { publisher, name, version, statTypeName: type });
1681
1682
const Accept = '*/*;api-version=4.0-preview.1';
1683
const commonHeaders = await this.commonHeadersPromise;
1684
const headers = { ...commonHeaders, Accept };
1685
try {
1686
await this.requestService.request({
1687
type: 'POST',
1688
url,
1689
headers
1690
}, CancellationToken.None);
1691
} catch (error) { /* Ignore */ }
1692
}
1693
1694
async download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void> {
1695
this.logService.trace('ExtensionGalleryService#download', extension.identifier.id);
1696
const data = getGalleryExtensionTelemetryData(extension);
1697
const startTime = new Date().getTime();
1698
1699
const operationParam = operation === InstallOperation.Install ? 'install' : operation === InstallOperation.Update ? 'update' : '';
1700
const downloadAsset = operationParam ? {
1701
uri: `${extension.assets.download.uri}${URI.parse(extension.assets.download.uri).query ? '&' : '?'}${operationParam}=true`,
1702
fallbackUri: `${extension.assets.download.fallbackUri}${URI.parse(extension.assets.download.fallbackUri).query ? '&' : '?'}${operationParam}=true`
1703
} : extension.assets.download;
1704
1705
const activityId = extension.queryContext?.[SEARCH_ACTIVITY_HEADER_NAME];
1706
const headers: IHeaders | undefined = activityId && typeof activityId === 'string' ? { [SEARCH_ACTIVITY_HEADER_NAME]: activityId } : undefined;
1707
const context = await this.getAsset(extension.identifier.id, downloadAsset, AssetType.VSIX, extension.version, headers ? { headers } : undefined);
1708
1709
try {
1710
await this.fileService.writeFile(location, context.stream);
1711
} catch (error) {
1712
try {
1713
await this.fileService.del(location);
1714
} catch (e) {
1715
/* ignore */
1716
this.logService.warn(`Error while deleting the file ${location.toString()}`, getErrorMessage(e));
1717
}
1718
throw new ExtensionGalleryError(getErrorMessage(error), ExtensionGalleryErrorCode.DownloadFailedWriting);
1719
}
1720
1721
/* __GDPR__
1722
"galleryService:downloadVSIX" : {
1723
"owner": "sandy081",
1724
"duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
1725
"${include}": [
1726
"${GalleryExtensionTelemetryData}"
1727
]
1728
}
1729
*/
1730
this.telemetryService.publicLog('galleryService:downloadVSIX', { ...data, duration: new Date().getTime() - startTime });
1731
}
1732
1733
async downloadSignatureArchive(extension: IGalleryExtension, location: URI): Promise<void> {
1734
if (!extension.assets.signature) {
1735
throw new Error('No signature asset found');
1736
}
1737
1738
this.logService.trace('ExtensionGalleryService#downloadSignatureArchive', extension.identifier.id);
1739
1740
const context = await this.getAsset(extension.identifier.id, extension.assets.signature, AssetType.Signature, extension.version);
1741
try {
1742
await this.fileService.writeFile(location, context.stream);
1743
} catch (error) {
1744
try {
1745
await this.fileService.del(location);
1746
} catch (e) {
1747
/* ignore */
1748
this.logService.warn(`Error while deleting the file ${location.toString()}`, getErrorMessage(e));
1749
}
1750
throw new ExtensionGalleryError(getErrorMessage(error), ExtensionGalleryErrorCode.DownloadFailedWriting);
1751
}
1752
1753
}
1754
1755
async getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string> {
1756
if (extension.assets.readme) {
1757
const context = await this.getAsset(extension.identifier.id, extension.assets.readme, AssetType.Details, extension.version, {}, token);
1758
const content = await asTextOrError(context);
1759
return content || '';
1760
}
1761
return '';
1762
}
1763
1764
async getManifest(extension: IGalleryExtension, token: CancellationToken): Promise<IExtensionManifest | null> {
1765
if (extension.assets.manifest) {
1766
const context = await this.getAsset(extension.identifier.id, extension.assets.manifest, AssetType.Manifest, extension.version, {}, token);
1767
const text = await asTextOrError(context);
1768
return text ? JSON.parse(text) : null;
1769
}
1770
return null;
1771
}
1772
1773
async getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation | null> {
1774
const asset = extension.assets.coreTranslations.filter(t => t[0] === languageId.toUpperCase())[0];
1775
if (asset) {
1776
const context = await this.getAsset(extension.identifier.id, asset[1], asset[0], extension.version);
1777
const text = await asTextOrError(context);
1778
return text ? JSON.parse(text) : null;
1779
}
1780
return null;
1781
}
1782
1783
async getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise<string> {
1784
if (extension.assets.changelog) {
1785
const context = await this.getAsset(extension.identifier.id, extension.assets.changelog, AssetType.Changelog, extension.version, {}, token);
1786
const content = await asTextOrError(context);
1787
return content || '';
1788
}
1789
return '';
1790
}
1791
1792
async getAllVersions(extensionIdentifier: IExtensionIdentifier): Promise<IGalleryExtensionVersion[]> {
1793
return this.getVersions(extensionIdentifier);
1794
}
1795
1796
async getAllCompatibleVersions(extensionIdentifier: IExtensionIdentifier, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtensionVersion[]> {
1797
return this.getVersions(extensionIdentifier, { version: includePreRelease ? VersionKind.Latest : VersionKind.Release, targetPlatform });
1798
}
1799
1800
private async getVersions(extensionIdentifier: IExtensionIdentifier, onlyCompatible?: { version: VersionKind; targetPlatform: TargetPlatform }): Promise<IGalleryExtensionVersion[]> {
1801
const extensionGalleryManifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();
1802
if (!extensionGalleryManifest) {
1803
throw new Error('No extension gallery service configured.');
1804
}
1805
1806
let query = new Query()
1807
.withFlags(Flag.IncludeVersions, Flag.IncludeCategoryAndTags, Flag.IncludeFiles, Flag.IncludeVersionProperties)
1808
.withPage(1, 1);
1809
1810
if (extensionIdentifier.uuid) {
1811
query = query.withFilter(FilterType.ExtensionId, extensionIdentifier.uuid);
1812
} else {
1813
query = query.withFilter(FilterType.ExtensionName, extensionIdentifier.id);
1814
}
1815
1816
const { galleryExtensions } = await this.queryRawGalleryExtensions(query, extensionGalleryManifest, CancellationToken.None);
1817
if (!galleryExtensions.length) {
1818
return [];
1819
}
1820
1821
const allTargetPlatforms = getAllTargetPlatforms(galleryExtensions[0]);
1822
if (onlyCompatible && isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, onlyCompatible.targetPlatform)) {
1823
return [];
1824
}
1825
1826
const versions: IRawGalleryExtensionVersion[] = [];
1827
const productVersion = { version: this.productService.version, date: this.productService.date };
1828
await Promise.all(galleryExtensions[0].versions.map(async (version) => {
1829
try {
1830
if (
1831
(await this.isValidVersion(
1832
{
1833
id: extensionIdentifier.id,
1834
version: version.version,
1835
isPreReleaseVersion: isPreReleaseVersion(version),
1836
targetPlatform: getTargetPlatformForExtensionVersion(version),
1837
engine: getEngine(version),
1838
manifestAsset: getVersionAsset(version, AssetType.Manifest),
1839
enabledApiProposals: getEnabledApiProposals(version)
1840
},
1841
{
1842
compatible: !!onlyCompatible,
1843
productVersion,
1844
targetPlatform: onlyCompatible?.targetPlatform,
1845
version: onlyCompatible?.version ?? version.version
1846
},
1847
galleryExtensions[0].publisher.displayName,
1848
allTargetPlatforms))
1849
) {
1850
versions.push(version);
1851
}
1852
} catch (error) { /* Ignore error and skip version */ }
1853
}));
1854
1855
const result: IGalleryExtensionVersion[] = [];
1856
const seen = new Map<string, number>();
1857
for (const version of sortExtensionVersions(versions, onlyCompatible?.targetPlatform ?? CURRENT_TARGET_PLATFORM)) {
1858
const index = seen.get(version.version);
1859
const existing = index !== undefined ? result[index] : undefined;
1860
const targetPlatform = getTargetPlatformForExtensionVersion(version);
1861
if (!existing) {
1862
seen.set(version.version, result.length);
1863
result.push({ version: version.version, date: version.lastUpdated, isPreReleaseVersion: isPreReleaseVersion(version), targetPlatforms: [targetPlatform] });
1864
} else {
1865
existing.targetPlatforms.push(targetPlatform);
1866
}
1867
}
1868
1869
return result;
1870
}
1871
1872
private async getAsset(extension: string, asset: IGalleryExtensionAsset, assetType: string, extensionVersion: string, options: IRequestOptions = {}, token: CancellationToken = CancellationToken.None): Promise<IRequestContext> {
1873
const commonHeaders = await this.commonHeadersPromise;
1874
const baseOptions = { type: 'GET' };
1875
const headers = { ...commonHeaders, ...(options.headers || {}) };
1876
options = { ...options, ...baseOptions, headers };
1877
1878
const url = asset.uri;
1879
const fallbackUrl = asset.fallbackUri;
1880
const firstOptions = { ...options, url, timeout: this.getRequestTimeout() };
1881
1882
let context;
1883
try {
1884
context = await this.requestService.request(firstOptions, token);
1885
if (context.res.statusCode === 200) {
1886
return context;
1887
}
1888
const message = await asTextOrError(context);
1889
throw new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`);
1890
} catch (err) {
1891
if (isCancellationError(err)) {
1892
throw err;
1893
}
1894
1895
const message = getErrorMessage(err);
1896
type GalleryServiceCDNFallbackClassification = {
1897
owner: 'sandy081';
1898
comment: 'Fallback request information when the primary asset request to CDN fails';
1899
extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'extension name' };
1900
assetType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'asset that failed' };
1901
message: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'error message' };
1902
extensionVersion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'version' };
1903
readonly server?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'server that handled the query' };
1904
readonly endToEndId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'end to end operation id' };
1905
readonly activityId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'activity id' };
1906
};
1907
type GalleryServiceCDNFallbackEvent = {
1908
extension: string;
1909
assetType: string;
1910
message: string;
1911
extensionVersion: string;
1912
server?: TelemetryTrustedValue<string>;
1913
endToEndId?: TelemetryTrustedValue<string>;
1914
activityId?: TelemetryTrustedValue<string>;
1915
};
1916
this.telemetryService.publicLog2<GalleryServiceCDNFallbackEvent, GalleryServiceCDNFallbackClassification>('galleryService:cdnFallback', {
1917
extension,
1918
assetType,
1919
message,
1920
extensionVersion,
1921
server: this.getHeaderValue(context?.res.headers, SERVER_HEADER_NAME),
1922
activityId: this.getHeaderValue(context?.res.headers, ACTIVITY_HEADER_NAME),
1923
endToEndId: this.getHeaderValue(context?.res.headers, END_END_ID_HEADER_NAME),
1924
});
1925
1926
const fallbackOptions = { ...options, url: fallbackUrl, timeout: this.getRequestTimeout() };
1927
return this.requestService.request(fallbackOptions, token);
1928
}
1929
}
1930
1931
async getExtensionsControlManifest(): Promise<IExtensionsControlManifest> {
1932
const manifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();
1933
if (!manifest) {
1934
throw new Error('No extension gallery service configured.');
1935
}
1936
1937
1938
if (!this.extensionsControlUrl) {
1939
return { malicious: [], deprecated: {}, search: [], autoUpdate: {} };
1940
}
1941
1942
const context = await this.requestService.request({
1943
type: 'GET',
1944
url: this.extensionsControlUrl,
1945
timeout: this.getRequestTimeout()
1946
}, CancellationToken.None);
1947
1948
if (context.res.statusCode !== 200) {
1949
throw new Error('Could not get extensions report.');
1950
}
1951
1952
const result = await asJson<IRawExtensionsControlManifest>(context);
1953
const malicious: Array<MaliciousExtensionInfo> = [];
1954
const deprecated: IStringDictionary<IDeprecationInfo> = {};
1955
const search: ISearchPrefferedResults[] = [];
1956
const autoUpdate: IStringDictionary<string> = result?.autoUpdate ?? {};
1957
if (result) {
1958
for (const id of result.malicious) {
1959
if (!isString(id)) {
1960
continue;
1961
}
1962
const publisherOrExtension = EXTENSION_IDENTIFIER_REGEX.test(id) ? { id } : id;
1963
malicious.push({ extensionOrPublisher: publisherOrExtension, learnMoreLink: result.learnMoreLinks?.[id] });
1964
}
1965
if (result.migrateToPreRelease) {
1966
for (const [unsupportedPreReleaseExtensionId, preReleaseExtensionInfo] of Object.entries(result.migrateToPreRelease)) {
1967
if (!preReleaseExtensionInfo.engine || isEngineValid(preReleaseExtensionInfo.engine, this.productService.version, this.productService.date)) {
1968
deprecated[unsupportedPreReleaseExtensionId.toLowerCase()] = {
1969
disallowInstall: true,
1970
extension: {
1971
id: preReleaseExtensionInfo.id,
1972
displayName: preReleaseExtensionInfo.displayName,
1973
autoMigrate: { storage: !!preReleaseExtensionInfo.migrateStorage },
1974
preRelease: true
1975
}
1976
};
1977
}
1978
}
1979
}
1980
if (result.deprecated) {
1981
for (const [deprecatedExtensionId, deprecationInfo] of Object.entries(result.deprecated)) {
1982
if (deprecationInfo) {
1983
deprecated[deprecatedExtensionId.toLowerCase()] = isBoolean(deprecationInfo) ? {} : deprecationInfo;
1984
}
1985
}
1986
}
1987
if (result.search) {
1988
for (const s of result.search) {
1989
search.push(s);
1990
}
1991
}
1992
}
1993
1994
deprecated[this.productService.defaultChatAgent.extensionId.toLowerCase()] = {
1995
disallowInstall: true,
1996
extension: {
1997
id: this.productService.defaultChatAgent.chatExtensionId,
1998
displayName: 'GitHub Copilot Chat',
1999
autoMigrate: { storage: false, donotDisable: true },
2000
preRelease: this.productService.quality !== 'stable'
2001
}
2002
};
2003
2004
return { malicious, deprecated, search, autoUpdate };
2005
}
2006
2007
private getRequestTimeout(): number {
2008
const configuredTimeout = this.configurationService.getValue<number>(ExtensionRequestsTimeoutConfigKey);
2009
return isNumber(configuredTimeout) && configuredTimeout >= 0 ? configuredTimeout : 60_000;
2010
}
2011
2012
}
2013
2014
export class ExtensionGalleryService extends AbstractExtensionGalleryService {
2015
2016
constructor(
2017
@IStorageService storageService: IStorageService,
2018
@IRequestService requestService: IRequestService,
2019
@ILogService logService: ILogService,
2020
@IEnvironmentService environmentService: IEnvironmentService,
2021
@ITelemetryService telemetryService: ITelemetryService,
2022
@IFileService fileService: IFileService,
2023
@IProductService productService: IProductService,
2024
@IConfigurationService configurationService: IConfigurationService,
2025
@IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService,
2026
@IExtensionGalleryManifestService extensionGalleryManifestService: IExtensionGalleryManifestService,
2027
) {
2028
super(storageService, requestService, logService, environmentService, telemetryService, fileService, productService, configurationService, allowedExtensionsService, extensionGalleryManifestService);
2029
}
2030
}
2031
2032
export class ExtensionGalleryServiceWithNoStorageService extends AbstractExtensionGalleryService {
2033
2034
constructor(
2035
@IRequestService requestService: IRequestService,
2036
@ILogService logService: ILogService,
2037
@IEnvironmentService environmentService: IEnvironmentService,
2038
@ITelemetryService telemetryService: ITelemetryService,
2039
@IFileService fileService: IFileService,
2040
@IProductService productService: IProductService,
2041
@IConfigurationService configurationService: IConfigurationService,
2042
@IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService,
2043
@IExtensionGalleryManifestService extensionGalleryManifestService: IExtensionGalleryManifestService,
2044
) {
2045
super(undefined, requestService, logService, environmentService, telemetryService, fileService, productService, configurationService, allowedExtensionsService, extensionGalleryManifestService);
2046
}
2047
}
2048
2049