src/vs/platform/extensionManagement/common/extensionGalleryService.ts

2045 LOC · 489 covered · 1556 uncovered · 86 ranges · 30 concepts · 11 introducers · 29 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the filesrc/vs/base/parts/request/common/request.ts · 66 LOCcommon/request.tssrc/vs/platform/configuration/test/common/testConfigurationService.ts · 91 LOCcommon/testConfiguration…src/vs/platform/extensionManagement/common/extensionManagement.ts · 808 LOCcommon/extensionManageme…src/vs/platform/externalServices/common/marketplace.ts · 39 LOCcommon/marketplace.tsmarketplace.ts ×1 · 24 introduced LOCmarketplace.ts ×1extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle both release and pre-release with same version replacement|occurrence=1 · 0 introduced LOCextensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle complex scenario with multiple versions and platforms|occurrence=1 · 0 introduced LOCextensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle replacement with non-compatible versions in between|occurrence=1 · 0 introduced LOCextensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle mixed release and pre-release versions across multiple platforms|occurrence=1 · 0 introduced LOCextensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle multiple specific platforms vs universal platform with version differences|occurrence=1 · 0 introduced LOCextensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should filter versions for linux-x64 target platform with mixed universal and platform-specific versions|occurrence=1 · 0 introduced LOCextensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should include all non-compatible platform versions|occurrence=1, extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should include one version per target platform for release versions|occurrence=1 · 0 introduced LOCextensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should not replace when specific platform is for different platform|occurrence=1, extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should include universal platform when no specific platforms conflict|occurrence=1 · 0 introduced LOCextensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should prefer specific target platform over undefined when same version exists for both|occurrence=1 · 0 introduced LOCextensionGalleryService.…extensionManagement.ts ×1 · 2 introduced LOCextensionManagement.ts ×…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should include latest prerelease and latest release versions for same platform|occurrence=1, extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should include latest release and latest pre-release versions for same platform|occurrence=1 · 0 introduced LOCextensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should keep only first compatible version when specific platform comes before undefined|occurrence=1, extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle higher version with specific platform vs lower version with universal platform|occurrence=1, +1 · 0 introduced LOCextensionGalleryService.…extensionGalleryService.ts ×1 · 2 introduced LOCextensionGalleryService.…extensionGalleryService.ts ×1 · 6 introduced LOCextensionGalleryService.…extensionGalleryService.ts ×1 · 2 introduced LOCextensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should return single version when only one version provided|occurrence=1 · 0 introduced LOCextensionGalleryService.…extensionGalleryService.ts ×1 · 4 introduced LOCextensionGalleryService.…extensionManagement.ts ×1 · 2 introduced LOCextensionManagement.ts ×…extensionManagement.ts ×3 · 9 introduced LOCextensionManagement.ts ×…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle versions without target platform (UNDEFINED)|occurrence=1 · 0 introduced LOCextensionGalleryService.…extensionGalleryService.ts ×2 · 5 introduced LOCextensionGalleryService.…extensionManagement.ts ×1 · 2 introduced LOCextensionManagement.ts ×…extensionGalleryService.ts ×5 · 23 introduced LOCextensionGalleryService.…extensionGalleryService.ts ×1 · 11 introduced LOCextensionGalleryService.…extensionGalleryService.ts ×2 · 7 introduced LOCextensionGalleryService.…extensionGalleryService.ts ×2 · 8 introduced LOCextensionGalleryService.…extensionManagement.ts ×1 · 20 introduced LOCextensionManagement.ts ×…extensionGalleryService.ts ×1 · 3 introduced LOCextensionGalleryService.…extensionGalleryService.ts ×69 · 504 introduced LOCextensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should filter versions for linux-x64 target platform with mixed universal and platform-specific versions|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should filter versions for linux-x64 target platform with mixed universal and platform-specific versions|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle both release and pre-release with same version replacement|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle both release and pre-release with same version replacement|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle complex scenario with multiple versions and platforms|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle complex scenario with multiple versions and platforms|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle explicit UNIVERSAL platform|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle explicit UNIVERSAL platform|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle higher version with specific platform vs lower version with universal platform|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle higher version with specific platform vs lower version with universal platform|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle higher version with universal platform vs lower version with specific platform|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle higher version with universal platform vs lower version with specific platform|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle mixed release and pre-release versions across multiple platforms|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle mixed release and pre-release versions across multiple platforms|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle multiple specific platforms vs universal platform with version differences|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle multiple specific platforms vs universal platform with version differences|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle replacement with non-compatible versions in between|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle replacement with non-compatible versions in between|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle versions without target platform (UNDEFINED)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should handle versions without target platform (UNDEFINED)|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should include all non-compatible platform versions|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should include all non-compatible platform versions|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should include latest prerelease and latest release versions for same platform|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should include latest prerelease and latest release versions for same platform|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should include latest release and latest pre-release versions for same platform|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should include latest release and latest pre-release versions for same platform|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should include one version per target platform for release versions|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should include one version per target platform for release versions|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should include universal platform when no specific platforms conflict|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should include universal platform when no specific platforms conflict|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should keep only first compatible version when specific platform comes before undefined|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should keep only first compatible version when specific platform comes before undefined|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should not replace when specific platform is for different platform|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should not replace when specific platform is for different platform|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should prefer specific target platform over undefined when same version exists for both|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should prefer specific target platform over undefined when same version exists for both|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should replace undefined pre-release with specific platform pre-release|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should replace undefined pre-release with specific platform pre-release|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should return empty array for empty input|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should return empty array for empty input|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should return single version when only one version provided|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service filterLatestExtensionVersionsForTargetPlatform should return single version when only one version provided|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service marketplace machine id|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service marketplace machine id|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service sorting multiple extension versions with target platforms - 1|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service sorting multiple extension versions with target platforms - 1|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service sorting multiple extension versions with target platforms - 2|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service sorting multiple extension versions with target platforms - 2|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service sorting multiple extension versions with target platforms - 3|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service sorting multiple extension versions with target platforms - 3|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service sorting multiple extension versions without target platforms|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service sorting multiple extension versions without target platforms|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service sorting single extension version with not compatible target platform|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service sorting single extension version with not compatible target platform|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service sorting single extension version with preferred target platform|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service sorting single extension version with preferred target platform|occurrence=1extensionGalleryService.…extensionGalleryService.test|title=Extension Gallery Service sorting single extension version without target platform|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/extensionManagement/test/common/extensionGalleryService.test|title=Extension Gallery Service sorting single extension version without target platform|occurrence=1extensionGalleryService.…Focused file · src/vs/platform/extensionManagement/common/extensionGalleryService.ts · 2045 LOCcommon/extensionGalleryS…

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- extensionGalleryService.ts ×69
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 { 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 }
261 >
262 > withPage(pageNumber: number, pageSize: number = this.state.pageSize): Query {
263 return new Query({ ...this.state, pageNumber, pageSize });
264 }
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 }
275 > withSortBy(sortBy: SortBy): Query {
276 return new Query({ ...this.state, sortBy });
277 }
279 > withSortOrder(sortOrder: SortOrder): Query {
280 return new Query({ ...this.state, sortOrder });
281 }
283 > withFlags(...flags: Flag[]): Query {
284 return new Query({ ...this.state, flags: distinct(flags) });
285 }
287 > withAssetTypes(...assetTypes: string[]): Query {
288 return new Query({ ...this.state, assetTypes });
289 }
291 > withSource(source: string): Query {
292 return new Query({ ...this.state, source });
293 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
351 function setEngine(version: IRawGalleryExtensionVersion, engine: string): void {
352 version.properties = version.properties ?? [];
353 version.properties.push({ key: PropertyType.Engine, value: engine });
354 }
356 > function isPreReleaseVersion(version: IRawGalleryExtensionVersion): boolean { extensionGalleryService.ts ×5
357 > const values = version.properties ? version.properties.filter(p => p.key === PropertyType.PreRelease) : [];
358 > return values.length > 0 && values[0].value === 'true';
359 > }
361 function hasPreReleaseForExtension(id: string, productService: IProductService): boolean | undefined {
362 return productService.extensionProperties?.[id.toLowerCase()]?.hasPrereleaseVersion;
363 }
365 function getExcludeVersionRangeForExtension(id: string, productService: IProductService): string | undefined {
366 return productService.extensionProperties?.[id.toLowerCase()]?.excludeVersionRange;
367 }
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 }
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 }
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 }
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 }
391 function getSponsorLink(version: IRawGalleryExtensionVersion): string | undefined {
392 return version.properties?.find(p => p.key === PropertyType.SponsorLink)?.value;
393 }
395 function getSupportLink(version: IRawGalleryExtensionVersion): string | undefined {
396 return version.properties?.find(p => p.key === PropertyType.SupportLink)?.value;
397 }
399 function getIsPreview(flags: string): boolean {
400 return flags.indexOf('preview') !== -1;
401 }
403 > function getTargetPlatformForExtensionVersion(version: IRawGalleryExtensionVersion): TargetPlatform { extensionGalleryService.ts ×1
404 > return version.targetPlatform ? toTargetPlatform(version.targetPlatform) : TargetPlatform.UNDEFINED;
405 > }
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 }
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 */ extensionGalleryService.ts ×2
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; extensionGalleryService.ts ×1
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 > }
447 > return versions;
448 > }
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[] = []; extensionGalleryService.ts ×2
470 >
471 > let preReleaseVersionIndex: number = -1;
472 > let releaseVersionIndex: number = -1;
473 > for (const version of versions) {
474 > const versionTargetPlatform = getTargetPlatformForExtensionVersion(version); extensionGalleryService.ts ×5
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); extensionGalleryService.ts ×1
480 > continue;
481 > }
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) { extensionGalleryService.ts ×1
487 > preReleaseVersionIndex = latestVersions.length;
488 > latestVersions.push(version);
489 > } else if (versionTargetPlatform === targetPlatform && latestVersions[preReleaseVersionIndex].version === version.version) {
490 > latestVersions[preReleaseVersionIndex] = version; extensionGalleryService.ts ×1
491 > }
493 > if (releaseVersionIndex === -1) { extensionGalleryService.ts ×2
494 > releaseVersionIndex = latestVersions.length;
495 > latestVersions.push(version);
496 > } else if (versionTargetPlatform === targetPlatform && latestVersions[releaseVersionIndex].version === version.version) {
497 > latestVersions[releaseVersionIndex] = version; extensionGalleryService.ts ×1
498 > }
502 > return latestVersions;
503 > }
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 }
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 }
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 >
613 > constructor(
614 storageService: IStorageService | undefined,
615 @IRequestService private readonly requestService: IRequestService,
616 @ILogService private readonly logService: ILogService,
617 @IEnvironmentService private readonly environmentService: IEnvironmentService,
618 @ITelemetryService private readonly telemetryService: ITelemetryService,
619 @IFileService private readonly fileService: IFileService,
620 @IProductService private readonly productService: IProductService,
621 @IConfigurationService private readonly configurationService: IConfigurationService,
622 @IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService,
623 @IExtensionGalleryManifestService private readonly extensionGalleryManifestService: IExtensionGalleryManifestService,
624 ) {
625 this.extensionsControlUrl = productService.extensionsGallery?.controlUrl;
626 this.unpkgResourceApi = productService.extensionsGallery?.extensionUrlTemplate;
627 this.commonHeadersPromise = resolveMarketplaceHeaders(
628 productService.version,
629 productService,
630 this.environmentService,
631 this.configurationService,
632 this.fileService,
633 storageService,
634 this.telemetryService);
635 }
637 > isEnabled(): boolean {
638 return this.extensionGalleryManifestService.extensionGalleryManifestStatus === ExtensionGalleryManifestStatus.Available;
639 }
641 > getExtensions(extensionInfos: ReadonlyArray<IExtensionInfo>, token: CancellationToken): Promise<IGalleryExtension[]>;
642 > getExtensions(extensionInfos: ReadonlyArray<IExtensionInfo>, options: IExtensionQueryOptions, token: CancellationToken): Promise<IGalleryExtension[]>;
643 > async getExtensions(extensionInfos: ReadonlyArray<IExtensionInfo>, arg1: CancellationToken | IExtensionQueryOptions, arg2?: CancellationToken): Promise<IGalleryExtension[]> {
644 const extensionGalleryManifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();
645 if (!extensionGalleryManifest) {
646 throw new Error('No extension gallery service configured.');
647 }
648
649 const options = CancellationToken.isCancellationToken(arg1) ? {} : arg1 as IExtensionQueryOptions;
650 const token = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2 as CancellationToken;
651
652 const resourceApi = this.getResourceApi(extensionGalleryManifest);
653 const result = resourceApi
654 ? await this.getExtensionsUsingResourceApi(extensionInfos, options, resourceApi, extensionGalleryManifest, token)
655 : await this.getExtensionsUsingQueryApi(extensionInfos, options, extensionGalleryManifest, token);
656
657 const uuids = result.map(r => r.identifier.uuid);
658 const extensionInfosByName: IExtensionInfo[] = [];
659 for (const e of extensionInfos) {
660 if (e.uuid && !uuids.includes(e.uuid)) {
661 extensionInfosByName.push({ ...e, uuid: undefined });
662 }
663 }
664
665 if (extensionInfosByName.length) {
666 // report telemetry data for additional query
667 this.telemetryService.publicLog2<
668 { count: number },
669 {
670 owner: 'sandy081';
671 comment: 'Report the query to the Marketplace for fetching extensions by name';
672 readonly count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of extensions to fetch' };
673 }>('galleryService:additionalQueryByName', {
674 count: extensionInfosByName.length
675 });
676
677 const extensions = await this.getExtensionsUsingQueryApi(extensionInfosByName, options, extensionGalleryManifest, token);
678 result.push(...extensions);
679 }
680
681 return result;
682 }
684 > private getResourceApi(extensionGalleryManifest: IExtensionGalleryManifest): { uri: string; fallback?: string } | undefined {
685 const latestVersionResource = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, ExtensionGalleryResourceType.ExtensionLatestVersionUri);
686 if (latestVersionResource) {
687 return {
688 uri: latestVersionResource,
689 fallback: this.unpkgResourceApi
690 };
691 }
692 return undefined;
693 }
695 > private async getExtensionsUsingQueryApi(extensionInfos: ReadonlyArray<IExtensionInfo>, options: IExtensionQueryOptions, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IGalleryExtension[]> {
696 const names: string[] = [],
697 ids: string[] = [],
698 includePreRelease: (IExtensionIdentifier & { includePreRelease: boolean })[] = [],
699 versions: (IExtensionIdentifier & { version: string })[] = [];
700 let isQueryForReleaseVersionFromPreReleaseVersion = true;
701
702 for (const extensionInfo of extensionInfos) {
703 if (extensionInfo.uuid) {
704 ids.push(extensionInfo.uuid);
705 } else {
706 names.push(extensionInfo.id);
707 }
708 if (extensionInfo.version) {
709 versions.push({ id: extensionInfo.id, uuid: extensionInfo.uuid, version: extensionInfo.version });
710 } else {
711 includePreRelease.push({ id: extensionInfo.id, uuid: extensionInfo.uuid, includePreRelease: !!extensionInfo.preRelease });
712 }
713 isQueryForReleaseVersionFromPreReleaseVersion = isQueryForReleaseVersionFromPreReleaseVersion && (!!extensionInfo.hasPreRelease && !extensionInfo.preRelease);
714 }
715
716 if (!ids.length && !names.length) {
717 return [];
718 }
719
720 let query = new Query().withPage(1, extensionInfos.length);
721 if (ids.length) {
722 query = query.withFilter(FilterType.ExtensionId, ...ids);
723 }
724 if (names.length) {
725 query = query.withFilter(FilterType.ExtensionName, ...names);
726 }
727 if (options.queryAllVersions) {
728 query = query.withFlags(...query.flags, Flag.IncludeVersions);
729 }
730 if (options.source) {
731 query = query.withSource(options.source);
732 }
733
734 const { extensions } = await this.queryGalleryExtensions(
735 query,
736 {
737 targetPlatform: options.targetPlatform ?? CURRENT_TARGET_PLATFORM,
738 includePreRelease,
739 versions,
740 compatible: !!options.compatible,
741 productVersion: options.productVersion ?? { version: this.productService.version, date: this.productService.date },
742 isQueryForReleaseVersionFromPreReleaseVersion
743 },
744 extensionGalleryManifest,
745 token);
746
747 if (options.source) {
748 extensions.forEach((e, index) => setTelemetry(e, index, options.source));
749 }
750
751 return extensions;
752 }
754 > private async getExtensionsUsingResourceApi(extensionInfos: ReadonlyArray<IExtensionInfo>, options: IExtensionQueryOptions, resourceApi: { uri: string; fallback?: string }, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IGalleryExtension[]> {
755
756 const result: IGalleryExtension[] = [];
757 const toQuery: IExtensionInfo[] = [];
758 const toFetchLatest: IExtensionInfo[] = [];
759
760 for (const extensionInfo of extensionInfos) {
761 if (!EXTENSION_IDENTIFIER_REGEX.test(extensionInfo.id)) {
762 continue;
763 }
764 if (extensionInfo.version) {
765 toQuery.push(extensionInfo);
766 } else {
767 toFetchLatest.push(extensionInfo);
768 }
769 }
770
771 await Promise.all(toFetchLatest.map(async extensionInfo => {
772 let galleryExtension: IGalleryExtension | string;
773 try {
774 galleryExtension = await this.getLatestGalleryExtension(extensionInfo, options, resourceApi, extensionGalleryManifest, token);
775 if (isString(galleryExtension)) {
776 if (galleryExtension === 'LATEST_IS_OUTDATED') {
777 this.logService.debug(`Skipping query API fallback for extension ${extensionInfo.id} because the latest gallery version is older than the current version`);
778 } else {
779 // fallback to query
780 this.telemetryService.publicLog2<
781 {
782 extension: string;
783 preRelease: boolean;
784 compatible: boolean;
785 errorCode: string;
786 },
787 {
788 owner: 'sandy081';
789 comment: 'Report the fallback to the Marketplace query for fetching extensions';
790 extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension id' };
791 preRelease: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get pre-release version' };
792 compatible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get compatible version' };
793 errorCode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Error code or reason' };
794 }>('galleryService:fallbacktoquery', {
795 extension: extensionInfo.id,
796 preRelease: !!extensionInfo.preRelease,
797 compatible: !!options.compatible,
798 errorCode: galleryExtension
799 });
800 toQuery.push(extensionInfo);
801 }
802 } else {
803 result.push(galleryExtension);
804 }
805 } catch (error) {
806 if (error instanceof ExtensionGalleryError) {
807 switch (error.code) {
808 case ExtensionGalleryErrorCode.Offline:
809 case ExtensionGalleryErrorCode.Cancelled:
810 case ExtensionGalleryErrorCode.Timeout:
811 throw error;
812 }
813 }
814
815 // fallback to query
816 this.logService.error(`Error while getting the latest version for the extension ${extensionInfo.id}.`, getErrorMessage(error));
817 this.telemetryService.publicLog2<
818 {
819 extension: string;
820 preRelease: boolean;
821 compatible: boolean;
822 errorCode: string;
823 },
824 {
825 owner: 'sandy081';
826 comment: 'Report the fallback to the Marketplace query for fetching extensions';
827 extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension id' };
828 preRelease: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get pre-release version' };
829 compatible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get compatible version' };
830 errorCode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Error code or reason' };
831 }>('galleryService:fallbacktoquery', {
832 extension: extensionInfo.id,
833 preRelease: !!extensionInfo.preRelease,
834 compatible: !!options.compatible,
835 errorCode: error instanceof ExtensionGalleryError ? error.code : 'Unknown'
836 });
837 toQuery.push(extensionInfo);
838 }
839
840 }));
841
842 if (toQuery.length) {
843 const extensions = await this.getExtensionsUsingQueryApi(toQuery, options, extensionGalleryManifest, token);
844 result.push(...extensions);
845 }
846
847 return result;
848 }
850 > private async getLatestGalleryExtension(extensionInfo: IExtensionInfo, options: IExtensionQueryOptions, resourceApi: { uri: string; fallback?: string }, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IGalleryExtension | string> {
851 const rawGalleryExtension = await this.getLatestRawGalleryExtensionWithFallback(extensionInfo, resourceApi, token);
852
853 if (!rawGalleryExtension) {
854 return 'NOT_FOUND';
855 }
856
857 const allTargetPlatforms = getAllTargetPlatforms(rawGalleryExtension);
858 const rawGalleryExtensionVersion = await this.getValidRawGalleryExtensionVersionFromLatestVersions(rawGalleryExtension, rawGalleryExtension.versions, extensionInfo, options, allTargetPlatforms);
859
860 if (!rawGalleryExtensionVersion) {
861 if (extensionInfo.currentVersion) {
862 const latestVersion = rawGalleryExtension.versions.length > 0 ? rawGalleryExtension.versions[0].version : undefined;
863 if (latestVersion && semver.lt(latestVersion, extensionInfo.currentVersion)) {
864 return 'LATEST_IS_OUTDATED';
865 }
866 }
867 return 'NOT_COMPATIBLE';
868 }
869
870 return toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, extensionGalleryManifest, this.productService);
871 }
873 > private async getValidRawGalleryExtensionVersionFromLatestVersions(rawGalleryExtension: IRawGalleryExtension, latestVersions: IRawGalleryExtensionVersion[], extensionInfo: IExtensionInfo, options: IExtensionQueryOptions, allTargetPlatforms: TargetPlatform[]): Promise<IRawGalleryExtensionVersion | null> {
874 const targetPlatform = options.targetPlatform ?? CURRENT_TARGET_PLATFORM;
875 const latestExtensionVersionsForTargetPlatform = filterLatestExtensionVersionsForTargetPlatform(latestVersions, targetPlatform, allTargetPlatforms);
876
877 // First, find a valid version matching the requested type (pre-release or release)
878 const result = await this.getValidRawGalleryExtensionVersion(
879 rawGalleryExtension,
880 latestExtensionVersionsForTargetPlatform,
881 {
882 targetPlatform,
883 compatible: !!options.compatible,
884 productVersion: options.productVersion ?? {
885 version: this.productService.version,
886 date: this.productService.date
887 },
888 version: extensionInfo.preRelease ? VersionKind.Prerelease : VersionKind.Release
889 }, allTargetPlatforms);
890
891 // For release version requests, simply return the found release version
892 if (!extensionInfo.preRelease) {
893 return result;
894 }
895
896 // For pre-release version requests, we need to consider both pre-release and release versions
897 const prereleaseVersion = result;
898 const releaseVersion = await this.getValidRawGalleryExtensionVersion(
899 rawGalleryExtension,
900 latestExtensionVersionsForTargetPlatform,
901 {
902 targetPlatform,
903 compatible: !!options.compatible,
904 productVersion: options.productVersion ?? {
905 version: this.productService.version,
906 date: this.productService.date
907 },
908 version: VersionKind.Release
909 }, allTargetPlatforms);
910
911 // When both versions exist, return whichever has the higher version number
912 if (prereleaseVersion && releaseVersion) {
913 return semver.gt(releaseVersion.version, prereleaseVersion.version) ? releaseVersion : prereleaseVersion;
914 }
915
916 // Special handling for compatible version requests
917 if (options.compatible) {
918 // If we have a compatible release version, check if it's better than any pre-release
919 if (releaseVersion) {
920 // Check if there exists any pre-release version (ignoring compatibility)
921 const anyPrereleaseVersion = await this.getValidRawGalleryExtensionVersion(
922 rawGalleryExtension,
923 latestExtensionVersionsForTargetPlatform,
924 {
925 targetPlatform,
926 compatible: false,
927 productVersion: options.productVersion ?? {
928 version: this.productService.version,
929 date: this.productService.date
930 },
931 version: VersionKind.Prerelease
932 }, allTargetPlatforms);
933
934 // If no pre-release exists or the release version is greater, prefer the compatible release
935 // This ensures users get a stable compatible version when pre-releases aren't newer or compatible
936 if (!anyPrereleaseVersion || semver.gt(releaseVersion.version, anyPrereleaseVersion.version)) {
937 return releaseVersion;
938 }
939 }
940 return prereleaseVersion;
941 }
942
943 // Return pre-release if available, otherwise release, otherwise null
944 return prereleaseVersion ?? releaseVersion ?? null;
945 }
947 > async getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise<IGalleryExtension | null> {
948 if (isNotWebExtensionInWebTargetPlatform(extension.allTargetPlatforms, targetPlatform)) {
949 return null;
950 }
951 if (await this.isExtensionCompatible(extension, includePreRelease, targetPlatform)) {
952 return extension;
953 }
954 if (this.allowedExtensionsService.isAllowed({ id: extension.identifier.id, publisherDisplayName: extension.publisherDisplayName }) !== true) {
955 return null;
956 }
957 const result = await this.getExtensions([{
958 ...extension.identifier,
959 preRelease: includePreRelease,
960 hasPreRelease: extension.hasPreReleaseVersion,
961 }], {
962 compatible: true,
963 productVersion,
964 queryAllVersions: true,
965 targetPlatform,
966 }, CancellationToken.None);
967
968 return result[0] ?? null;
969 }
971 > async isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise<boolean> {
972 return this.isValidVersion(
973 {
974 id: extension.identifier.id,
975 version: extension.version,
976 isPreReleaseVersion: extension.properties.isPreReleaseVersion,
977 targetPlatform: extension.properties.targetPlatform,
978 manifestAsset: extension.assets.manifest,
979 engine: extension.properties.engine,
980 enabledApiProposals: extension.properties.enabledApiProposals
981 },
982 {
983 targetPlatform,
984 compatible: true,
985 productVersion,
986 version: includePreRelease ? VersionKind.Latest : VersionKind.Release
987 },
988 extension.publisherDisplayName,
989 extension.allTargetPlatforms
990 );
991 }
993 > private async isValidVersion(
994 extension: { id: string; version: string; isPreReleaseVersion: boolean; targetPlatform: TargetPlatform; manifestAsset: IGalleryExtensionAsset | null; engine: string | undefined; enabledApiProposals: string[] | undefined },
995 { targetPlatform, compatible, productVersion, version }: Omit<ExtensionVersionCriteria, 'targetPlatform'> & { targetPlatform: TargetPlatform | undefined },
996 publisherDisplayName: string,
997 allTargetPlatforms: TargetPlatform[]
998 ): Promise<boolean> {
999
1000 const hasPreRelease = hasPreReleaseForExtension(extension.id, this.productService);
1001 const excludeVersionRange = getExcludeVersionRangeForExtension(extension.id, this.productService);
1002
1003 if (extension.isPreReleaseVersion && hasPreRelease === false /* Skip if hasPreRelease is not defined for this extension */) {
1004 return false;
1005 }
1006
1007 if (excludeVersionRange && semver.satisfies(extension.version, excludeVersionRange)) {
1008 return false;
1009 }
1010
1011 // Specific version
1012 if (isString(version)) {
1013 if (extension.version !== version) {
1014 return false;
1015 }
1016 }
1017
1018 // Prerelease or release version kind
1019 else if (version === VersionKind.Release || version === VersionKind.Prerelease) {
1020 if (extension.isPreReleaseVersion !== (version === VersionKind.Prerelease)) {
1021 return false;
1022 }
1023 }
1024
1025 if (targetPlatform && !isTargetPlatformCompatible(extension.targetPlatform, allTargetPlatforms, targetPlatform)) {
1026 return false;
1027 }
1028
1029 if (compatible) {
1030 if (this.allowedExtensionsService.isAllowed({ id: extension.id, publisherDisplayName, version: extension.version, prerelease: extension.isPreReleaseVersion, targetPlatform: extension.targetPlatform }) !== true) {
1031 return false;
1032 }
1033
1034 if (!(await this.isEngineValid(extension.id, extension.version, extension.engine, extension.manifestAsset, productVersion))) {
1035 return false;
1036 }
1037 }
1038
1039 return true;
1040 }
1042 > private async isEngineValid(extensionId: string, version: string, engine: string | undefined, manifestAsset: IGalleryExtensionAsset | null, productVersion: IProductVersion): Promise<boolean> {
1043 if (!engine) {
1044 try {
1045 engine = await this.getEngine(extensionId, version, manifestAsset);
1046 } catch (error) {
1047 this.logService.error(`Error while getting the engine for the version ${version}.`, getErrorMessage(error));
1048 return false;
1049 }
1050 }
1051
1052 if (!engine) {
1053 this.logService.error(`Missing engine for the extension ${extensionId} with version ${version}`);
1054 return false;
1055 }
1056
1057 return isEngineValid(engine, productVersion.version, productVersion.date);
1058 }
1060 > private async getEngine(extensionId: string, version: string, manifestAsset: IGalleryExtensionAsset | null): Promise<string | undefined> {
1061 if (!manifestAsset) {
1062 this.logService.error(`Missing engine and manifest asset for the extension ${extensionId} with version ${version}`);
1063 return undefined;
1064 }
1065 try {
1066 type GalleryServiceEngineFallbackClassification = {
1067 owner: 'sandy081';
1068 comment: 'Fallback request when engine is not found in properties of an extension version';
1069 extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'extension name' };
1070 extensionVersion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'version' };
1071 };
1072 type GalleryServiceEngineFallbackEvent = {
1073 extension: string;
1074 extensionVersion: string;
1075 };
1076 this.telemetryService.publicLog2<GalleryServiceEngineFallbackEvent, GalleryServiceEngineFallbackClassification>('galleryService:engineFallback', { extension: extensionId, extensionVersion: version });
1077
1078 const headers = { 'Accept-Encoding': 'gzip' };
1079 const context = await this.getAsset(extensionId, manifestAsset, AssetType.Manifest, version, 'extensionGalleryService.engineVersion', { headers });
1080 const manifest = await asJson<IExtensionManifest>(context);
1081 if (!manifest) {
1082 this.logService.error(`Manifest was not found for the extension ${extensionId} with version ${version}`);
1083 return undefined;
1084 }
1085 return manifest.engines.vscode;
1086 } catch (error) {
1087 this.logService.error(`Error while getting the engine for the version ${version}.`, getErrorMessage(error));
1088 return undefined;
1089 }
1090 }
1092 > async query(options: IQueryOptions, token: CancellationToken): Promise<IPager<IGalleryExtension>> {
1093 const extensionGalleryManifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();
1094
1095 if (!extensionGalleryManifest) {
1096 throw new Error('No extension gallery service configured.');
1097 }
1098
1099 let text = options.text || '';
1100 const pageSize = options.pageSize ?? 50;
1101
1102 let query = new Query()
1103 .withPage(1, pageSize);
1104
1105 if (text) {
1106 // Use category filter instead of "category:themes"
1107 text = text.replace(/\bcategory:("([^"]*)"|([^"]\S*))(\s+|\b|$)/g, (_, quotedCategory, category) => {
1108 query = query.withFilter(FilterType.Category, category || quotedCategory);
1109 return '';
1110 });
1111
1112 // Use tag filter instead of "tag:debuggers"
1113 text = text.replace(/\btag:("([^"]*)"|([^"]\S*))(\s+|\b|$)/g, (_, quotedTag, tag) => {
1114 query = query.withFilter(FilterType.Tag, tag || quotedTag);
1115 return '';
1116 });
1117
1118 // Use featured filter
1119 text = text.replace(/\bfeatured(\s+|\b|$)/g, () => {
1120 query = query.withFilter(FilterType.Featured);
1121 return '';
1122 });
1123
1124 text = text.trim();
1125
1126 if (text) {
1127 text = text.length < 200 ? text : text.substring(0, 200);
1128 query = query.withFilter(FilterType.SearchText, text);
1129 }
1130
1131 if (extensionGalleryManifest.capabilities.extensionQuery.sorting?.some(c => c.name === SortBy.NoneOrRelevance)) {
1132 query = query.withSortBy(SortBy.NoneOrRelevance);
1133 }
1134 } else {
1135 if (extensionGalleryManifest.capabilities.extensionQuery.sorting?.some(c => c.name === SortBy.InstallCount)) {
1136 query = query.withSortBy(SortBy.InstallCount);
1137 }
1138 }
1139
1140 if (options.sortBy && extensionGalleryManifest.capabilities.extensionQuery.sorting?.some(c => c.name === options.sortBy)) {
1141 query = query.withSortBy(options.sortBy);
1142 }
1143
1144 if (typeof options.sortOrder === 'number') {
1145 query = query.withSortOrder(options.sortOrder);
1146 }
1147
1148 if (options.source) {
1149 query = query.withSource(options.source);
1150 }
1151
1152 const runQuery = async (query: Query, token: CancellationToken) => {
1153 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);
1154
1155 const result: IGalleryExtension[] = [];
1156 let defaultChatAgentExtension: IGalleryExtension | undefined;
1157 for (let index = 0; index < extensions.length; index++) {
1158 const extension = extensions[index];
1159 setTelemetry(extension, ((query.pageNumber - 1) * query.pageSize) + index, options.source);
1160 if (areSameExtensions(extension.identifier, { id: this.productService.defaultChatAgent.extensionId, })) {
1161 defaultChatAgentExtension = extension;
1162 } else {
1163 result.push(extension);
1164 }
1165 }
1166 if (defaultChatAgentExtension) {
1167 result.push(defaultChatAgentExtension);
1168 }
1169
1170 return { extensions: result, total };
1171 };
1172 const { extensions, total } = await runQuery(query, token);
1173 const getPage = async (pageIndex: number, ct: CancellationToken) => {
1174 if (ct.isCancellationRequested) {
1175 throw new CancellationError();
1176 }
1177 const { extensions } = await runQuery(query.withPage(pageIndex + 1), ct);
1178 return extensions;
1179 };
1180
1181 return { firstPage: extensions, total, pageSize: query.pageSize, getPage };
1182 }
1184 > private async queryGalleryExtensions(query: Query, criteria: ExtensionsCriteria, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<{ extensions: IGalleryExtension[]; total: number }> {
1185 const flags = query.flags;
1186
1187 /**
1188 * If both version flags (IncludeLatestVersionOnly and IncludeVersions) are included, then only include latest versions (IncludeLatestVersionOnly) flag.
1189 */
1190 if (query.flags.includes(Flag.IncludeLatestVersionOnly) && query.flags.includes(Flag.IncludeVersions)) {
1191 query = query.withFlags(...query.flags.filter(flag => flag !== Flag.IncludeVersions));
1192 }
1193
1194 /**
1195 * If version flags (IncludeLatestVersionOnly and IncludeVersions) are not included, default is to query for latest versions (IncludeLatestVersionOnly).
1196 */
1197 if (!query.flags.includes(Flag.IncludeLatestVersionOnly) && !query.flags.includes(Flag.IncludeVersions)) {
1198 query = query.withFlags(...query.flags, Flag.IncludeLatestVersionOnly);
1199 }
1200
1201 /**
1202 * 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.
1203 */
1204 if (criteria.versions?.length || criteria.isQueryForReleaseVersionFromPreReleaseVersion) {
1205 query = query.withFlags(...query.flags.filter(flag => flag !== Flag.IncludeLatestVersionOnly), Flag.IncludeVersions);
1206 }
1207
1208 /**
1209 * Add necessary extension flags
1210 */
1211 query = query.withFlags(...query.flags, Flag.IncludeAssetUri, Flag.IncludeCategoryAndTags, Flag.IncludeFiles, Flag.IncludeStatistics, Flag.IncludeVersionProperties);
1212 const { galleryExtensions: rawGalleryExtensions, total, context } = await this.queryRawGalleryExtensions(query, extensionGalleryManifest, token);
1213
1214 const hasAllVersions: boolean = !query.flags.includes(Flag.IncludeLatestVersionOnly);
1215 if (hasAllVersions) {
1216 const extensions: IGalleryExtension[] = [];
1217 for (const rawGalleryExtension of rawGalleryExtensions) {
1218 const allTargetPlatforms = getAllTargetPlatforms(rawGalleryExtension);
1219 const extensionIdentifier = { id: getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), uuid: rawGalleryExtension.extensionId };
1220 const includePreRelease = isBoolean(criteria.includePreRelease) ? criteria.includePreRelease : !!criteria.includePreRelease.find(extensionIdentifierWithPreRelease => areSameExtensions(extensionIdentifierWithPreRelease, extensionIdentifier))?.includePreRelease;
1221 const rawGalleryExtensionVersion = await this.getValidRawGalleryExtensionVersion(
1222 rawGalleryExtension,
1223 rawGalleryExtension.versions,
1224 {
1225 compatible: criteria.compatible,
1226 targetPlatform: criteria.targetPlatform,
1227 productVersion: criteria.productVersion,
1228 version: criteria.versions?.find(extensionIdentifierWithVersion => areSameExtensions(extensionIdentifierWithVersion, extensionIdentifier))?.version
1229 ?? (includePreRelease ? VersionKind.Latest : VersionKind.Release)
1230 },
1231 allTargetPlatforms
1232 );
1233 if (rawGalleryExtensionVersion) {
1234 extensions.push(toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, extensionGalleryManifest, this.productService, context));
1235 }
1236 }
1237 return { extensions, total };
1238 }
1239
1240 const result: [number, IGalleryExtension][] = [];
1241 const needAllVersions = new Map<string, number>();
1242 for (let index = 0; index < rawGalleryExtensions.length; index++) {
1243 const rawGalleryExtension = rawGalleryExtensions[index];
1244 const extensionIdentifier = { id: getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), uuid: rawGalleryExtension.extensionId };
1245 const includePreRelease = isBoolean(criteria.includePreRelease) ? criteria.includePreRelease : !!criteria.includePreRelease.find(extensionIdentifierWithPreRelease => areSameExtensions(extensionIdentifierWithPreRelease, extensionIdentifier))?.includePreRelease;
1246 const allTargetPlatforms = getAllTargetPlatforms(rawGalleryExtension);
1247 if (criteria.compatible) {
1248 // Skip looking for all versions if requested for a web-compatible extension and it is not a web extension.
1249 if (isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, criteria.targetPlatform)) {
1250 continue;
1251 }
1252 // Skip looking for all versions if the extension is not allowed.
1253 if (this.allowedExtensionsService.isAllowed({ id: extensionIdentifier.id, publisherDisplayName: rawGalleryExtension.publisher.displayName }) !== true) {
1254 continue;
1255 }
1256 }
1257 const rawGalleryExtensionVersion = await this.getValidRawGalleryExtensionVersion(
1258 rawGalleryExtension,
1259 rawGalleryExtension.versions,
1260 {
1261 compatible: criteria.compatible,
1262 targetPlatform: criteria.targetPlatform,
1263 productVersion: criteria.productVersion,
1264 version: criteria.versions?.find(extensionIdentifierWithVersion => areSameExtensions(extensionIdentifierWithVersion, extensionIdentifier))?.version
1265 ?? (includePreRelease ? VersionKind.Latest : VersionKind.Release)
1266 },
1267 allTargetPlatforms
1268 );
1269 const extension = rawGalleryExtensionVersion ? toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, extensionGalleryManifest, this.productService, context) : null;
1270 if (!extension
1271 /** Need all versions if the extension is a pre-release version but
1272 * - the query is to look for a release version or
1273 * - the extension has no release version
1274 * Get all versions to get or check the release version
1275 */
1276 || (extension.properties.isPreReleaseVersion && (!includePreRelease || !extension.hasReleaseVersion))
1277 /**
1278 * Need all versions if the extension is a release version with a different target platform than requested and also has a pre-release version
1279 * Because, this is a platform specific extension and can have a newer release version supporting this platform.
1280 * See https://github.com/microsoft/vscode/issues/139628
1281 */
1282 || (!extension.properties.isPreReleaseVersion && extension.properties.targetPlatform !== criteria.targetPlatform && extension.hasPreReleaseVersion)
1283 ) {
1284 needAllVersions.set(rawGalleryExtension.extensionId, index);
1285 } else {
1286 result.push([index, extension]);
1287 }
1288 }
1289
1290 if (needAllVersions.size) {
1291 const stopWatch = new StopWatch();
1292 const query = new Query()
1293 .withFlags(...flags.filter(flag => flag !== Flag.IncludeLatestVersionOnly), Flag.IncludeVersions)
1294 .withPage(1, needAllVersions.size)
1295 .withFilter(FilterType.ExtensionId, ...needAllVersions.keys());
1296 const { extensions } = await this.queryGalleryExtensions(query, criteria, extensionGalleryManifest, token);
1297 this.telemetryService.publicLog2<GalleryServiceAdditionalQueryEvent, GalleryServiceAdditionalQueryClassification>('galleryService:additionalQuery', {
1298 duration: stopWatch.elapsed(),
1299 count: needAllVersions.size
1300 });
1301 for (const extension of extensions) {
1302 const index = needAllVersions.get(extension.identifier.uuid)!;
1303 result.push([index, extension]);
1304 }
1305 }
1306
1307 return { extensions: result.sort((a, b) => a[0] - b[0]).map(([, extension]) => extension), total };
1308 }
1310 > private async getValidRawGalleryExtensionVersion(rawGalleryExtension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[], criteria: ExtensionVersionCriteria, allTargetPlatforms: TargetPlatform[]): Promise<IRawGalleryExtensionVersion | null> {
1311 const extensionIdentifier = { id: getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), uuid: rawGalleryExtension.extensionId };
1312 const rawGalleryExtensionVersions = sortExtensionVersions(versions, criteria.targetPlatform);
1313
1314 if (criteria.compatible && isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, criteria.targetPlatform)) {
1315 return null;
1316 }
1317
1318 const version = isString(criteria.version) ? criteria.version : undefined;
1319
1320 for (let index = 0; index < rawGalleryExtensionVersions.length; index++) {
1321 const rawGalleryExtensionVersion = rawGalleryExtensionVersions[index];
1322 if (criteria.compatible) {
1323 await this.setEngineIfNotExists(extensionIdentifier.id, rawGalleryExtensionVersion);
1324 }
1325 if (await this.isValidVersion(
1326 {
1327 id: extensionIdentifier.id,
1328 version: rawGalleryExtensionVersion.version,
1329 isPreReleaseVersion: isPreReleaseVersion(rawGalleryExtensionVersion),
1330 targetPlatform: getTargetPlatformForExtensionVersion(rawGalleryExtensionVersion),
1331 engine: getEngine(rawGalleryExtensionVersion),
1332 manifestAsset: getVersionAsset(rawGalleryExtensionVersion, AssetType.Manifest),
1333 enabledApiProposals: getEnabledApiProposals(rawGalleryExtensionVersion)
1334 },
1335 criteria,
1336 rawGalleryExtension.publisher.displayName,
1337 allTargetPlatforms)
1338 ) {
1339 return rawGalleryExtensionVersion;
1340 }
1341 if (version && rawGalleryExtensionVersion.version === version) {
1342 return null;
1343 }
1344 }
1345
1346 if (version || criteria.compatible) {
1347 return null;
1348 }
1349
1350 /**
1351 * Fallback: Return the latest version
1352 * This can happen when the extension does not have a release version or does not have a version compatible with the given target platform.
1353 */
1354 return rawGalleryExtension.versions[0];
1355 }
1357 > private async setEngineIfNotExists(extensionId: string, rawGalleryExtensionVersion: IRawGalleryExtensionVersion): Promise<void> {
1358 if (getEngine(rawGalleryExtensionVersion)) {
1359 return;
1360 }
1361
1362 try {
1363 const engine = await this.getEngine(extensionId, rawGalleryExtensionVersion.version, getVersionAsset(rawGalleryExtensionVersion, AssetType.Manifest));
1364 if (engine) {
1365 setEngine(rawGalleryExtensionVersion, engine);
1366 }
1367 } catch (error) {
1368 this.logService.error(`Error while getting the engine for the version ${rawGalleryExtensionVersion.version}.`, getErrorMessage(error));
1369 }
1370 }
1372 > private async queryRawGalleryExtensions(query: Query, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IRawGalleryExtensionsResult> {
1373 const extensionsQueryApi = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, ExtensionGalleryResourceType.ExtensionQueryService);
1374
1375 if (!extensionsQueryApi) {
1376 throw new Error('No extension gallery query service configured.');
1377 }
1378
1379 query = query
1380 /* Always exclude non validated extensions */
1381 .withFlags(...query.flags, Flag.ExcludeNonValidated)
1382 .withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code');
1383
1384 const unpublishedFlag = extensionGalleryManifest.capabilities.extensionQuery.flags?.find(f => f.name === Flag.Unpublished);
1385 /* Always exclude unpublished extensions */
1386 if (unpublishedFlag) {
1387 query = query.withFilter(FilterType.ExcludeWithFlags, String(unpublishedFlag.value));
1388 }
1389
1390 const data = JSON.stringify({
1391 filters: [
1392 {
1393 criteria: query.criteria.reduce<{ filterType: number; value?: string }[]>((criteria, c) => {
1394 const criterium = extensionGalleryManifest.capabilities.extensionQuery.filtering?.find(f => f.name === c.filterType);
1395 if (criterium) {
1396 criteria.push({
1397 filterType: criterium.value,
1398 value: c.value,
1399 });
1400 }
1401 return criteria;
1402 }, []),
1403 pageNumber: query.pageNumber,
1404 pageSize: query.pageSize,
1405 sortBy: extensionGalleryManifest.capabilities.extensionQuery.sorting?.find(s => s.name === query.sortBy)?.value,
1406 sortOrder: query.sortOrder,
1407 }
1408 ],
1409 assetTypes: query.assetTypes,
1410 flags: query.flags.reduce<number>((flags, flag) => {
1411 const flagValue = extensionGalleryManifest.capabilities.extensionQuery.flags?.find(f => f.name === flag);
1412 if (flagValue) {
1413 flags |= flagValue.value;
1414 }
1415 return flags;
1416 }, 0)
1417 });
1418
1419 const commonHeaders = await this.commonHeadersPromise;
1420 const headers = {
1421 ...commonHeaders,
1422 'Content-Type': 'application/json',
1423 'Accept': 'application/json;api-version=3.0-preview.1',
1424 'Accept-Encoding': 'gzip',
1425 'Content-Length': String(data.length),
1426 };
1427
1428 const stopWatch = new StopWatch();
1429 let context: IRequestContext | undefined, errorCode: ExtensionGalleryErrorCode | undefined, total: number = 0;
1430
1431 try {
1432 context = await this.requestService.request({
1433 type: 'POST',
1434 url: extensionsQueryApi,
1435 data,
1436 headers,
1437 callSite: 'extensionGalleryService.queryRawGalleryExtensions'
1438 }, token);
1439
1440 if (context.res.statusCode && context.res.statusCode >= 400 && context.res.statusCode < 500) {
1441 return { galleryExtensions: [], total };
1442 }
1443
1444 const result = await asJson<IRawGalleryQueryResult>(context);
1445 if (result) {
1446 const r = result.results[0];
1447 const galleryExtensions = r.extensions;
1448 const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0];
1449 total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0;
1450
1451 return {
1452 galleryExtensions,
1453 total,
1454 context: context.res.headers['activityid'] ? {
1455 [SEARCH_ACTIVITY_HEADER_NAME]: context.res.headers['activityid']
1456 } : {}
1457 };
1458 }
1459 return { galleryExtensions: [], total };
1460
1461 } catch (e) {
1462 if (isCancellationError(e)) {
1463 errorCode = ExtensionGalleryErrorCode.Cancelled;
1464 throw e;
1465 } else {
1466 const errorMessage = getErrorMessage(e);
1467 errorCode = isOfflineError(e)
1468 ? ExtensionGalleryErrorCode.Offline
1469 : errorMessage.startsWith('XHR timeout')
1470 ? ExtensionGalleryErrorCode.Timeout
1471 : ExtensionGalleryErrorCode.Failed;
1472 throw new ExtensionGalleryError(errorMessage, errorCode);
1473 }
1474 } finally {
1475 this.telemetryService.publicLog2<GalleryServiceQueryEvent, GalleryServiceQueryClassification>('galleryService:query', {
1476 filterTypes: query.criteria.map(criterium => criterium.filterType),
1477 flags: query.flags,
1478 sortBy: query.sortBy,
1479 sortOrder: String(query.sortOrder),
1480 pageNumber: String(query.pageNumber),
1481 source: query.source,
1482 searchTextLength: query.searchText.length,
1483 requestBodySize: String(data.length),
1484 duration: stopWatch.elapsed(),
1485 success: !!context && isSuccess(context),
1486 responseBodySize: context?.res.headers['Content-Length'],
1487 statusCode: context ? String(context.res.statusCode) : undefined,
1488 errorCode,
1489 count: String(total),
1490 server: this.getHeaderValue(context?.res.headers, SERVER_HEADER_NAME),
1491 activityId: this.getHeaderValue(context?.res.headers, ACTIVITY_HEADER_NAME),
1492 endToEndId: this.getHeaderValue(context?.res.headers, END_END_ID_HEADER_NAME),
1493 });
1494 }
1495 }
1497 > private getHeaderValue(headers: IHeaders | undefined, name: string): TelemetryTrustedValue<string> | undefined {
1498 const headerValue = headers?.[name.toLowerCase()];
1499 const value = Array.isArray(headerValue) ? headerValue[0] : headerValue;
1500 return value ? new TelemetryTrustedValue(value) : undefined;
1501 }
1503 > private async getLatestRawGalleryExtensionWithFallback(extensionInfo: IExtensionInfo, resourceApi: { uri: string; fallback?: string }, token: CancellationToken): Promise<IRawGalleryExtension | null> {
1504 const [publisher, name] = extensionInfo.id.split('.');
1505 let errorCode: string | undefined;
1506 try {
1507 const uri = URI.parse(format2(resourceApi.uri, { publisher, name }));
1508 return await this.getLatestRawGalleryExtension(extensionInfo.id, uri, token);
1509 } catch (error) {
1510 if (error instanceof ExtensionGalleryError) {
1511 errorCode = error.code;
1512 switch (error.code) {
1513 case ExtensionGalleryErrorCode.Offline:
1514 case ExtensionGalleryErrorCode.Cancelled:
1515 case ExtensionGalleryErrorCode.Timeout:
1516 case ExtensionGalleryErrorCode.ClientError:
1517 throw error;
1518 }
1519 } else {
1520 errorCode = 'Unknown';
1521 }
1522 if (!resourceApi.fallback) {
1523 throw error;
1524 }
1525 } finally {
1526 this.telemetryService.publicLog2<
1527 {
1528 extension: string;
1529 errorCode?: string;
1530 },
1531 {
1532 owner: 'sandy081';
1533 comment: 'Report fetching latest version of an extension';
1534 extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the extension' };
1535 errorCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error code in case of error' };
1536 }
1537 >('galleryService:getmarketplacelatest', {
1538 extension: extensionInfo.id,
1539 errorCode,
1540 });
1541 }
1542
1543 this.logService.error(`Error while getting the latest version for the extension ${extensionInfo.id} from ${resourceApi.uri}. Trying the fallback ${resourceApi.fallback}`, errorCode);
1544 try {
1545 const uri = URI.parse(format2(resourceApi.fallback, { publisher, name }));
1546 return await this.getLatestRawGalleryExtension(extensionInfo.id, uri, token);
1547 } catch (error) {
1548 errorCode = error instanceof ExtensionGalleryError ? error.code : 'Unknown';
1549 throw error;
1550 } finally {
1551 this.telemetryService.publicLog2<
1552 {
1553 extension: string;
1554 errorCode?: string;
1555 },
1556 {
1557 owner: 'sandy081';
1558 comment: 'Report the fallback to the unpkg service for getting latest extension';
1559 extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension id' };
1560 errorCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error code in case of error' };
1561 }>('galleryService:fallbacktounpkg', {
1562 extension: extensionInfo.id,
1563 errorCode,
1564 });
1565 }
1566 }
1568 > private async getLatestRawGalleryExtension(extension: string, uri: URI, token: CancellationToken): Promise<IRawGalleryExtension | null> {
1569 let context;
1570 let errorCode: string | undefined;
1571 const stopWatch = new StopWatch();
1572
1573 try {
1574 const commonHeaders = await this.commonHeadersPromise;
1575 const headers = {
1576 ...commonHeaders,
1577 'Content-Type': 'application/json',
1578 'Accept': 'application/json;api-version=7.2-preview',
1579 'Accept-Encoding': 'gzip',
1580 };
1581
1582 context = await this.requestService.request({
1583 type: 'GET',
1584 url: uri.toString(true),
1585 headers,
1586 timeout: this.getRequestTimeout(),
1587 callSite: 'extensionGalleryService.getLatestRawGalleryExtension'
1588 }, token);
1589
1590 if (context.res.statusCode === 404) {
1591 errorCode = 'NotFound';
1592 return null;
1593 }
1594
1595 if (context.res.statusCode && context.res.statusCode !== 200) {
1596 throw new Error('Unexpected HTTP response: ' + context.res.statusCode);
1597 }
1598
1599 const result = await asJson<IRawGalleryExtension>(context);
1600 if (!result) {
1601 errorCode = 'NoData';
1602 }
1603 return result;
1604 }
1605
1606 catch (error) {
1607 let galleryErrorCode: ExtensionGalleryErrorCode;
1608 if (isCancellationError(error)) {
1609 galleryErrorCode = ExtensionGalleryErrorCode.Cancelled;
1610 } else if (isOfflineError(error)) {
1611 galleryErrorCode = ExtensionGalleryErrorCode.Offline;
1612 } else if (getErrorMessage(error).startsWith('XHR timeout')) {
1613 galleryErrorCode = ExtensionGalleryErrorCode.Timeout;
1614 } else if (context && isClientError(context)) {
1615 galleryErrorCode = ExtensionGalleryErrorCode.ClientError;
1616 } else if (context && isServerError(context)) {
1617 galleryErrorCode = ExtensionGalleryErrorCode.ServerError;
1618 } else {
1619 galleryErrorCode = ExtensionGalleryErrorCode.Failed;
1620 }
1621 errorCode = galleryErrorCode;
1622 throw new ExtensionGalleryError(error, galleryErrorCode);
1623 }
1624
1625 finally {
1626 type GalleryServiceGetLatestEventClassification = {
1627 owner: 'sandy081';
1628 comment: 'Report the query to the Marketplace for fetching latest version of an extension';
1629 host: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The host of the end point' };
1630 extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the extension' };
1631 duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Duration in ms for the query' };
1632 errorCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error code in case of error' };
1633 statusCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The status code in case of error' };
1634 server?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The server of the end point' };
1635 activityId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The activity ID of the request' };
1636 endToEndId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The end-to-end ID of the request' };
1637 };
1638 type GalleryServiceGetLatestEvent = {
1639 extension: string;
1640 host: string;
1641 duration: number;
1642 errorCode?: string;
1643 statusCode?: string;
1644 server?: TelemetryTrustedValue<string>;
1645 activityId?: TelemetryTrustedValue<string>;
1646 endToEndId?: TelemetryTrustedValue<string>;
1647 };
1648 this.telemetryService.publicLog2<GalleryServiceGetLatestEvent, GalleryServiceGetLatestEventClassification>('galleryService:getLatest', {
1649 extension,
1650 host: uri.authority,
1651 duration: stopWatch.elapsed(),
1652 errorCode,
1653 statusCode: context?.res.statusCode && context?.res.statusCode !== 200 ? `${context.res.statusCode}` : undefined,
1654 server: this.getHeaderValue(context?.res.headers, SERVER_HEADER_NAME),
1655 activityId: this.getHeaderValue(context?.res.headers, ACTIVITY_HEADER_NAME),
1656 endToEndId: this.getHeaderValue(context?.res.headers, END_END_ID_HEADER_NAME),
1657 });
1658 }
1659 }
1661 > async reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise<void> {
1662 if (isWeb) {
1663 this.logService.info('ExtensionGalleryService#reportStatistic: Skipped in web');
1664 return undefined;
1665 }
1666
1667 const manifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();
1668 if (!manifest) {
1669 return undefined;
1670 }
1671
1672 const resource = getExtensionGalleryManifestResourceUri(manifest, ExtensionGalleryResourceType.ExtensionStatisticsUri);
1673 if (!resource) {
1674 return;
1675 }
1676 const url = format2(resource, { publisher, name, version, statTypeName: type });
1677
1678 const Accept = '*/*;api-version=4.0-preview.1';
1679 const commonHeaders = await this.commonHeadersPromise;
1680 const headers = { ...commonHeaders, Accept };
1681 try {
1682 await this.requestService.request({
1683 type: 'POST',
1684 url,
1685 headers,
1686 callSite: 'extensionGalleryService.reportStatistic'
1687 }, CancellationToken.None);
1688 } catch (error) { /* Ignore */ }
1689 }
1691 > async download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void> {
1692 this.logService.trace('ExtensionGalleryService#download', extension.identifier.id);
1693 const data = getGalleryExtensionTelemetryData(extension);
1694 const startTime = new Date().getTime();
1695
1696 const operationParam = operation === InstallOperation.Install ? 'install' : operation === InstallOperation.Update ? 'update' : '';
1697 const downloadAsset = operationParam ? {
1698 uri: `${extension.assets.download.uri}${URI.parse(extension.assets.download.uri).query ? '&' : '?'}${operationParam}=true`,
1699 fallbackUri: `${extension.assets.download.fallbackUri}${URI.parse(extension.assets.download.fallbackUri).query ? '&' : '?'}${operationParam}=true`
1700 } : extension.assets.download;
1701
1702 const activityId = extension.queryContext?.[SEARCH_ACTIVITY_HEADER_NAME];
1703 const headers: IHeaders | undefined = activityId && typeof activityId === 'string' ? { [SEARCH_ACTIVITY_HEADER_NAME]: activityId } : undefined;
1704 const context = await this.getAsset(extension.identifier.id, downloadAsset, AssetType.VSIX, extension.version, 'extensionGalleryService.download', headers ? { headers } : undefined);
1705
1706 try {
1707 await this.fileService.writeFile(location, context.stream);
1708 } catch (error) {
1709 try {
1710 await this.fileService.del(location);
1711 } catch (e) {
1712 /* ignore */
1713 this.logService.warn(`Error while deleting the file ${location.toString()}`, getErrorMessage(e));
1714 }
1715 throw new ExtensionGalleryError(getErrorMessage(error), ExtensionGalleryErrorCode.DownloadFailedWriting);
1716 }
1717
1718 /* __GDPR__
1719 "galleryService:downloadVSIX" : {
1720 "owner": "sandy081",
1721 "duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
1722 "${include}": [
1723 "${GalleryExtensionTelemetryData}"
1724 ]
1725 }
1726 */
1727 this.telemetryService.publicLog('galleryService:downloadVSIX', { ...data, duration: new Date().getTime() - startTime });
1728 }
1730 > async downloadSignatureArchive(extension: IGalleryExtension, location: URI): Promise<void> {
1731 if (!extension.assets.signature) {
1732 throw new Error('No signature asset found');
1733 }
1734
1735 this.logService.trace('ExtensionGalleryService#downloadSignatureArchive', extension.identifier.id);
1736
1737 const context = await this.getAsset(extension.identifier.id, extension.assets.signature, AssetType.Signature, extension.version, 'extensionGalleryService.signature');
1738 try {
1739 await this.fileService.writeFile(location, context.stream);
1740 } catch (error) {
1741 try {
1742 await this.fileService.del(location);
1743 } catch (e) {
1744 /* ignore */
1745 this.logService.warn(`Error while deleting the file ${location.toString()}`, getErrorMessage(e));
1746 }
1747 throw new ExtensionGalleryError(getErrorMessage(error), ExtensionGalleryErrorCode.DownloadFailedWriting);
1748 }
1749
1750 }
1752 > async getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string> {
1753 if (extension.assets.readme) {
1754 const context = await this.getAsset(extension.identifier.id, extension.assets.readme, AssetType.Details, extension.version, 'extensionGalleryService.readme', {}, token);
1755 const content = await asTextOrError(context);
1756 return content || '';
1757 }
1758 return '';
1759 }
1761 > async getManifest(extension: IGalleryExtension, token: CancellationToken): Promise<IExtensionManifest | null> {
1762 if (extension.assets.manifest) {
1763 const context = await this.getAsset(extension.identifier.id, extension.assets.manifest, AssetType.Manifest, extension.version, 'extensionGalleryService.manifest', {}, token);
1764 const text = await asTextOrError(context);
1765 return text ? JSON.parse(text) : null;
1766 }
1767 return null;
1768 }
1770 > async getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation | null> {
1771 const asset = extension.assets.coreTranslations.filter(t => t[0] === languageId.toUpperCase())[0];
1772 if (asset) {
1773 const context = await this.getAsset(extension.identifier.id, asset[1], asset[0], extension.version, 'extensionGalleryService.coreTranslation');
1774 const text = await asTextOrError(context);
1775 return text ? JSON.parse(text) : null;
1776 }
1777 return null;
1778 }
1780 > async getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise<string> {
1781 if (extension.assets.changelog) {
1782 const context = await this.getAsset(extension.identifier.id, extension.assets.changelog, AssetType.Changelog, extension.version, 'extensionGalleryService.changelog', {}, token);
1783 const content = await asTextOrError(context);
1784 return content || '';
1785 }
1786 return '';
1787 }
1789 > async getAllVersions(extensionIdentifier: IExtensionIdentifier): Promise<IGalleryExtensionVersion[]> {
1790 return this.getVersions(extensionIdentifier);
1791 }
1793 > async getAllCompatibleVersions(extensionIdentifier: IExtensionIdentifier, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtensionVersion[]> {
1794 return this.getVersions(extensionIdentifier, { version: includePreRelease ? VersionKind.Latest : VersionKind.Release, targetPlatform });
1795 }
1797 > private async getVersions(extensionIdentifier: IExtensionIdentifier, onlyCompatible?: { version: VersionKind; targetPlatform: TargetPlatform }): Promise<IGalleryExtensionVersion[]> {
1798 const extensionGalleryManifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();
1799 if (!extensionGalleryManifest) {
1800 throw new Error('No extension gallery service configured.');
1801 }
1802
1803 let query = new Query()
1804 .withFlags(Flag.IncludeVersions, Flag.IncludeCategoryAndTags, Flag.IncludeFiles, Flag.IncludeVersionProperties)
1805 .withPage(1, 1);
1806
1807 if (extensionIdentifier.uuid) {
1808 query = query.withFilter(FilterType.ExtensionId, extensionIdentifier.uuid);
1809 } else {
1810 query = query.withFilter(FilterType.ExtensionName, extensionIdentifier.id);
1811 }
1812
1813 const { galleryExtensions } = await this.queryRawGalleryExtensions(query, extensionGalleryManifest, CancellationToken.None);
1814 if (!galleryExtensions.length) {
1815 return [];
1816 }
1817
1818 const allTargetPlatforms = getAllTargetPlatforms(galleryExtensions[0]);
1819 if (onlyCompatible && isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, onlyCompatible.targetPlatform)) {
1820 return [];
1821 }
1822
1823 const versions: IRawGalleryExtensionVersion[] = [];
1824 const productVersion = { version: this.productService.version, date: this.productService.date };
1825 await Promise.all(galleryExtensions[0].versions.map(async (version) => {
1826 try {
1827 if (
1828 (await this.isValidVersion(
1829 {
1830 id: extensionIdentifier.id,
1831 version: version.version,
1832 isPreReleaseVersion: isPreReleaseVersion(version),
1833 targetPlatform: getTargetPlatformForExtensionVersion(version),
1834 engine: getEngine(version),
1835 manifestAsset: getVersionAsset(version, AssetType.Manifest),
1836 enabledApiProposals: getEnabledApiProposals(version)
1837 },
1838 {
1839 compatible: !!onlyCompatible,
1840 productVersion,
1841 targetPlatform: onlyCompatible?.targetPlatform,
1842 version: onlyCompatible?.version ?? version.version
1843 },
1844 galleryExtensions[0].publisher.displayName,
1845 allTargetPlatforms))
1846 ) {
1847 versions.push(version);
1848 }
1849 } catch (error) { /* Ignore error and skip version */ }
1850 }));
1851
1852 const result: IGalleryExtensionVersion[] = [];
1853 const seen = new Map<string, number>();
1854 for (const version of sortExtensionVersions(versions, onlyCompatible?.targetPlatform ?? CURRENT_TARGET_PLATFORM)) {
1855 const index = seen.get(version.version);
1856 const existing = index !== undefined ? result[index] : undefined;
1857 const targetPlatform = getTargetPlatformForExtensionVersion(version);
1858 if (!existing) {
1859 seen.set(version.version, result.length);
1860 result.push({ version: version.version, date: version.lastUpdated, isPreReleaseVersion: isPreReleaseVersion(version), targetPlatforms: [targetPlatform] });
1861 } else {
1862 existing.targetPlatforms.push(targetPlatform);
1863 }
1864 }
1865
1866 return result;
1867 }
1869 > private async getAsset(extension: string, asset: IGalleryExtensionAsset, assetType: string, extensionVersion: string, callSite: string, options: Omit<IRequestOptions, 'callSite'> = {}, token: CancellationToken = CancellationToken.None): Promise<IRequestContext> {
1870 const commonHeaders = await this.commonHeadersPromise;
1871 const baseOptions = { type: 'GET' };
1872 const headers = { ...commonHeaders, ...(options.headers || {}) };
1873 options = { ...options, ...baseOptions, headers };
1874
1875 const url = asset.uri;
1876 const fallbackUrl = asset.fallbackUri;
1877 const firstOptions = { ...options, url, timeout: this.getRequestTimeout(), callSite };
1878
1879 let context;
1880 try {
1881 context = await this.requestService.request(firstOptions, token);
1882 if (context.res.statusCode === 200) {
1883 return context;
1884 }
1885 const message = await asTextOrError(context);
1886 throw new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`);
1887 } catch (err) {
1888 if (isCancellationError(err)) {
1889 throw err;
1890 }
1891
1892 const message = getErrorMessage(err);
1893 type GalleryServiceCDNFallbackClassification = {
1894 owner: 'sandy081';
1895 comment: 'Fallback request information when the primary asset request to CDN fails';
1896 extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'extension name' };
1897 assetType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'asset that failed' };
1898 message: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'error message' };
1899 extensionVersion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'version' };
1900 readonly server?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'server that handled the query' };
1901 readonly endToEndId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'end to end operation id' };
1902 readonly activityId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'activity id' };
1903 };
1904 type GalleryServiceCDNFallbackEvent = {
1905 extension: string;
1906 assetType: string;
1907 message: string;
1908 extensionVersion: string;
1909 server?: TelemetryTrustedValue<string>;
1910 endToEndId?: TelemetryTrustedValue<string>;
1911 activityId?: TelemetryTrustedValue<string>;
1912 };
1913 this.telemetryService.publicLog2<GalleryServiceCDNFallbackEvent, GalleryServiceCDNFallbackClassification>('galleryService:cdnFallback', {
1914 extension,
1915 assetType,
1916 message,
1917 extensionVersion,
1918 server: this.getHeaderValue(context?.res.headers, SERVER_HEADER_NAME),
1919 activityId: this.getHeaderValue(context?.res.headers, ACTIVITY_HEADER_NAME),
1920 endToEndId: this.getHeaderValue(context?.res.headers, END_END_ID_HEADER_NAME),
1921 });
1922
1923 const fallbackOptions = { ...options, url: fallbackUrl, timeout: this.getRequestTimeout(), callSite: `${callSite}.fallback` };
1924 return this.requestService.request(fallbackOptions, token);
1925 }
1926 }
1928 > async getExtensionsControlManifest(): Promise<IExtensionsControlManifest> {
1929 const manifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();
1930 if (!manifest) {
1931 throw new Error('No extension gallery service configured.');
1932 }
1933
1934
1935 if (!this.extensionsControlUrl) {
1936 return { malicious: [], deprecated: {}, search: [], autoUpdate: {} };
1937 }
1938
1939 const context = await this.requestService.request({
1940 type: 'GET',
1941 url: this.extensionsControlUrl,
1942 timeout: this.getRequestTimeout(),
1943 callSite: 'extensionGalleryService.getExtensionsControlManifest'
1944 }, CancellationToken.None);
1945
1946 if (context.res.statusCode !== 200) {
1947 throw new Error('Could not get extensions report.');
1948 }
1949
1950 const result = await asJson<IRawExtensionsControlManifest>(context);
1951 const malicious: Array<MaliciousExtensionInfo> = [];
1952 const deprecated: IStringDictionary<IDeprecationInfo> = {};
1953 const search: ISearchPrefferedResults[] = [];
1954 const autoUpdate: IStringDictionary<string> = result?.autoUpdate ?? {};
1955 if (result) {
1956 for (const id of result.malicious) {
1957 if (!isString(id)) {
1958 continue;
1959 }
1960 const publisherOrExtension = EXTENSION_IDENTIFIER_REGEX.test(id) ? { id } : id;
1961 malicious.push({ extensionOrPublisher: publisherOrExtension, learnMoreLink: result.learnMoreLinks?.[id] });
1962 }
1963 if (result.migrateToPreRelease) {
1964 for (const [unsupportedPreReleaseExtensionId, preReleaseExtensionInfo] of Object.entries(result.migrateToPreRelease)) {
1965 if (!preReleaseExtensionInfo.engine || isEngineValid(preReleaseExtensionInfo.engine, this.productService.version, this.productService.date)) {
1966 deprecated[unsupportedPreReleaseExtensionId.toLowerCase()] = {
1967 disallowInstall: true,
1968 extension: {
1969 id: preReleaseExtensionInfo.id,
1970 displayName: preReleaseExtensionInfo.displayName,
1971 autoMigrate: { storage: !!preReleaseExtensionInfo.migrateStorage },
1972 preRelease: true
1973 }
1974 };
1975 }
1976 }
1977 }
1978 if (result.deprecated) {
1979 for (const [deprecatedExtensionId, deprecationInfo] of Object.entries(result.deprecated)) {
1980 if (deprecationInfo) {
1981 deprecated[deprecatedExtensionId.toLowerCase()] = isBoolean(deprecationInfo) ? {} : deprecationInfo;
1982 }
1983 }
1984 }
1985 if (result.search) {
1986 for (const s of result.search) {
1987 search.push(s);
1988 }
1989 }
1990 }
1991
1992 deprecated[this.productService.defaultChatAgent.extensionId.toLowerCase()] = {
1993 disallowInstall: true,
1994 extension: {
1995 id: this.productService.defaultChatAgent.chatExtensionId,
1996 displayName: 'GitHub Copilot Chat',
1997 autoMigrate: { storage: false, donotDisable: true },
1998 preRelease: this.productService.quality !== 'stable'
1999 }
2000 };
2001
2002 return { malicious, deprecated, search, autoUpdate };
2003 }
2005 > private getRequestTimeout(): number {
2006 const configuredTimeout = this.configurationService.getValue<number>(ExtensionRequestsTimeoutConfigKey);
2007 return isNumber(configuredTimeout) && configuredTimeout >= 0 ? configuredTimeout : 60_000;
2008 }
2010 > }
2011 >
2012 > export class ExtensionGalleryService extends AbstractExtensionGalleryService {
2013 >
2014 > constructor(
2015 @IStorageService storageService: IStorageService,
2016 @IRequestService requestService: IRequestService,
2017 @ILogService logService: ILogService,
2018 @IEnvironmentService environmentService: IEnvironmentService,
2019 @ITelemetryService telemetryService: ITelemetryService,
2020 @IFileService fileService: IFileService,
2021 @IProductService productService: IProductService,
2022 @IConfigurationService configurationService: IConfigurationService,
2023 @IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService,
2024 @IExtensionGalleryManifestService extensionGalleryManifestService: IExtensionGalleryManifestService,
2025 ) {
2026 super(storageService, requestService, logService, environmentService, telemetryService, fileService, productService, configurationService, allowedExtensionsService, extensionGalleryManifestService);
2027 }
2029 >
2030 > export class ExtensionGalleryServiceWithNoStorageService extends AbstractExtensionGalleryService {
2031 >
2032 > constructor(
2033 @IRequestService requestService: IRequestService,
2034 @ILogService logService: ILogService,
2035 @IEnvironmentService environmentService: IEnvironmentService,
2036 @ITelemetryService telemetryService: ITelemetryService,
2037 @IFileService fileService: IFileService,
2038 @IProductService productService: IProductService,
2039 @IConfigurationService configurationService: IConfigurationService,
2040 @IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService,
2041 @IExtensionGalleryManifestService extensionGalleryManifestService: IExtensionGalleryManifestService,
2042 ) {
2043 super(undefined, requestService, logService, environmentService, telemetryService, fileService, productService, configurationService, allowedExtensionsService, extensionGalleryManifestService);
2044 }