sussudio
Version:
An unofficial VS Code Internal API
715 lines (714 loc) • 46.8 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { distinct, isNonEmptyArray } from "../../../base/common/arrays.mjs";
import { Barrier, createCancelablePromise } from "../../../base/common/async.mjs";
import { CancellationToken } from "../../../base/common/cancellation.mjs";
import { CancellationError, getErrorMessage } from "../../../base/common/errors.mjs";
import { Emitter, Event } from "../../../base/common/event.mjs";
import { Disposable, toDisposable } from "../../../base/common/lifecycle.mjs";
import { isWeb } from "../../../base/common/platform.mjs";
import { URI } from "../../../base/common/uri.mjs";
import * as nls from "../../../nls.mjs";
import { ExtensionManagementError, IExtensionGalleryService, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode } from "./extensionManagement.mjs";
import { areSameExtensions, ExtensionKey, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from "./extensionManagementUtil.mjs";
import { isApplicationScopedExtension } from "../../extensions/common/extensions.mjs";
import { ILogService } from "../../log/common/log.mjs";
import { IProductService } from "../../product/common/productService.mjs";
import { ITelemetryService } from "../../telemetry/common/telemetry.mjs";
import { IUserDataProfilesService } from "../../userDataProfile/common/userDataProfile.mjs";
export var ExtensionVerificationStatus;
(function (ExtensionVerificationStatus) {
ExtensionVerificationStatus["Verified"] = "Verified";
ExtensionVerificationStatus["Unverified"] = "Unverified";
ExtensionVerificationStatus["UnknownError"] = "UnknownError";
})(ExtensionVerificationStatus || (ExtensionVerificationStatus = {}));
let AbstractExtensionManagementService = class AbstractExtensionManagementService extends Disposable {
galleryService;
telemetryService;
logService;
productService;
userDataProfilesService;
extensionsControlManifest;
lastReportTimestamp = 0;
installingExtensions = new Map();
uninstallingExtensions = new Map();
_onInstallExtension = this._register(new Emitter());
get onInstallExtension() { return this._onInstallExtension.event; }
_onDidInstallExtensions = this._register(new Emitter());
get onDidInstallExtensions() { return this._onDidInstallExtensions.event; }
_onUninstallExtension = this._register(new Emitter());
get onUninstallExtension() { return this._onUninstallExtension.event; }
_onDidUninstallExtension = this._register(new Emitter());
get onDidUninstallExtension() { return this._onDidUninstallExtension.event; }
participants = [];
constructor(galleryService, telemetryService, logService, productService, userDataProfilesService) {
super();
this.galleryService = galleryService;
this.telemetryService = telemetryService;
this.logService = logService;
this.productService = productService;
this.userDataProfilesService = userDataProfilesService;
this._register(toDisposable(() => {
this.installingExtensions.forEach(({ task }) => task.cancel());
this.uninstallingExtensions.forEach(promise => promise.cancel());
this.installingExtensions.clear();
this.uninstallingExtensions.clear();
}));
}
async canInstall(extension) {
const currentTargetPlatform = await this.getTargetPlatform();
return extension.allTargetPlatforms.some(targetPlatform => isTargetPlatformCompatible(targetPlatform, extension.allTargetPlatforms, currentTargetPlatform));
}
async installFromGallery(extension, options = {}) {
try {
if (!this.galleryService.isEnabled()) {
throw new ExtensionManagementError(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"), ExtensionManagementErrorCode.Internal);
}
const compatible = await this.checkAndGetCompatibleVersion(extension, !!options.installGivenVersion, !!options.installPreReleaseVersion);
return await this.installExtension(compatible.manifest, compatible.extension, options);
}
catch (error) {
reportTelemetry(this.telemetryService, 'extensionGallery:install', { extensionData: getGalleryExtensionTelemetryData(extension), error });
this.logService.error(`Failed to install extension.`, extension.identifier.id);
this.logService.error(error);
throw toExtensionManagementError(error);
}
}
async uninstall(extension, options = {}) {
this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id);
return this.uninstallExtension(extension, options);
}
getExtensionsControlManifest() {
const now = new Date().getTime();
if (!this.extensionsControlManifest || now - this.lastReportTimestamp > 1000 * 60 * 5) { // 5 minute cache freshness
this.extensionsControlManifest = this.updateControlCache();
this.lastReportTimestamp = now;
}
return this.extensionsControlManifest;
}
registerParticipant(participant) {
this.participants.push(participant);
}
async installExtension(manifest, extension, options) {
const installExtensionTaskOptions = {
...options,
installOnlyNewlyAddedFromExtensionPack: URI.isUri(extension) ? options.installOnlyNewlyAddedFromExtensionPack : true,
profileLocation: isApplicationScopedExtension(manifest) ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation()
};
const getInstallExtensionTaskKey = (extension) => `${ExtensionKey.create(extension).toString()}${installExtensionTaskOptions.profileLocation ? `-${installExtensionTaskOptions.profileLocation.toString()}` : ''}`;
// only cache gallery extensions tasks
if (!URI.isUri(extension)) {
const installingExtension = this.installingExtensions.get(getInstallExtensionTaskKey(extension));
if (installingExtension) {
this.logService.info('Extensions is already requested to install', extension.identifier.id);
const { local } = await installingExtension.task.waitUntilTaskIsFinished();
return local;
}
}
const allInstallExtensionTasks = [];
const alreadyRequestedInstallations = [];
const installResults = [];
const installExtensionTask = this.createInstallExtensionTask(manifest, extension, installExtensionTaskOptions);
if (!URI.isUri(extension)) {
this.installingExtensions.set(getInstallExtensionTaskKey(extension), { task: installExtensionTask, waitingTasks: [] });
}
this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension, profileLocation: installExtensionTaskOptions.profileLocation });
this.logService.info('Installing extension:', installExtensionTask.identifier.id);
allInstallExtensionTasks.push({ task: installExtensionTask, manifest });
let installExtensionHasDependents = false;
try {
if (installExtensionTaskOptions.donotIncludePackAndDependencies) {
this.logService.info('Installing the extension without checking dependencies and pack', installExtensionTask.identifier.id);
}
else {
try {
const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensions(installExtensionTask.identifier, manifest, !!installExtensionTaskOptions.installOnlyNewlyAddedFromExtensionPack, !!installExtensionTaskOptions.installPreReleaseVersion, installExtensionTaskOptions.profileLocation);
const installed = await this.getInstalled(undefined, installExtensionTaskOptions.profileLocation);
for (const { gallery, manifest } of distinct(allDepsAndPackExtensionsToInstall, ({ gallery }) => gallery.identifier.id)) {
installExtensionHasDependents = installExtensionHasDependents || !!manifest.extensionDependencies?.some(id => areSameExtensions({ id }, installExtensionTask.identifier));
const key = getInstallExtensionTaskKey(gallery);
const existingInstallingExtension = this.installingExtensions.get(key);
if (existingInstallingExtension) {
if (this.canWaitForTask(installExtensionTask, existingInstallingExtension.task)) {
const identifier = existingInstallingExtension.task.identifier;
this.logService.info('Waiting for already requested installing extension', identifier.id, installExtensionTask.identifier.id);
existingInstallingExtension.waitingTasks.push(installExtensionTask);
// add promise that waits until the extension is completely installed, ie., onDidInstallExtensions event is triggered for this extension
alreadyRequestedInstallations.push(Event.toPromise(Event.filter(this.onDidInstallExtensions, results => results.some(result => areSameExtensions(result.identifier, identifier)))).then(results => {
this.logService.info('Finished waiting for already requested installing extension', identifier.id, installExtensionTask.identifier.id);
const result = results.find(result => areSameExtensions(result.identifier, identifier));
if (!result?.local) {
// Extension failed to install
throw new Error(`Extension ${identifier.id} is not installed`);
}
}));
}
}
else if (!installed.some(({ identifier }) => areSameExtensions(identifier, gallery.identifier))) {
const task = this.createInstallExtensionTask(manifest, gallery, { ...installExtensionTaskOptions, donotIncludePackAndDependencies: true });
this.installingExtensions.set(key, { task, waitingTasks: [installExtensionTask] });
this._onInstallExtension.fire({ identifier: task.identifier, source: gallery, profileLocation: installExtensionTaskOptions.profileLocation });
this.logService.info('Installing extension:', task.identifier.id, installExtensionTask.identifier.id);
allInstallExtensionTasks.push({ task, manifest });
}
}
}
catch (error) {
// Installing through VSIX
if (URI.isUri(installExtensionTask.source)) {
// Ignore installing dependencies and packs
if (isNonEmptyArray(manifest.extensionDependencies)) {
this.logService.warn(`Cannot install dependencies of extension:`, installExtensionTask.identifier.id, error.message);
}
if (isNonEmptyArray(manifest.extensionPack)) {
this.logService.warn(`Cannot install packed extensions of extension:`, installExtensionTask.identifier.id, error.message);
}
}
else {
this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', installExtensionTask.identifier.id);
throw error;
}
}
}
const extensionsToInstallMap = allInstallExtensionTasks.reduce((result, { task, manifest }) => {
result.set(task.identifier.id.toLowerCase(), { task, manifest });
return result;
}, new Map());
while (extensionsToInstallMap.size) {
let extensionsToInstall;
const extensionsWithoutDepsToInstall = [...extensionsToInstallMap.values()].filter(({ manifest }) => !manifest.extensionDependencies?.some(id => extensionsToInstallMap.has(id.toLowerCase())));
if (extensionsWithoutDepsToInstall.length) {
extensionsToInstall = extensionsToInstallMap.size === 1 ? extensionsWithoutDepsToInstall
/* If the main extension has no dependents remove it and install it at the end */
: extensionsWithoutDepsToInstall.filter(({ task }) => !(task === installExtensionTask && !installExtensionHasDependents));
}
else {
this.logService.info('Found extensions with circular dependencies', extensionsWithoutDepsToInstall.map(({ task }) => task.identifier.id));
extensionsToInstall = [...extensionsToInstallMap.values()];
}
// Install extensions in parallel and wait until all extensions are installed / failed
await this.joinAllSettled(extensionsToInstall.map(async ({ task }) => {
const startTime = new Date().getTime();
try {
const { local } = await task.run();
await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, installExtensionTaskOptions, CancellationToken.None)));
if (!URI.isUri(task.source)) {
const isUpdate = task.operation === 3 /* InstallOperation.Update */;
const durationSinceUpdate = isUpdate ? undefined : (new Date().getTime() - task.source.lastUpdated) / 1000;
reportTelemetry(this.telemetryService, isUpdate ? 'extensionGallery:update' : 'extensionGallery:install', {
extensionData: getGalleryExtensionTelemetryData(task.source),
verificationStatus: task.verificationStatus,
duration: new Date().getTime() - startTime,
durationSinceUpdate
});
// In web, report extension install statistics explicitly. In Desktop, statistics are automatically updated while downloading the VSIX.
if (isWeb && task.operation !== 3 /* InstallOperation.Update */) {
try {
await this.galleryService.reportStatistic(local.manifest.publisher, local.manifest.name, local.manifest.version, "install" /* StatisticType.Install */);
}
catch (error) { /* ignore */ }
}
}
installResults.push({ local, identifier: task.identifier, operation: task.operation, source: task.source, context: installExtensionTaskOptions.context, profileLocation: installExtensionTaskOptions.profileLocation, applicationScoped: local.isApplicationScoped });
}
catch (error) {
if (!URI.isUri(task.source)) {
reportTelemetry(this.telemetryService, task.operation === 3 /* InstallOperation.Update */ ? 'extensionGallery:update' : 'extensionGallery:install', {
extensionData: getGalleryExtensionTelemetryData(task.source),
verificationStatus: task.verificationStatus,
duration: new Date().getTime() - startTime,
error
});
}
this.logService.error('Error while installing the extension:', task.identifier.id);
throw error;
}
finally {
extensionsToInstallMap.delete(task.identifier.id.toLowerCase());
}
}));
}
if (alreadyRequestedInstallations.length) {
await this.joinAllSettled(alreadyRequestedInstallations);
}
installResults.forEach(({ identifier }) => this.logService.info(`Extension installed successfully:`, identifier.id));
this._onDidInstallExtensions.fire(installResults);
return installResults.filter(({ identifier }) => areSameExtensions(identifier, installExtensionTask.identifier))[0].local;
}
catch (error) {
// cancel all tasks
allInstallExtensionTasks.forEach(({ task }) => task.cancel());
// rollback installed extensions
if (installResults.length) {
try {
const result = await Promise.allSettled(installResults.map(({ local }) => this.createUninstallExtensionTask(local, { versionOnly: true, profileLocation: installExtensionTaskOptions.profileLocation }).run()));
for (let index = 0; index < result.length; index++) {
const r = result[index];
const { identifier } = installResults[index];
if (r.status === 'fulfilled') {
this.logService.info('Rollback: Uninstalled extension', identifier.id);
}
else {
this.logService.warn('Rollback: Error while uninstalling extension', identifier.id, getErrorMessage(r.reason));
}
}
}
catch (error) {
// ignore error
this.logService.warn('Error while rolling back extensions', getErrorMessage(error), installResults.map(({ identifier }) => identifier.id));
}
}
this._onDidInstallExtensions.fire(allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: 2 /* InstallOperation.Install */, source: task.source, context: installExtensionTaskOptions.context, profileLocation: installExtensionTaskOptions.profileLocation })));
throw error;
}
finally {
// Finally, remove all the tasks from the cache
for (const { task } of allInstallExtensionTasks) {
if (task.source && !URI.isUri(task.source)) {
this.installingExtensions.delete(getInstallExtensionTaskKey(task.source));
}
}
}
}
canWaitForTask(taskToWait, taskToWaitFor) {
for (const [, { task, waitingTasks }] of this.installingExtensions.entries()) {
if (task === taskToWait) {
// Cannot be waited, If taskToWaitFor is waiting for taskToWait
if (waitingTasks.includes(taskToWaitFor)) {
return false;
}
// Cannot be waited, If taskToWaitFor is waiting for tasks waiting for taskToWait
if (waitingTasks.some(waitingTask => this.canWaitForTask(waitingTask, taskToWaitFor))) {
return false;
}
}
// Cannot be waited, if the taskToWait cannot be waited for the task created the taskToWaitFor
// Because, the task waits for the tasks it created
if (task === taskToWaitFor && waitingTasks[0] && !this.canWaitForTask(taskToWait, waitingTasks[0])) {
return false;
}
}
return true;
}
async joinAllSettled(promises) {
const results = [];
const errors = [];
const promiseResults = await Promise.allSettled(promises);
for (const r of promiseResults) {
if (r.status === 'fulfilled') {
results.push(r.value);
}
else {
errors.push(r.reason);
}
}
// If there are errors, throw the error.
if (errors.length) {
throw joinErrors(errors);
}
return results;
}
async getAllDepsAndPackExtensions(extensionIdentifier, manifest, getOnlyNewlyAddedFromExtensionPack, installPreRelease, profile) {
if (!this.galleryService.isEnabled()) {
return [];
}
const installed = await this.getInstalled(undefined, profile);
const knownIdentifiers = [];
const allDependenciesAndPacks = [];
const collectDependenciesAndPackExtensionsToInstall = async (extensionIdentifier, manifest) => {
knownIdentifiers.push(extensionIdentifier);
const dependecies = manifest.extensionDependencies || [];
const dependenciesAndPackExtensions = [...dependecies];
if (manifest.extensionPack) {
const existing = getOnlyNewlyAddedFromExtensionPack ? installed.find(e => areSameExtensions(e.identifier, extensionIdentifier)) : undefined;
for (const extension of manifest.extensionPack) {
// add only those extensions which are new in currently installed extension
if (!(existing && existing.manifest.extensionPack && existing.manifest.extensionPack.some(old => areSameExtensions({ id: old }, { id: extension })))) {
if (dependenciesAndPackExtensions.every(e => !areSameExtensions({ id: e }, { id: extension }))) {
dependenciesAndPackExtensions.push(extension);
}
}
}
}
if (dependenciesAndPackExtensions.length) {
// filter out known extensions
const ids = dependenciesAndPackExtensions.filter(id => knownIdentifiers.every(galleryIdentifier => !areSameExtensions(galleryIdentifier, { id })));
if (ids.length) {
const galleryExtensions = await this.galleryService.getExtensions(ids.map(id => ({ id, preRelease: installPreRelease })), CancellationToken.None);
for (const galleryExtension of galleryExtensions) {
if (knownIdentifiers.find(identifier => areSameExtensions(identifier, galleryExtension.identifier))) {
continue;
}
const isDependency = dependecies.some(id => areSameExtensions({ id }, galleryExtension.identifier));
let compatible;
try {
compatible = await this.checkAndGetCompatibleVersion(galleryExtension, false, installPreRelease);
}
catch (error) {
if (error instanceof ExtensionManagementError && error.code === ExtensionManagementErrorCode.IncompatibleTargetPlatform && !isDependency) {
this.logService.info('Skipping the packed extension as it cannot be installed', galleryExtension.identifier.id);
continue;
}
else {
throw error;
}
}
allDependenciesAndPacks.push({ gallery: compatible.extension, manifest: compatible.manifest });
await collectDependenciesAndPackExtensionsToInstall(compatible.extension.identifier, compatible.manifest);
}
}
}
};
await collectDependenciesAndPackExtensionsToInstall(extensionIdentifier, manifest);
return allDependenciesAndPacks;
}
async checkAndGetCompatibleVersion(extension, sameVersion, installPreRelease) {
const extensionsControlManifest = await this.getExtensionsControlManifest();
if (extensionsControlManifest.malicious.some(identifier => areSameExtensions(extension.identifier, identifier))) {
throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), ExtensionManagementErrorCode.Malicious);
}
if (!await this.canInstall(extension)) {
const targetPlatform = await this.getTargetPlatform();
throw new ExtensionManagementError(nls.localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform)), ExtensionManagementErrorCode.IncompatibleTargetPlatform);
}
const compatibleExtension = await this.getCompatibleVersion(extension, sameVersion, installPreRelease);
if (compatibleExtension) {
if (installPreRelease && !sameVersion && extension.hasPreReleaseVersion && !compatibleExtension.properties.isPreReleaseVersion) {
throw new ExtensionManagementError(nls.localize('notFoundCompatiblePrereleaseDependency', "Can't install pre-release version of '{0}' extension because it is not compatible with the current version of {1} (version {2}).", extension.identifier.id, this.productService.nameLong, this.productService.version), ExtensionManagementErrorCode.IncompatiblePreRelease);
}
}
else {
/** If no compatible release version is found, check if the extension has a release version or not and throw relevant error */
if (!installPreRelease && extension.properties.isPreReleaseVersion && (await this.galleryService.getExtensions([extension.identifier], CancellationToken.None))[0]) {
throw new ExtensionManagementError(nls.localize('notFoundReleaseExtension', "Can't install release version of '{0}' extension because it has no release version.", extension.identifier.id), ExtensionManagementErrorCode.ReleaseVersionNotFound);
}
throw new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of {1} (version {2}).", extension.identifier.id, this.productService.nameLong, this.productService.version), ExtensionManagementErrorCode.Incompatible);
}
this.logService.info('Getting Manifest...', compatibleExtension.identifier.id);
const manifest = await this.galleryService.getManifest(compatibleExtension, CancellationToken.None);
if (manifest === null) {
throw new ExtensionManagementError(`Missing manifest for extension ${extension.identifier.id}`, ExtensionManagementErrorCode.Invalid);
}
if (manifest.version !== compatibleExtension.version) {
throw new ExtensionManagementError(`Cannot install '${extension.identifier.id}' extension because of version mismatch in Marketplace`, ExtensionManagementErrorCode.Invalid);
}
return { extension: compatibleExtension, manifest };
}
async getCompatibleVersion(extension, sameVersion, includePreRelease) {
const targetPlatform = await this.getTargetPlatform();
let compatibleExtension = null;
if (!sameVersion && extension.hasPreReleaseVersion && extension.properties.isPreReleaseVersion !== includePreRelease) {
compatibleExtension = (await this.galleryService.getExtensions([{ ...extension.identifier, preRelease: includePreRelease }], { targetPlatform, compatible: true }, CancellationToken.None))[0] || null;
}
if (!compatibleExtension && await this.galleryService.isExtensionCompatible(extension, includePreRelease, targetPlatform)) {
compatibleExtension = extension;
}
if (!compatibleExtension) {
if (sameVersion) {
compatibleExtension = (await this.galleryService.getExtensions([{ ...extension.identifier, version: extension.version }], { targetPlatform, compatible: true }, CancellationToken.None))[0] || null;
}
else {
compatibleExtension = await this.galleryService.getCompatibleExtension(extension, includePreRelease, targetPlatform);
}
}
return compatibleExtension;
}
async uninstallExtension(extension, options) {
const uninstallOptions = {
...options,
profileLocation: extension.isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation()
};
const getUninstallExtensionTaskKey = (identifier) => `${identifier.id.toLowerCase()}${uninstallOptions.versionOnly ? `-${extension.manifest.version}` : ''}${uninstallOptions.profileLocation ? `@${uninstallOptions.profileLocation.toString()}` : ''}`;
const uninstallExtensionTask = this.uninstallingExtensions.get(getUninstallExtensionTaskKey(extension.identifier));
if (uninstallExtensionTask) {
this.logService.info('Extensions is already requested to uninstall', extension.identifier.id);
return uninstallExtensionTask.waitUntilTaskIsFinished();
}
const createUninstallExtensionTask = (extension) => {
const uninstallExtensionTask = this.createUninstallExtensionTask(extension, uninstallOptions);
this.uninstallingExtensions.set(getUninstallExtensionTaskKey(uninstallExtensionTask.extension.identifier), uninstallExtensionTask);
if (uninstallOptions.profileLocation) {
this.logService.info('Uninstalling extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString());
}
else {
this.logService.info('Uninstalling extension:', `${extension.identifier.id}@${extension.manifest.version}`);
}
this._onUninstallExtension.fire({ identifier: extension.identifier, profileLocation: uninstallOptions.profileLocation, applicationScoped: extension.isApplicationScoped });
return uninstallExtensionTask;
};
const postUninstallExtension = (extension, error) => {
if (error) {
if (uninstallOptions.profileLocation) {
this.logService.error('Failed to uninstall extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString(), error.message);
}
else {
this.logService.error('Failed to uninstall extension:', `${extension.identifier.id}@${extension.manifest.version}`, error.message);
}
}
else {
if (uninstallOptions.profileLocation) {
this.logService.info('Successfully uninstalled extension from the profile', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString());
}
else {
this.logService.info('Successfully uninstalled extension:', `${extension.identifier.id}@${extension.manifest.version}`);
}
}
reportTelemetry(this.telemetryService, 'extensionGallery:uninstall', { extensionData: getLocalExtensionTelemetryData(extension), error });
this._onDidUninstallExtension.fire({ identifier: extension.identifier, error: error?.code, profileLocation: uninstallOptions.profileLocation, applicationScoped: extension.isApplicationScoped });
};
const allTasks = [];
const processedTasks = [];
try {
allTasks.push(createUninstallExtensionTask(extension));
const installed = await this.getInstalled(1 /* ExtensionType.User */, uninstallOptions.profileLocation);
if (uninstallOptions.donotIncludePack) {
this.logService.info('Uninstalling the extension without including packed extension', `${extension.identifier.id}@${extension.manifest.version}`);
}
else {
const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed);
for (const packedExtension of packedExtensions) {
if (this.uninstallingExtensions.has(getUninstallExtensionTaskKey(packedExtension.identifier))) {
this.logService.info('Extensions is already requested to uninstall', packedExtension.identifier.id);
}
else {
allTasks.push(createUninstallExtensionTask(packedExtension));
}
}
}
if (uninstallOptions.donotCheckDependents) {
this.logService.info('Uninstalling the extension without checking dependents', `${extension.identifier.id}@${extension.manifest.version}`);
}
else {
this.checkForDependents(allTasks.map(task => task.extension), installed, extension);
}
// Uninstall extensions in parallel and wait until all extensions are uninstalled / failed
await this.joinAllSettled(allTasks.map(async (task) => {
try {
await task.run();
await this.joinAllSettled(this.participants.map(participant => participant.postUninstall(task.extension, uninstallOptions, CancellationToken.None)));
// only report if extension has a mapped gallery extension. UUID identifies the gallery extension.
if (task.extension.identifier.uuid) {
try {
await this.galleryService.reportStatistic(task.extension.manifest.publisher, task.extension.manifest.name, task.extension.manifest.version, "uninstall" /* StatisticType.Uninstall */);
}
catch (error) { /* ignore */ }
}
postUninstallExtension(task.extension);
}
catch (e) {
const error = e instanceof ExtensionManagementError ? e : new ExtensionManagementError(getErrorMessage(e), ExtensionManagementErrorCode.Internal);
postUninstallExtension(task.extension, error);
throw error;
}
finally {
processedTasks.push(task);
}
}));
}
catch (e) {
const error = e instanceof ExtensionManagementError ? e : new ExtensionManagementError(getErrorMessage(e), ExtensionManagementErrorCode.Internal);
for (const task of allTasks) {
// cancel the tasks
try {
task.cancel();
}
catch (error) { /* ignore */ }
if (!processedTasks.includes(task)) {
postUninstallExtension(task.extension, error);
}
}
throw error;
}
finally {
// Remove tasks from cache
for (const task of allTasks) {
if (!this.uninstallingExtensions.delete(getUninstallExtensionTaskKey(task.extension.identifier))) {
this.logService.warn('Uninstallation task is not found in the cache', task.extension.identifier.id);
}
}
}
}
checkForDependents(extensionsToUninstall, installed, extensionToUninstall) {
for (const extension of extensionsToUninstall) {
const dependents = this.getDependents(extension, installed);
if (dependents.length) {
const remainingDependents = dependents.filter(dependent => !extensionsToUninstall.some(e => areSameExtensions(e.identifier, dependent.identifier)));
if (remainingDependents.length) {
throw new Error(this.getDependentsErrorMessage(extension, remainingDependents, extensionToUninstall));
}
}
}
}
getDependentsErrorMessage(dependingExtension, dependents, extensionToUninstall) {
if (extensionToUninstall === dependingExtension) {
if (dependents.length === 1) {
return nls.localize('singleDependentError', "Cannot uninstall '{0}' extension. '{1}' extension depends on this.", extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name);
}
if (dependents.length === 2) {
return nls.localize('twoDependentsError', "Cannot uninstall '{0}' extension. '{1}' and '{2}' extensions depend on this.", extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
}
return nls.localize('multipleDependentsError', "Cannot uninstall '{0}' extension. '{1}', '{2}' and other extension depend on this.", extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
}
if (dependents.length === 1) {
return nls.localize('singleIndirectDependentError', "Cannot uninstall '{0}' extension . It includes uninstalling '{1}' extension and '{2}' extension depends on this.", extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName
|| dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name);
}
if (dependents.length === 2) {
return nls.localize('twoIndirectDependentsError', "Cannot uninstall '{0}' extension. It includes uninstalling '{1}' extension and '{2}' and '{3}' extensions depend on this.", extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName
|| dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
}
return nls.localize('multipleIndirectDependentsError', "Cannot uninstall '{0}' extension. It includes uninstalling '{1}' extension and '{2}', '{3}' and other extensions depend on this.", extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName
|| dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
}
getAllPackExtensionsToUninstall(extension, installed, checked = []) {
if (checked.indexOf(extension) !== -1) {
return [];
}
checked.push(extension);
const extensionsPack = extension.manifest.extensionPack ? extension.manifest.extensionPack : [];
if (extensionsPack.length) {
const packedExtensions = installed.filter(i => !i.isBuiltin && extensionsPack.some(id => areSameExtensions({ id }, i.identifier)));
const packOfPackedExtensions = [];
for (const packedExtension of packedExtensions) {
packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked));
}
return [...packedExtensions, ...packOfPackedExtensions];
}
return [];
}
getDependents(extension, installed) {
return installed.filter(e => e.manifest.extensionDependencies && e.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier)));
}
async updateControlCache() {
try {
this.logService.trace('ExtensionManagementService.refreshReportedCache');
const manifest = await this.galleryService.getExtensionsControlManifest();
this.logService.trace(`ExtensionManagementService.refreshControlCache`, manifest);
return manifest;
}
catch (err) {
this.logService.trace('ExtensionManagementService.refreshControlCache - failed to get extension control manifest');
return { malicious: [], deprecated: {} };
}
}
};
AbstractExtensionManagementService = __decorate([
__param(0, IExtensionGalleryService),
__param(1, ITelemetryService),
__param(2, ILogService),
__param(3, IProductService),
__param(4, IUserDataProfilesService)
], AbstractExtensionManagementService);
export { AbstractExtensionManagementService };
export function joinErrors(errorOrErrors) {
const errors = Array.isArray(errorOrErrors) ? errorOrErrors : [errorOrErrors];
if (errors.length === 1) {
return errors[0] instanceof Error ? errors[0] : new Error(errors[0]);
}
return errors.reduce((previousValue, currentValue) => {
return new Error(`${previousValue.message}${previousValue.message ? ',' : ''}${currentValue instanceof Error ? currentValue.message : currentValue}`);
}, new Error(''));
}
function toExtensionManagementError(error) {
if (error instanceof ExtensionManagementError) {
return error;
}
const e = new ExtensionManagementError(error.message, ExtensionManagementErrorCode.Internal);
e.stack = error.stack;
return e;
}
export function reportTelemetry(telemetryService, eventName, { extensionData, verificationStatus, duration, error, durationSinceUpdate }) {
let errorcode;
let errorcodeDetail;
if (error) {
if (error instanceof ExtensionManagementError) {
errorcode = error.code;
if (error.code === ExtensionManagementErrorCode.Signature) {
errorcodeDetail = error.message;
}
}
else {
errorcode = ExtensionManagementErrorCode.Internal;
}
}
/* __GDPR__
"extensionGallery:install" : {
"owner": "sandy081",
"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"durationSinceUpdate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"errorcodeDetail": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"recommendationReason": { "retiredFromVersion": "1.23.0", "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"verificationStatus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
/* __GDPR__
"extensionGallery:uninstall" : {
"owner": "sandy081",
"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
/* __GDPR__
"extensionGallery:update" : {
"owner": "sandy081",
"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"errorcodeDetail": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"verificationStatus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
telemetryService.publicLog(eventName, { ...extensionData, verificationStatus, success: !error, duration, errorcode, errorcodeDetail, durationSinceUpdate });
}
export class AbstractExtensionTask {
barrier = new Barrier();
cancellablePromise;
async waitUntilTaskIsFinished() {
await this.barrier.wait();
return this.cancellablePromise;
}
async run() {
if (!this.cancellablePromise) {
this.cancellablePromise = createCancelablePromise(token => this.doRun(token));
}
this.barrier.open();
return this.cancellablePromise;
}
cancel() {
if (!this.cancellablePromise) {
this.cancellablePromise = createCancelablePromise(token => {
return new Promise((c, e) => {
const disposable = token.onCancellationRequested(() => {
disposable.dispose();
e(new CancellationError());
});
});
});
this.barrier.open();
}
this.cancellablePromise.cancel();
}
}