Path: blob/main/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts
5251 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 { Emitter, Event } from '../../../base/common/event.js';6import { cloneAndChange } from '../../../base/common/objects.js';7import { URI, UriComponents } from '../../../base/common/uri.js';8import { DefaultURITransformer, IURITransformer, transformAndReviveIncomingURIs } from '../../../base/common/uriIpc.js';9import { IChannel, IServerChannel } from '../../../base/parts/ipc/common/ipc.js';10import {11IExtensionIdentifier, IExtensionTipsService, IGalleryExtension, ILocalExtension, IExtensionsControlManifest, InstallOptions,12UninstallOptions, Metadata, IExtensionManagementService, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult,13UninstallExtensionEvent, InstallOperation, InstallExtensionInfo, IProductVersion, DidUpdateExtensionMetadata, UninstallExtensionInfo,14IAllowedExtensionsService15} from './extensionManagement.js';16import { ExtensionType, IExtensionManifest, TargetPlatform } from '../../extensions/common/extensions.js';17import { IProductService } from '../../product/common/productService.js';18import { CommontExtensionManagementService } from './abstractExtensionManagementService.js';19import { language } from '../../../base/common/platform.js';20import { RemoteAgentConnectionContext } from '../../remote/common/remoteAgentEnvironment.js';2122function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI;23function transformIncomingURI(uri: UriComponents | undefined, transformer: IURITransformer | null): URI | undefined;24function transformIncomingURI(uri: UriComponents | undefined, transformer: IURITransformer | null): URI | undefined {25return uri ? URI.revive(transformer ? transformer.transformIncoming(uri) : uri) : undefined;26}2728function transformOutgoingURI(uri: URI, transformer: IURITransformer | null): URI {29return transformer ? transformer.transformOutgoingURI(uri) : uri;30}3132function transformIncomingExtension(extension: ILocalExtension, transformer: IURITransformer | null): ILocalExtension {33transformer = transformer ? transformer : DefaultURITransformer;34const manifest = extension.manifest;35const transformed = transformAndReviveIncomingURIs({ ...extension, ...{ manifest: undefined } }, transformer);36return { ...transformed, ...{ manifest } };37}3839function transformIncomingOptions<O extends { profileLocation?: UriComponents }>(options: O | undefined, transformer: IURITransformer | null): O | undefined {40return options?.profileLocation ? transformAndReviveIncomingURIs(options, transformer ?? DefaultURITransformer) : options;41}4243function transformOutgoingExtension(extension: ILocalExtension, transformer: IURITransformer | null): ILocalExtension {44return transformer ? cloneAndChange(extension, value => value instanceof URI ? transformer.transformOutgoingURI(value) : undefined) : extension;45}4647export class ExtensionManagementChannel<TContext = RemoteAgentConnectionContext | string> implements IServerChannel<TContext> {4849readonly onInstallExtension: Event<InstallExtensionEvent>;50readonly onDidInstallExtensions: Event<readonly InstallExtensionResult[]>;51readonly onUninstallExtension: Event<UninstallExtensionEvent>;52readonly onDidUninstallExtension: Event<DidUninstallExtensionEvent>;53readonly onDidUpdateExtensionMetadata: Event<DidUpdateExtensionMetadata>;5455constructor(private service: IExtensionManagementService, private getUriTransformer: (requestContext: TContext) => IURITransformer | null) {56this.onInstallExtension = Event.buffer(service.onInstallExtension, true);57this.onDidInstallExtensions = Event.buffer(service.onDidInstallExtensions, true);58this.onUninstallExtension = Event.buffer(service.onUninstallExtension, true);59this.onDidUninstallExtension = Event.buffer(service.onDidUninstallExtension, true);60this.onDidUpdateExtensionMetadata = Event.buffer(service.onDidUpdateExtensionMetadata, true);61}6263// eslint-disable-next-line @typescript-eslint/no-explicit-any64listen(context: any, event: string): Event<any> {65const uriTransformer = this.getUriTransformer(context);66switch (event) {67case 'onInstallExtension': {68return Event.map<InstallExtensionEvent, InstallExtensionEvent>(this.onInstallExtension, e => {69return {70...e,71profileLocation: e.profileLocation ? transformOutgoingURI(e.profileLocation, uriTransformer) : e.profileLocation72};73});74}75case 'onDidInstallExtensions': {76return Event.map<readonly InstallExtensionResult[], readonly InstallExtensionResult[]>(this.onDidInstallExtensions, results =>77results.map(i => ({78...i,79local: i.local ? transformOutgoingExtension(i.local, uriTransformer) : i.local,80profileLocation: i.profileLocation ? transformOutgoingURI(i.profileLocation, uriTransformer) : i.profileLocation81})));82}83case 'onUninstallExtension': {84return Event.map<UninstallExtensionEvent, UninstallExtensionEvent>(this.onUninstallExtension, e => {85return {86...e,87profileLocation: e.profileLocation ? transformOutgoingURI(e.profileLocation, uriTransformer) : e.profileLocation88};89});90}91case 'onDidUninstallExtension': {92return Event.map<DidUninstallExtensionEvent, DidUninstallExtensionEvent>(this.onDidUninstallExtension, e => {93return {94...e,95profileLocation: e.profileLocation ? transformOutgoingURI(e.profileLocation, uriTransformer) : e.profileLocation96};97});98}99case 'onDidUpdateExtensionMetadata': {100return Event.map<DidUpdateExtensionMetadata, DidUpdateExtensionMetadata>(this.onDidUpdateExtensionMetadata, e => {101return {102local: transformOutgoingExtension(e.local, uriTransformer),103profileLocation: transformOutgoingURI(e.profileLocation, uriTransformer)104};105});106}107}108109throw new Error('Invalid listen');110}111112// eslint-disable-next-line @typescript-eslint/no-explicit-any113async call(context: any, command: string, args?: any): Promise<any> {114const uriTransformer: IURITransformer | null = this.getUriTransformer(context);115switch (command) {116case 'zip': {117const extension = transformIncomingExtension(args[0], uriTransformer);118const uri = await this.service.zip(extension);119return transformOutgoingURI(uri, uriTransformer);120}121case 'install': {122return this.service.install(transformIncomingURI(args[0], uriTransformer), transformIncomingOptions(args[1], uriTransformer));123}124case 'installFromLocation': {125return this.service.installFromLocation(transformIncomingURI(args[0], uriTransformer), transformIncomingURI(args[1], uriTransformer));126}127case 'installExtensionsFromProfile': {128return this.service.installExtensionsFromProfile(args[0], transformIncomingURI(args[1], uriTransformer), transformIncomingURI(args[2], uriTransformer));129}130case 'getManifest': {131return this.service.getManifest(transformIncomingURI(args[0], uriTransformer));132}133case 'getTargetPlatform': {134return this.service.getTargetPlatform();135}136case 'installFromGallery': {137return this.service.installFromGallery(args[0], transformIncomingOptions(args[1], uriTransformer));138}139case 'installGalleryExtensions': {140const arg: InstallExtensionInfo[] = args[0];141return this.service.installGalleryExtensions(arg.map(({ extension, options }) => ({ extension, options: transformIncomingOptions(options, uriTransformer) ?? {} })));142}143case 'uninstall': {144return this.service.uninstall(transformIncomingExtension(args[0], uriTransformer), transformIncomingOptions(args[1], uriTransformer));145}146case 'uninstallExtensions': {147const arg: UninstallExtensionInfo[] = args[0];148return this.service.uninstallExtensions(arg.map(({ extension, options }) => ({ extension: transformIncomingExtension(extension, uriTransformer), options: transformIncomingOptions(options, uriTransformer) })));149}150case 'getInstalled': {151const extensions = await this.service.getInstalled(args[0], transformIncomingURI(args[1], uriTransformer), args[2], args[3]);152return extensions.map(e => transformOutgoingExtension(e, uriTransformer));153}154case 'toggleApplicationScope': {155const extension = await this.service.toggleApplicationScope(transformIncomingExtension(args[0], uriTransformer), transformIncomingURI(args[1], uriTransformer));156return transformOutgoingExtension(extension, uriTransformer);157}158case 'copyExtensions': {159return this.service.copyExtensions(transformIncomingURI(args[0], uriTransformer), transformIncomingURI(args[1], uriTransformer));160}161case 'updateMetadata': {162const e = await this.service.updateMetadata(transformIncomingExtension(args[0], uriTransformer), args[1], transformIncomingURI(args[2], uriTransformer));163return transformOutgoingExtension(e, uriTransformer);164}165case 'resetPinnedStateForAllUserExtensions': {166return this.service.resetPinnedStateForAllUserExtensions(args[0]);167}168case 'getExtensionsControlManifest': {169return this.service.getExtensionsControlManifest();170}171case 'download': {172return this.service.download(args[0], args[1], args[2]);173}174case 'cleanUp': {175return this.service.cleanUp();176}177}178179throw new Error('Invalid call');180}181}182183export interface ExtensionEventResult {184readonly profileLocation: URI;185readonly local?: ILocalExtension;186readonly applicationScoped?: boolean;187}188189export class ExtensionManagementChannelClient extends CommontExtensionManagementService implements IExtensionManagementService {190191declare readonly _serviceBrand: undefined;192193protected readonly _onInstallExtension = this._register(new Emitter<InstallExtensionEvent>());194get onInstallExtension() { return this._onInstallExtension.event; }195196protected readonly _onDidInstallExtensions = this._register(new Emitter<readonly InstallExtensionResult[]>());197get onDidInstallExtensions() { return this._onDidInstallExtensions.event; }198199protected readonly _onUninstallExtension = this._register(new Emitter<UninstallExtensionEvent>());200get onUninstallExtension() { return this._onUninstallExtension.event; }201202protected readonly _onDidUninstallExtension = this._register(new Emitter<DidUninstallExtensionEvent>());203get onDidUninstallExtension() { return this._onDidUninstallExtension.event; }204205protected readonly _onDidUpdateExtensionMetadata = this._register(new Emitter<DidUpdateExtensionMetadata>());206get onDidUpdateExtensionMetadata() { return this._onDidUpdateExtensionMetadata.event; }207208constructor(209private readonly channel: IChannel,210productService: IProductService,211allowedExtensionsService: IAllowedExtensionsService,212) {213super(productService, allowedExtensionsService);214this._register(this.channel.listen<InstallExtensionEvent>('onInstallExtension')(e => this.onInstallExtensionEvent({ ...e, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source, profileLocation: URI.revive(e.profileLocation) })));215this._register(this.channel.listen<readonly InstallExtensionResult[]>('onDidInstallExtensions')(results => this.onDidInstallExtensionsEvent(results.map(e => ({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source, profileLocation: URI.revive(e.profileLocation) })))));216this._register(this.channel.listen<UninstallExtensionEvent>('onUninstallExtension')(e => this.onUninstallExtensionEvent({ ...e, profileLocation: URI.revive(e.profileLocation) })));217this._register(this.channel.listen<DidUninstallExtensionEvent>('onDidUninstallExtension')(e => this.onDidUninstallExtensionEvent({ ...e, profileLocation: URI.revive(e.profileLocation) })));218this._register(this.channel.listen<DidUpdateExtensionMetadata>('onDidUpdateExtensionMetadata')(e => this.onDidUpdateExtensionMetadataEvent({ profileLocation: URI.revive(e.profileLocation), local: transformIncomingExtension(e.local, null) })));219}220221protected onInstallExtensionEvent(event: InstallExtensionEvent): void {222this._onInstallExtension.fire(event);223}224225protected onDidInstallExtensionsEvent(results: readonly InstallExtensionResult[]): void {226this._onDidInstallExtensions.fire(results);227}228229protected onUninstallExtensionEvent(event: UninstallExtensionEvent): void {230this._onUninstallExtension.fire(event);231}232233protected onDidUninstallExtensionEvent(event: DidUninstallExtensionEvent): void {234this._onDidUninstallExtension.fire(event);235}236237protected onDidUpdateExtensionMetadataEvent(event: DidUpdateExtensionMetadata): void {238this._onDidUpdateExtensionMetadata.fire(event);239}240241private isUriComponents(obj: unknown): obj is UriComponents {242if (!obj) {243return false;244}245const thing = obj as UriComponents | undefined;246return typeof thing?.path === 'string' &&247typeof thing?.scheme === 'string';248}249250protected _targetPlatformPromise: Promise<TargetPlatform> | undefined;251getTargetPlatform(): Promise<TargetPlatform> {252if (!this._targetPlatformPromise) {253this._targetPlatformPromise = this.channel.call<TargetPlatform>('getTargetPlatform');254}255return this._targetPlatformPromise;256}257258zip(extension: ILocalExtension): Promise<URI> {259return Promise.resolve(this.channel.call<UriComponents>('zip', [extension]).then(result => URI.revive(result)));260}261262install(vsix: URI, options?: InstallOptions): Promise<ILocalExtension> {263return Promise.resolve(this.channel.call<ILocalExtension>('install', [vsix, options])).then(local => transformIncomingExtension(local, null));264}265266installFromLocation(location: URI, profileLocation: URI): Promise<ILocalExtension> {267return Promise.resolve(this.channel.call<ILocalExtension>('installFromLocation', [location, profileLocation])).then(local => transformIncomingExtension(local, null));268}269270async installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise<ILocalExtension[]> {271const result = await this.channel.call<ILocalExtension[]>('installExtensionsFromProfile', [extensions, fromProfileLocation, toProfileLocation]);272return result.map(local => transformIncomingExtension(local, null));273}274275getManifest(vsix: URI): Promise<IExtensionManifest> {276return Promise.resolve(this.channel.call<IExtensionManifest>('getManifest', [vsix]));277}278279installFromGallery(extension: IGalleryExtension, installOptions?: InstallOptions): Promise<ILocalExtension> {280return Promise.resolve(this.channel.call<ILocalExtension>('installFromGallery', [extension, installOptions])).then(local => transformIncomingExtension(local, null));281}282283async installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise<InstallExtensionResult[]> {284const results = await this.channel.call<InstallExtensionResult[]>('installGalleryExtensions', [extensions]);285return results.map(e => ({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source, profileLocation: URI.revive(e.profileLocation) }));286}287288uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void> {289if (extension.isWorkspaceScoped) {290throw new Error('Cannot uninstall a workspace extension');291}292return Promise.resolve(this.channel.call<void>('uninstall', [extension, options]));293}294295uninstallExtensions(extensions: UninstallExtensionInfo[]): Promise<void> {296if (extensions.some(e => e.extension.isWorkspaceScoped)) {297throw new Error('Cannot uninstall a workspace extension');298}299return Promise.resolve(this.channel.call<void>('uninstallExtensions', [extensions]));300301}302303getInstalled(type: ExtensionType | null = null, extensionsProfileResource?: URI, productVersion?: IProductVersion): Promise<ILocalExtension[]> {304return Promise.resolve(this.channel.call<ILocalExtension[]>('getInstalled', [type, extensionsProfileResource, productVersion, language]))305.then(extensions => extensions.map(extension => transformIncomingExtension(extension, null)));306}307308updateMetadata(local: ILocalExtension, metadata: Partial<Metadata>, extensionsProfileResource?: URI): Promise<ILocalExtension> {309return Promise.resolve(this.channel.call<ILocalExtension>('updateMetadata', [local, metadata, extensionsProfileResource]))310.then(extension => transformIncomingExtension(extension, null));311}312313resetPinnedStateForAllUserExtensions(pinned: boolean): Promise<void> {314return this.channel.call<void>('resetPinnedStateForAllUserExtensions', [pinned]);315}316317toggleApplicationScope(local: ILocalExtension, fromProfileLocation: URI): Promise<ILocalExtension> {318return this.channel.call<ILocalExtension>('toggleApplicationScope', [local, fromProfileLocation])319.then(extension => transformIncomingExtension(extension, null));320}321322copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise<void> {323return this.channel.call<void>('copyExtensions', [fromProfileLocation, toProfileLocation]);324}325326getExtensionsControlManifest(): Promise<IExtensionsControlManifest> {327return Promise.resolve(this.channel.call<IExtensionsControlManifest>('getExtensionsControlManifest'));328}329330async download(extension: IGalleryExtension, operation: InstallOperation, donotVerifySignature: boolean): Promise<URI> {331const result = await this.channel.call<UriComponents>('download', [extension, operation, donotVerifySignature]);332return URI.revive(result);333}334335async cleanUp(): Promise<void> {336return this.channel.call('cleanUp');337}338339registerParticipant() { throw new Error('Not Supported'); }340}341342export class ExtensionTipsChannel implements IServerChannel {343344constructor(private service: IExtensionTipsService) {345}346347// eslint-disable-next-line @typescript-eslint/no-explicit-any348listen(context: any, event: string): Event<any> {349throw new Error('Invalid listen');350}351352// eslint-disable-next-line @typescript-eslint/no-explicit-any353call(context: any, command: string, args?: any): Promise<any> {354switch (command) {355case 'getConfigBasedTips': return this.service.getConfigBasedTips(URI.revive(args[0]));356case 'getImportantExecutableBasedTips': return this.service.getImportantExecutableBasedTips();357case 'getOtherExecutableBasedTips': return this.service.getOtherExecutableBasedTips();358}359360throw new Error('Invalid call');361}362}363364365