Path: blob/main/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts
3294 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { isWeb } from '../../../base/common/platform.js';6import { format2 } from '../../../base/common/strings.js';7import { URI } from '../../../base/common/uri.js';8import { IConfigurationService } from '../../configuration/common/configuration.js';9import { IEnvironmentService } from '../../environment/common/environment.js';10import { IFileService } from '../../files/common/files.js';11import { createDecorator } from '../../instantiation/common/instantiation.js';12import { IProductService } from '../../product/common/productService.js';13import { getServiceMachineId } from '../../externalServices/common/serviceMachineId.js';14import { IStorageService } from '../../storage/common/storage.js';15import { TelemetryLevel } from '../../telemetry/common/telemetry.js';16import { getTelemetryLevel, supportsTelemetry } from '../../telemetry/common/telemetryUtils.js';17import { RemoteAuthorities } from '../../../base/common/network.js';18import { TargetPlatform } from '../../extensions/common/extensions.js';19import { ExtensionGalleryResourceType, getExtensionGalleryManifestResourceUri, IExtensionGalleryManifest, IExtensionGalleryManifestService } from '../../extensionManagement/common/extensionGalleryManifest.js';20import { ILogService } from '../../log/common/log.js';21import { Disposable } from '../../../base/common/lifecycle.js';2223const WEB_EXTENSION_RESOURCE_END_POINT_SEGMENT = '/web-extension-resource/';2425export const IExtensionResourceLoaderService = createDecorator<IExtensionResourceLoaderService>('extensionResourceLoaderService');2627/**28* A service useful for reading resources from within extensions.29*/30export interface IExtensionResourceLoaderService {31readonly _serviceBrand: undefined;3233/**34* Read a certain resource within an extension.35*/36readExtensionResource(uri: URI): Promise<string>;3738/**39* Returns whether the gallery provides extension resources.40*/41supportsExtensionGalleryResources(): Promise<boolean>;4243/**44* Return true if the given URI is a extension gallery resource.45*/46isExtensionGalleryResource(uri: URI): Promise<boolean>;4748/**49* Computes the URL of a extension gallery resource. Returns `undefined` if gallery does not provide extension resources.50*/51getExtensionGalleryResourceURL(galleryExtension: { publisher: string; name: string; version: string; targetPlatform?: TargetPlatform }, path?: string): Promise<URI | undefined>;52}5354export function migratePlatformSpecificExtensionGalleryResourceURL(resource: URI, targetPlatform: TargetPlatform): URI | undefined {55if (resource.query !== `target=${targetPlatform}`) {56return undefined;57}58const paths = resource.path.split('/');59if (!paths[3]) {60return undefined;61}62paths[3] = `${paths[3]}+${targetPlatform}`;63return resource.with({ query: null, path: paths.join('/') });64}6566export abstract class AbstractExtensionResourceLoaderService extends Disposable implements IExtensionResourceLoaderService {6768readonly _serviceBrand: undefined;6970private readonly _initPromise: Promise<void>;7172private _extensionGalleryResourceUrlTemplate: string | undefined;73private _extensionGalleryAuthority: string | undefined;7475constructor(76protected readonly _fileService: IFileService,77private readonly _storageService: IStorageService,78private readonly _productService: IProductService,79private readonly _environmentService: IEnvironmentService,80private readonly _configurationService: IConfigurationService,81private readonly _extensionGalleryManifestService: IExtensionGalleryManifestService,82protected readonly _logService: ILogService,83) {84super();85this._initPromise = this._init();86}8788private async _init(): Promise<void> {89try {90const manifest = await this._extensionGalleryManifestService.getExtensionGalleryManifest();91this.resolve(manifest);92this._register(this._extensionGalleryManifestService.onDidChangeExtensionGalleryManifest(() => this.resolve(manifest)));93} catch (error) {94this._logService.error(error);95}96}9798private resolve(manifest: IExtensionGalleryManifest | null): void {99this._extensionGalleryResourceUrlTemplate = manifest ? getExtensionGalleryManifestResourceUri(manifest, ExtensionGalleryResourceType.ExtensionResourceUri) : undefined;100this._extensionGalleryAuthority = this._extensionGalleryResourceUrlTemplate ? this._getExtensionGalleryAuthority(URI.parse(this._extensionGalleryResourceUrlTemplate)) : undefined;101}102103public async supportsExtensionGalleryResources(): Promise<boolean> {104await this._initPromise;105return this._extensionGalleryResourceUrlTemplate !== undefined;106}107108public async getExtensionGalleryResourceURL({ publisher, name, version, targetPlatform }: { publisher: string; name: string; version: string; targetPlatform?: TargetPlatform }, path?: string): Promise<URI | undefined> {109await this._initPromise;110if (this._extensionGalleryResourceUrlTemplate) {111const uri = URI.parse(format2(this._extensionGalleryResourceUrlTemplate, {112publisher,113name,114version: targetPlatform !== undefined115&& targetPlatform !== TargetPlatform.UNDEFINED116&& targetPlatform !== TargetPlatform.UNKNOWN117&& targetPlatform !== TargetPlatform.UNIVERSAL118? `${version}+${targetPlatform}`119: version,120path: 'extension'121}));122return this._isWebExtensionResourceEndPoint(uri) ? uri.with({ scheme: RemoteAuthorities.getPreferredWebSchema() }) : uri;123}124return undefined;125}126127public abstract readExtensionResource(uri: URI): Promise<string>;128129async isExtensionGalleryResource(uri: URI): Promise<boolean> {130await this._initPromise;131return !!this._extensionGalleryAuthority && this._extensionGalleryAuthority === this._getExtensionGalleryAuthority(uri);132}133134protected async getExtensionGalleryRequestHeaders(): Promise<Record<string, string>> {135const headers: Record<string, string> = {136'X-Client-Name': `${this._productService.applicationName}${isWeb ? '-web' : ''}`,137'X-Client-Version': this._productService.version138};139if (supportsTelemetry(this._productService, this._environmentService) && getTelemetryLevel(this._configurationService) === TelemetryLevel.USAGE) {140headers['X-Machine-Id'] = await this._getServiceMachineId();141}142if (this._productService.commit) {143headers['X-Client-Commit'] = this._productService.commit;144}145return headers;146}147148private _serviceMachineIdPromise: Promise<string> | undefined;149private _getServiceMachineId(): Promise<string> {150if (!this._serviceMachineIdPromise) {151this._serviceMachineIdPromise = getServiceMachineId(this._environmentService, this._fileService, this._storageService);152}153return this._serviceMachineIdPromise;154}155156private _getExtensionGalleryAuthority(uri: URI): string | undefined {157if (this._isWebExtensionResourceEndPoint(uri)) {158return uri.authority;159}160const index = uri.authority.indexOf('.');161return index !== -1 ? uri.authority.substring(index + 1) : undefined;162}163164protected _isWebExtensionResourceEndPoint(uri: URI): boolean {165const uriPath = uri.path, serverRootPath = RemoteAuthorities.getServerRootPath();166// test if the path starts with the server root path followed by the web extension resource end point segment167return uriPath.startsWith(serverRootPath) && uriPath.startsWith(WEB_EXTENSION_RESOURCE_END_POINT_SEGMENT, serverRootPath.length);168}169170}171172173