Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as nls from '../../../../nls.js';
7
import * as semver from '../../../../base/common/semver/semver.js';
8
import { Event, Emitter } from '../../../../base/common/event.js';
9
import { index } from '../../../../base/common/arrays.js';
10
import { CancelablePromise, Promises, ThrottledDelayer, createCancelablePromise } from '../../../../base/common/async.js';
11
import { CancellationError, getErrorMessage, isCancellationError } from '../../../../base/common/errors.js';
12
import { Disposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
13
import { IPager, singlePagePager } from '../../../../base/common/paging.js';
14
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
15
import {
16
IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions,
17
InstallExtensionEvent, DidUninstallExtensionEvent, InstallOperation, WEB_EXTENSION_TAG, InstallExtensionResult,
18
IExtensionsControlManifest, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo, isTargetPlatformCompatible, InstallExtensionInfo, EXTENSION_IDENTIFIER_REGEX,
19
InstallOptions, IProductVersion,
20
UninstallExtensionInfo,
21
TargetPlatformToString,
22
IAllowedExtensionsService,
23
AllowedExtensionsConfigKey,
24
EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT,
25
ExtensionManagementError,
26
ExtensionManagementErrorCode,
27
MaliciousExtensionInfo,
28
shouldRequireRepositorySignatureFor,
29
IGalleryExtensionVersion
30
} from '../../../../platform/extensionManagement/common/extensionManagement.js';
31
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService, IResourceExtension } from '../../../services/extensionManagement/common/extensionManagement.js';
32
import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, groupByExtension, getGalleryExtensionId, findMatchingMaliciousEntry } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';
33
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
34
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
35
import { IHostService } from '../../../services/host/browser/host.js';
36
import { URI } from '../../../../base/common/uri.js';
37
import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, HasOutdatedExtensionsContext, AutoUpdateConfigurationValue, InstallExtensionOptions, ExtensionRuntimeState, ExtensionRuntimeActionType, AutoRestartConfigurationKey, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionsNotification } from '../common/extensions.js';
38
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from '../../../services/editor/common/editorService.js';
39
import { IURLService, IURLHandler, IOpenURLOptions } from '../../../../platform/url/common/url.js';
40
import { ExtensionsInput, IExtensionEditorOptions } from '../common/extensionsInput.js';
41
import { ILogService } from '../../../../platform/log/common/log.js';
42
import { IProgressOptions, IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js';
43
import { INotificationService, NotificationPriority, Severity } from '../../../../platform/notification/common/notification.js';
44
import * as resources from '../../../../base/common/resources.js';
45
import { CancellationToken } from '../../../../base/common/cancellation.js';
46
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
47
import { IFileService } from '../../../../platform/files/common/files.js';
48
import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension, TargetPlatform, ExtensionIdentifier, IExtensionIdentifier, IExtensionDescription, isApplicationScopedExtension } from '../../../../platform/extensions/common/extensions.js';
49
import { ILanguageService } from '../../../../editor/common/languages/language.js';
50
import { IProductService } from '../../../../platform/product/common/productService.js';
51
import { FileAccess } from '../../../../base/common/network.js';
52
import { IIgnoredExtensionsManagementService } from '../../../../platform/userDataSync/common/ignoredExtensions.js';
53
import { IUserDataAutoSyncService, IUserDataSyncEnablementService, SyncResource } from '../../../../platform/userDataSync/common/userDataSync.js';
54
import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
55
import { isBoolean, isDefined, isString, isUndefined } from '../../../../base/common/types.js';
56
import { IExtensionManifestPropertiesService } from '../../../services/extensions/common/extensionManifestPropertiesService.js';
57
import { IExtensionService, IExtensionsStatus as IExtensionRuntimeStatus, toExtension, toExtensionDescription } from '../../../services/extensions/common/extensions.js';
58
import { isWeb, language } from '../../../../base/common/platform.js';
59
import { getLocale } from '../../../../platform/languagePacks/common/languagePacks.js';
60
import { ILocaleService } from '../../../services/localization/common/locale.js';
61
import { TelemetryTrustedValue } from '../../../../platform/telemetry/common/telemetryUtils.js';
62
import { ILifecycleService, LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js';
63
import { IUserDataProfileService } from '../../../services/userDataProfile/common/userDataProfile.js';
64
import { mainWindow } from '../../../../base/browser/window.js';
65
import { IDialogService, IFileDialogService, IPromptButton } from '../../../../platform/dialogs/common/dialogs.js';
66
import { IUpdateService, StateType } from '../../../../platform/update/common/update.js';
67
import { areApiProposalsCompatible, isEngineValid } from '../../../../platform/extensions/common/extensionValidator.js';
68
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
69
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
70
import { ShowCurrentReleaseNotesActionId } from '../../update/common/update.js';
71
import { IViewsService } from '../../../services/views/common/viewsService.js';
72
import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js';
73
import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js';
74
import { ExtensionGalleryResourceType, getExtensionGalleryManifestResourceUri, IExtensionGalleryManifestService } from '../../../../platform/extensionManagement/common/extensionGalleryManifest.js';
75
import { fromNow } from '../../../../base/common/date.js';
76
import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';
77
78
interface IExtensionStateProvider<T> {
79
(extension: Extension): T;
80
}
81
82
interface InstalledExtensionsEvent {
83
readonly extensionIds: TelemetryTrustedValue<string>;
84
readonly count: number;
85
}
86
type ExtensionsLoadClassification = {
87
owner: 'digitarald';
88
comment: 'Helps to understand which extensions are the most actively used.';
89
readonly extensionIds: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The list of extension ids that are installed.' };
90
readonly count: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The number of extensions that are installed.' };
91
};
92
93
export class Extension implements IExtension {
94
95
public enablementState: EnablementState = EnablementState.EnabledGlobally;
96
97
private galleryResourcesCache = new Map<string, any>();
98
99
private _missingFromGallery: boolean | undefined;
100
101
constructor(
102
private stateProvider: IExtensionStateProvider<ExtensionState>,
103
private runtimeStateProvider: IExtensionStateProvider<ExtensionRuntimeState | undefined>,
104
public readonly server: IExtensionManagementServer | undefined,
105
public local: ILocalExtension | undefined,
106
private _gallery: IGalleryExtension | undefined,
107
private readonly resourceExtensionInfo: { resourceExtension: IResourceExtension; isWorkspaceScoped: boolean } | undefined,
108
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
109
@ITelemetryService private readonly telemetryService: ITelemetryService,
110
@ILogService private readonly logService: ILogService,
111
@IFileService private readonly fileService: IFileService,
112
@IProductService private readonly productService: IProductService
113
) {
114
}
115
116
get resourceExtension(): IResourceExtension | undefined {
117
if (this.resourceExtensionInfo) {
118
return this.resourceExtensionInfo.resourceExtension;
119
}
120
if (this.local?.isWorkspaceScoped) {
121
return {
122
type: 'resource',
123
identifier: this.local.identifier,
124
location: this.local.location,
125
manifest: this.local.manifest,
126
changelogUri: this.local.changelogUrl,
127
readmeUri: this.local.readmeUrl,
128
};
129
}
130
return undefined;
131
}
132
133
get gallery(): IGalleryExtension | undefined {
134
return this._gallery;
135
}
136
137
set gallery(gallery: IGalleryExtension | undefined) {
138
this._gallery = gallery;
139
this.galleryResourcesCache.clear();
140
}
141
142
get missingFromGallery(): boolean {
143
return !!this._missingFromGallery;
144
}
145
146
set missingFromGallery(missing: boolean) {
147
this._missingFromGallery = missing;
148
}
149
150
get type(): ExtensionType {
151
return this.local ? this.local.type : ExtensionType.User;
152
}
153
154
get isBuiltin(): boolean {
155
return this.local ? this.local.isBuiltin : false;
156
}
157
158
get isWorkspaceScoped(): boolean {
159
if (this.local) {
160
return this.local.isWorkspaceScoped;
161
}
162
if (this.resourceExtensionInfo) {
163
return this.resourceExtensionInfo.isWorkspaceScoped;
164
}
165
return false;
166
}
167
168
get name(): string {
169
if (this.gallery) {
170
return this.gallery.name;
171
}
172
return this.getManifestFromLocalOrResource()?.name ?? '';
173
}
174
175
get displayName(): string {
176
if (this.gallery) {
177
return this.gallery.displayName || this.gallery.name;
178
}
179
180
return this.getManifestFromLocalOrResource()?.displayName ?? this.name;
181
}
182
183
get identifier(): IExtensionIdentifier {
184
if (this.gallery) {
185
return this.gallery.identifier;
186
}
187
if (this.resourceExtension) {
188
return this.resourceExtension.identifier;
189
}
190
return this.local?.identifier ?? { id: '' };
191
}
192
193
get uuid(): string | undefined {
194
return this.gallery ? this.gallery.identifier.uuid : this.local?.identifier.uuid;
195
}
196
197
get publisher(): string {
198
if (this.gallery) {
199
return this.gallery.publisher;
200
}
201
return this.getManifestFromLocalOrResource()?.publisher ?? '';
202
}
203
204
get publisherDisplayName(): string {
205
if (this.gallery) {
206
return this.gallery.publisherDisplayName || this.gallery.publisher;
207
}
208
209
if (this.local?.publisherDisplayName) {
210
return this.local.publisherDisplayName;
211
}
212
213
return this.publisher;
214
}
215
216
get publisherUrl(): URI | undefined {
217
return this.gallery?.publisherLink ? URI.parse(this.gallery.publisherLink) : undefined;
218
}
219
220
get publisherDomain(): { link: string; verified: boolean } | undefined {
221
return this.gallery?.publisherDomain;
222
}
223
224
get publisherSponsorLink(): URI | undefined {
225
return this.gallery?.publisherSponsorLink ? URI.parse(this.gallery.publisherSponsorLink) : undefined;
226
}
227
228
get version(): string {
229
return this.local ? this.local.manifest.version : this.latestVersion;
230
}
231
232
get private(): boolean {
233
return this.gallery ? this.gallery.private : this.local ? this.local.private : false;
234
}
235
236
get pinned(): boolean {
237
return !!this.local?.pinned;
238
}
239
240
get latestVersion(): string {
241
return this.gallery ? this.gallery.version : this.getManifestFromLocalOrResource()?.version ?? '';
242
}
243
244
get description(): string {
245
return this.gallery ? this.gallery.description : this.getManifestFromLocalOrResource()?.description ?? '';
246
}
247
248
get url(): string | undefined {
249
return this.gallery?.detailsLink;
250
}
251
252
get iconUrl(): string | undefined {
253
return this.galleryIconUrl || this.resourceExtensionIconUrl || this.localIconUrl || this.defaultIconUrl;
254
}
255
256
get iconUrlFallback(): string | undefined {
257
return this.gallery?.assets.icon?.fallbackUri;
258
}
259
260
private get localIconUrl(): string | undefined {
261
if (this.local && this.local.manifest.icon) {
262
return FileAccess.uriToBrowserUri(resources.joinPath(this.local.location, this.local.manifest.icon)).toString(true);
263
}
264
return undefined;
265
}
266
267
private get resourceExtensionIconUrl(): string | undefined {
268
if (this.resourceExtension?.manifest.icon) {
269
return FileAccess.uriToBrowserUri(resources.joinPath(this.resourceExtension.location, this.resourceExtension.manifest.icon)).toString(true);
270
}
271
return undefined;
272
}
273
274
private get galleryIconUrl(): string | undefined {
275
return this.gallery?.assets.icon?.uri;
276
}
277
278
private get defaultIconUrl(): string | undefined {
279
if (this.type === ExtensionType.System && this.local) {
280
if (this.local.manifest && this.local.manifest.contributes) {
281
if (Array.isArray(this.local.manifest.contributes.themes) && this.local.manifest.contributes.themes.length) {
282
return FileAccess.asBrowserUri('vs/workbench/contrib/extensions/browser/media/theme-icon.png').toString(true);
283
}
284
if (Array.isArray(this.local.manifest.contributes.grammars) && this.local.manifest.contributes.grammars.length) {
285
return FileAccess.asBrowserUri('vs/workbench/contrib/extensions/browser/media/language-icon.svg').toString(true);
286
}
287
}
288
}
289
return undefined;
290
}
291
292
get repository(): string | undefined {
293
return this.gallery && this.gallery.assets.repository ? this.gallery.assets.repository.uri : undefined;
294
}
295
296
get licenseUrl(): string | undefined {
297
return this.gallery && this.gallery.assets.license ? this.gallery.assets.license.uri : undefined;
298
}
299
300
get supportUrl(): string | undefined {
301
return this.gallery && this.gallery.supportLink ? this.gallery.supportLink : undefined;
302
}
303
304
get state(): ExtensionState {
305
return this.stateProvider(this);
306
}
307
308
private malicious: MaliciousExtensionInfo | undefined;
309
public get isMalicious(): boolean | undefined {
310
return !!this.malicious || this.enablementState === EnablementState.DisabledByMalicious;
311
}
312
313
public get maliciousInfoLink(): string | undefined {
314
return this.malicious?.learnMoreLink;
315
}
316
317
public deprecationInfo: IDeprecationInfo | undefined;
318
319
get installCount(): number | undefined {
320
return this.gallery ? this.gallery.installCount : undefined;
321
}
322
323
get rating(): number | undefined {
324
return this.gallery ? this.gallery.rating : undefined;
325
}
326
327
get ratingCount(): number | undefined {
328
return this.gallery ? this.gallery.ratingCount : undefined;
329
}
330
331
get ratingUrl(): string | undefined {
332
return this.gallery?.ratingLink;
333
}
334
335
get outdated(): boolean {
336
try {
337
if (!this.gallery || !this.local) {
338
return false;
339
}
340
// Do not allow updating system extensions in stable
341
if (this.type === ExtensionType.System && this.productService.quality === 'stable') {
342
return false;
343
}
344
if (!this.local.preRelease && this.gallery.properties.isPreReleaseVersion) {
345
return false;
346
}
347
if (semver.gt(this.latestVersion, this.version)) {
348
return true;
349
}
350
if (this.outdatedTargetPlatform) {
351
return true;
352
}
353
} catch (error) {
354
/* Ignore */
355
}
356
return false;
357
}
358
359
get outdatedTargetPlatform(): boolean {
360
return !!this.local && !!this.gallery
361
&& ![TargetPlatform.UNDEFINED, TargetPlatform.WEB].includes(this.local.targetPlatform)
362
&& this.gallery.properties.targetPlatform !== TargetPlatform.WEB
363
&& this.local.targetPlatform !== this.gallery.properties.targetPlatform
364
&& semver.eq(this.latestVersion, this.version);
365
}
366
367
get runtimeState(): ExtensionRuntimeState | undefined {
368
return this.runtimeStateProvider(this);
369
}
370
371
get telemetryData(): any {
372
const { local, gallery } = this;
373
374
if (gallery) {
375
return getGalleryExtensionTelemetryData(gallery);
376
} else if (local) {
377
return getLocalExtensionTelemetryData(local);
378
} else {
379
return {};
380
}
381
}
382
383
get preview(): boolean {
384
return this.local?.manifest.preview ?? this.gallery?.preview ?? false;
385
}
386
387
get preRelease(): boolean {
388
return !!this.local?.preRelease;
389
}
390
391
get isPreReleaseVersion(): boolean {
392
if (this.local) {
393
return this.local.isPreReleaseVersion;
394
}
395
return !!this.gallery?.properties.isPreReleaseVersion;
396
}
397
398
get hasPreReleaseVersion(): boolean {
399
return this.gallery ? this.gallery.hasPreReleaseVersion : !!this.local?.hasPreReleaseVersion;
400
}
401
402
get hasReleaseVersion(): boolean {
403
return !!this.resourceExtension || !!this.gallery?.hasReleaseVersion;
404
}
405
406
private getLocal(): ILocalExtension | undefined {
407
return this.local && !this.outdated ? this.local : undefined;
408
}
409
410
async getManifest(token: CancellationToken): Promise<IExtensionManifest | null> {
411
const local = this.getLocal();
412
if (local) {
413
return local.manifest;
414
}
415
416
if (this.gallery) {
417
return this.getGalleryManifest(token);
418
}
419
420
if (this.resourceExtension) {
421
return this.resourceExtension.manifest;
422
}
423
424
return null;
425
}
426
427
async getGalleryManifest(token: CancellationToken = CancellationToken.None): Promise<IExtensionManifest | null> {
428
if (this.gallery) {
429
let cache = this.galleryResourcesCache.get('manifest');
430
if (!cache) {
431
if (this.gallery.assets.manifest) {
432
this.galleryResourcesCache.set('manifest', cache = this.galleryService.getManifest(this.gallery, token)
433
.catch(e => {
434
this.galleryResourcesCache.delete('manifest');
435
throw e;
436
}));
437
} else {
438
this.logService.error(nls.localize('Manifest is not found', "Manifest is not found"), this.identifier.id);
439
}
440
}
441
return cache;
442
}
443
return null;
444
}
445
446
hasReadme(): boolean {
447
if (this.local && this.local.readmeUrl) {
448
return true;
449
}
450
451
if (this.gallery && this.gallery.assets.readme) {
452
return true;
453
}
454
455
if (this.resourceExtension?.readmeUri) {
456
return true;
457
}
458
459
return this.type === ExtensionType.System;
460
}
461
462
async getReadme(token: CancellationToken): Promise<string> {
463
const local = this.getLocal();
464
if (local?.readmeUrl) {
465
const content = await this.fileService.readFile(local.readmeUrl);
466
return content.value.toString();
467
}
468
469
if (this.gallery) {
470
if (this.gallery.assets.readme) {
471
return this.galleryService.getReadme(this.gallery, token);
472
}
473
this.telemetryService.publicLog('extensions:NotFoundReadMe', this.telemetryData);
474
}
475
476
if (this.type === ExtensionType.System) {
477
return Promise.resolve(`# ${this.displayName || this.name}
478
**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled.
479
## Features
480
${this.description}
481
`);
482
}
483
484
if (this.resourceExtension?.readmeUri) {
485
const content = await this.fileService.readFile(this.resourceExtension?.readmeUri);
486
return content.value.toString();
487
}
488
489
return Promise.reject(new Error('not available'));
490
}
491
492
hasChangelog(): boolean {
493
if (this.local && this.local.changelogUrl) {
494
return true;
495
}
496
497
if (this.gallery && this.gallery.assets.changelog) {
498
return true;
499
}
500
501
return this.type === ExtensionType.System;
502
}
503
504
async getChangelog(token: CancellationToken): Promise<string> {
505
const local = this.getLocal();
506
if (local?.changelogUrl) {
507
const content = await this.fileService.readFile(local.changelogUrl);
508
return content.value.toString();
509
}
510
511
if (this.gallery?.assets.changelog) {
512
return this.galleryService.getChangelog(this.gallery, token);
513
}
514
515
if (this.type === ExtensionType.System) {
516
return Promise.resolve(`Please check the [VS Code Release Notes](command:${ShowCurrentReleaseNotesActionId}) for changes to the built-in extensions.`);
517
}
518
519
return Promise.reject(new Error('not available'));
520
}
521
522
get categories(): readonly string[] {
523
const { local, gallery, resourceExtension } = this;
524
if (local && local.manifest.categories && !this.outdated) {
525
return local.manifest.categories;
526
}
527
if (gallery) {
528
return gallery.categories;
529
}
530
if (resourceExtension) {
531
return resourceExtension.manifest.categories ?? [];
532
}
533
return [];
534
}
535
536
get tags(): readonly string[] {
537
const { gallery } = this;
538
if (gallery) {
539
return gallery.tags.filter(tag => !tag.startsWith('_'));
540
}
541
return [];
542
}
543
544
get dependencies(): string[] {
545
const { local, gallery, resourceExtension } = this;
546
if (local && local.manifest.extensionDependencies && !this.outdated) {
547
return local.manifest.extensionDependencies;
548
}
549
if (gallery) {
550
return gallery.properties.dependencies || [];
551
}
552
if (resourceExtension) {
553
return resourceExtension.manifest.extensionDependencies || [];
554
}
555
return [];
556
}
557
558
get extensionPack(): string[] {
559
const { local, gallery, resourceExtension } = this;
560
if (local && local.manifest.extensionPack && !this.outdated) {
561
return local.manifest.extensionPack;
562
}
563
if (gallery) {
564
return gallery.properties.extensionPack || [];
565
}
566
if (resourceExtension) {
567
return resourceExtension.manifest.extensionPack || [];
568
}
569
return [];
570
}
571
572
setExtensionsControlManifest(extensionsControlManifest: IExtensionsControlManifest): void {
573
this.malicious = findMatchingMaliciousEntry(this.identifier, extensionsControlManifest.malicious);
574
this.deprecationInfo = extensionsControlManifest.deprecated ? extensionsControlManifest.deprecated[this.identifier.id.toLowerCase()] : undefined;
575
}
576
577
private getManifestFromLocalOrResource(): IExtensionManifest | null {
578
if (this.local) {
579
return this.local.manifest;
580
}
581
if (this.resourceExtension) {
582
return this.resourceExtension.manifest;
583
}
584
return null;
585
}
586
}
587
588
const EXTENSIONS_AUTO_UPDATE_KEY = 'extensions.autoUpdate';
589
const EXTENSIONS_DONOT_AUTO_UPDATE_KEY = 'extensions.donotAutoUpdate';
590
const EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY = 'extensions.dismissedNotifications';
591
592
class Extensions extends Disposable {
593
594
private readonly _onChange = this._register(new Emitter<{ extension: Extension; operation?: InstallOperation } | undefined>());
595
get onChange() { return this._onChange.event; }
596
597
private readonly _onReset = this._register(new Emitter<void>());
598
get onReset() { return this._onReset.event; }
599
600
private installing: Extension[] = [];
601
private uninstalling: Extension[] = [];
602
private installed: Extension[] = [];
603
604
constructor(
605
readonly server: IExtensionManagementServer,
606
private readonly stateProvider: IExtensionStateProvider<ExtensionState>,
607
private readonly runtimeStateProvider: IExtensionStateProvider<ExtensionRuntimeState | undefined>,
608
private readonly isWorkspaceServer: boolean,
609
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
610
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
611
@IWorkbenchExtensionManagementService private readonly workbenchExtensionManagementService: IWorkbenchExtensionManagementService,
612
@ITelemetryService private readonly telemetryService: ITelemetryService,
613
@IInstantiationService private readonly instantiationService: IInstantiationService
614
) {
615
super();
616
this._register(server.extensionManagementService.onInstallExtension(e => this.onInstallExtension(e)));
617
this._register(server.extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e)));
618
this._register(server.extensionManagementService.onUninstallExtension(e => this.onUninstallExtension(e.identifier)));
619
this._register(server.extensionManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e)));
620
this._register(server.extensionManagementService.onDidUpdateExtensionMetadata(e => this.onDidUpdateExtensionMetadata(e.local)));
621
this._register(server.extensionManagementService.onDidChangeProfile(() => this.reset()));
622
this._register(extensionEnablementService.onEnablementChanged(e => this.onEnablementChanged(e)));
623
this._register(Event.any(this.onChange, this.onReset)(() => this._local = undefined));
624
if (this.isWorkspaceServer) {
625
this._register(this.workbenchExtensionManagementService.onInstallExtension(e => {
626
if (e.workspaceScoped) {
627
this.onInstallExtension(e);
628
}
629
}));
630
this._register(this.workbenchExtensionManagementService.onDidInstallExtensions(e => {
631
const result = e.filter(e => e.workspaceScoped);
632
if (result.length) {
633
this.onDidInstallExtensions(result);
634
}
635
}));
636
this._register(this.workbenchExtensionManagementService.onUninstallExtension(e => {
637
if (e.workspaceScoped) {
638
this.onUninstallExtension(e.identifier);
639
}
640
}));
641
this._register(this.workbenchExtensionManagementService.onDidUninstallExtension(e => {
642
if (e.workspaceScoped) {
643
this.onDidUninstallExtension(e);
644
}
645
}));
646
}
647
}
648
649
private _local: Extension[] | undefined;
650
get local(): Extension[] {
651
if (!this._local) {
652
this._local = [];
653
for (const extension of this.installed) {
654
this._local.push(extension);
655
}
656
for (const extension of this.installing) {
657
if (!this.installed.some(installed => areSameExtensions(installed.identifier, extension.identifier))) {
658
this._local.push(extension);
659
}
660
}
661
}
662
return this._local;
663
}
664
665
async queryInstalled(productVersion: IProductVersion): Promise<IExtension[]> {
666
await this.fetchInstalledExtensions(productVersion);
667
this._onChange.fire(undefined);
668
return this.local;
669
}
670
671
async syncInstalledExtensionsWithGallery(galleryExtensions: IGalleryExtension[], productVersion: IProductVersion, flagExtensionsMissingFromGallery?: IExtensionInfo[]): Promise<void> {
672
const extensions = await this.mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions, productVersion);
673
for (const [extension, gallery] of extensions) {
674
// update metadata of the extension if it does not exist
675
if (extension.local && !extension.local.identifier.uuid) {
676
extension.local = await this.updateMetadata(extension.local, gallery);
677
}
678
if (!extension.gallery || extension.gallery.version !== gallery.version || extension.gallery.properties.targetPlatform !== gallery.properties.targetPlatform) {
679
extension.gallery = gallery;
680
this._onChange.fire({ extension });
681
}
682
}
683
// Detect extensions that do not have a corresponding gallery entry.
684
if (flagExtensionsMissingFromGallery) {
685
const extensionsToQuery = [];
686
for (const extension of this.local) {
687
// Extension is already paired with a gallery object
688
if (extension.gallery) {
689
continue;
690
}
691
// Already flagged as missing from gallery
692
if (extension.missingFromGallery) {
693
continue;
694
}
695
// A UUID indicates extension originated from gallery
696
if (!extension.identifier.uuid) {
697
continue;
698
}
699
// Extension is not present in the set we are concerned about
700
if (!flagExtensionsMissingFromGallery.some(f => areSameExtensions(f, extension.identifier))) {
701
continue;
702
}
703
extensionsToQuery.push(extension);
704
}
705
if (extensionsToQuery.length) {
706
const queryResult = await this.galleryService.getExtensions(extensionsToQuery.map(e => ({ ...e.identifier, version: e.version })), CancellationToken.None);
707
const queriedIds: string[] = [];
708
const missingIds: string[] = [];
709
for (const extension of extensionsToQuery) {
710
queriedIds.push(extension.identifier.id);
711
const gallery = queryResult.find(g => areSameExtensions(g.identifier, extension.identifier));
712
if (gallery) {
713
extension.gallery = gallery;
714
} else {
715
extension.missingFromGallery = true;
716
missingIds.push(extension.identifier.id);
717
}
718
this._onChange.fire({ extension });
719
}
720
type MissingFromGalleryClassification = {
721
owner: 'joshspicer';
722
comment: 'Report when installed extensions are no longer available in the gallery';
723
queriedIds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extensions queried as potentially missing from gallery' };
724
missingIds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extensions determined missing from gallery' };
725
};
726
type MissingFromGalleryEvent = {
727
readonly queriedIds: TelemetryTrustedValue<string>;
728
readonly missingIds: TelemetryTrustedValue<string>;
729
};
730
this.telemetryService.publicLog2<MissingFromGalleryEvent, MissingFromGalleryClassification>('extensions:missingFromGallery', {
731
queriedIds: new TelemetryTrustedValue(queriedIds.join(';')),
732
missingIds: new TelemetryTrustedValue(missingIds.join(';'))
733
});
734
}
735
}
736
}
737
738
private async mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions: IGalleryExtension[], productVersion: IProductVersion): Promise<[Extension, IGalleryExtension][]> {
739
const mappedExtensions = this.mapInstalledExtensionWithGalleryExtension(galleryExtensions);
740
const targetPlatform = await this.server.extensionManagementService.getTargetPlatform();
741
const compatibleGalleryExtensions: IGalleryExtension[] = [];
742
const compatibleGalleryExtensionsToFetch: IExtensionInfo[] = [];
743
await Promise.allSettled(mappedExtensions.map(async ([extension, gallery]) => {
744
if (extension.local) {
745
if (await this.galleryService.isExtensionCompatible(gallery, extension.local.preRelease, targetPlatform, productVersion)) {
746
compatibleGalleryExtensions.push(gallery);
747
} else {
748
compatibleGalleryExtensionsToFetch.push({ ...extension.local.identifier, preRelease: extension.local.preRelease });
749
}
750
}
751
}));
752
if (compatibleGalleryExtensionsToFetch.length) {
753
const result = await this.galleryService.getExtensions(compatibleGalleryExtensionsToFetch, { targetPlatform, compatible: true, queryAllVersions: true, productVersion }, CancellationToken.None);
754
compatibleGalleryExtensions.push(...result);
755
}
756
return this.mapInstalledExtensionWithGalleryExtension(compatibleGalleryExtensions);
757
}
758
759
private mapInstalledExtensionWithGalleryExtension(galleryExtensions: IGalleryExtension[]): [Extension, IGalleryExtension][] {
760
const mappedExtensions: [Extension, IGalleryExtension][] = [];
761
const byUUID = new Map<string, IGalleryExtension>(), byID = new Map<string, IGalleryExtension>();
762
for (const gallery of galleryExtensions) {
763
byUUID.set(gallery.identifier.uuid, gallery);
764
byID.set(gallery.identifier.id.toLowerCase(), gallery);
765
}
766
for (const installed of this.installed) {
767
if (installed.uuid) {
768
const gallery = byUUID.get(installed.uuid);
769
if (gallery) {
770
mappedExtensions.push([installed, gallery]);
771
continue;
772
}
773
}
774
if (installed.local?.source !== 'resource') {
775
const gallery = byID.get(installed.identifier.id.toLowerCase());
776
if (gallery) {
777
mappedExtensions.push([installed, gallery]);
778
}
779
}
780
}
781
return mappedExtensions;
782
}
783
784
private async updateMetadata(localExtension: ILocalExtension, gallery: IGalleryExtension): Promise<ILocalExtension> {
785
let isPreReleaseVersion = false;
786
if (localExtension.manifest.version !== gallery.version) {
787
type GalleryServiceMatchInstalledExtensionClassification = {
788
owner: 'sandy081';
789
comment: 'Report when a request is made to update metadata of an installed extension';
790
};
791
this.telemetryService.publicLog2<{}, GalleryServiceMatchInstalledExtensionClassification>('galleryService:updateMetadata');
792
const galleryWithLocalVersion: IGalleryExtension | undefined = (await this.galleryService.getExtensions([{ ...localExtension.identifier, version: localExtension.manifest.version }], CancellationToken.None))[0];
793
isPreReleaseVersion = !!galleryWithLocalVersion?.properties?.isPreReleaseVersion;
794
}
795
return this.workbenchExtensionManagementService.updateMetadata(localExtension, { id: gallery.identifier.uuid, publisherDisplayName: gallery.publisherDisplayName, publisherId: gallery.publisherId, isPreReleaseVersion });
796
}
797
798
canInstall(galleryExtension: IGalleryExtension): Promise<true | IMarkdownString> {
799
return this.server.extensionManagementService.canInstall(galleryExtension);
800
}
801
802
private onInstallExtension(event: InstallExtensionEvent): void {
803
const { source } = event;
804
if (source && !URI.isUri(source)) {
805
const extension = this.installed.find(e => areSameExtensions(e.identifier, source.identifier))
806
?? this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, undefined, source, undefined);
807
this.installing.push(extension);
808
this._onChange.fire({ extension });
809
}
810
}
811
812
private async fetchInstalledExtensions(productVersion?: IProductVersion): Promise<void> {
813
const extensionsControlManifest = await this.server.extensionManagementService.getExtensionsControlManifest();
814
const all = await this.server.extensionManagementService.getInstalled(undefined, undefined, productVersion);
815
if (this.isWorkspaceServer) {
816
all.push(...await this.workbenchExtensionManagementService.getInstalledWorkspaceExtensions(true));
817
}
818
819
// dedup workspace, user and system extensions by giving priority to workspace first and then to user extension.
820
const installed = groupByExtension(all, r => r.identifier).reduce((result, extensions) => {
821
if (extensions.length === 1) {
822
result.push(extensions[0]);
823
} else {
824
let workspaceExtension: ILocalExtension | undefined,
825
userExtension: ILocalExtension | undefined,
826
systemExtension: ILocalExtension | undefined;
827
for (const extension of extensions) {
828
if (extension.isWorkspaceScoped) {
829
workspaceExtension = extension;
830
} else if (extension.type === ExtensionType.User) {
831
userExtension = extension;
832
} else {
833
systemExtension = extension;
834
}
835
}
836
const extension = workspaceExtension ?? userExtension ?? systemExtension;
837
if (extension) {
838
result.push(extension);
839
}
840
}
841
return result;
842
}, []);
843
844
const byId = index(this.installed, e => e.local ? e.local.identifier.id : e.identifier.id);
845
this.installed = installed.map(local => {
846
const extension = byId[local.identifier.id] || this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, local, undefined, undefined);
847
extension.local = local;
848
extension.enablementState = this.extensionEnablementService.getEnablementState(local);
849
extension.setExtensionsControlManifest(extensionsControlManifest);
850
return extension;
851
});
852
}
853
854
private async reset(): Promise<void> {
855
this.installed = [];
856
this.installing = [];
857
this.uninstalling = [];
858
await this.fetchInstalledExtensions();
859
this._onReset.fire();
860
}
861
862
private async onDidInstallExtensions(results: readonly InstallExtensionResult[]): Promise<void> {
863
const extensions: Extension[] = [];
864
for (const event of results) {
865
const { local, source } = event;
866
const gallery = source && !URI.isUri(source) ? source : undefined;
867
const location = source && URI.isUri(source) ? source : undefined;
868
const installingExtension = gallery ? this.installing.filter(e => areSameExtensions(e.identifier, gallery.identifier))[0] : null;
869
this.installing = installingExtension ? this.installing.filter(e => e !== installingExtension) : this.installing;
870
871
let extension: Extension | undefined = installingExtension ? installingExtension
872
: (location || local) ? this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, local, undefined, undefined)
873
: undefined;
874
if (extension) {
875
if (local) {
876
const installed = this.installed.filter(e => areSameExtensions(e.identifier, extension!.identifier))[0];
877
if (installed) {
878
extension = installed;
879
} else {
880
this.installed.push(extension);
881
}
882
extension.local = local;
883
if (!extension.gallery) {
884
extension.gallery = gallery;
885
}
886
extension.enablementState = this.extensionEnablementService.getEnablementState(local);
887
}
888
extensions.push(extension);
889
}
890
this._onChange.fire(!local || !extension ? undefined : { extension, operation: event.operation });
891
}
892
893
if (extensions.length) {
894
const manifest = await this.server.extensionManagementService.getExtensionsControlManifest();
895
for (const extension of extensions) {
896
extension.setExtensionsControlManifest(manifest);
897
}
898
this.matchInstalledExtensionsWithGallery(extensions);
899
}
900
}
901
902
private async onDidUpdateExtensionMetadata(local: ILocalExtension): Promise<void> {
903
const extension = this.installed.find(e => areSameExtensions(e.identifier, local.identifier));
904
if (extension?.local) {
905
extension.local = local;
906
this._onChange.fire({ extension });
907
}
908
}
909
910
private async matchInstalledExtensionsWithGallery(extensions: Extension[]): Promise<void> {
911
const toMatch = extensions.filter(e => e.local && !e.gallery && e.local.source !== 'resource');
912
if (!toMatch.length) {
913
return;
914
}
915
if (!this.galleryService.isEnabled()) {
916
return;
917
}
918
const galleryExtensions = await this.galleryService.getExtensions(toMatch.map(e => ({ ...e.identifier, preRelease: e.local?.preRelease })), { compatible: true, targetPlatform: await this.server.extensionManagementService.getTargetPlatform() }, CancellationToken.None);
919
for (const extension of extensions) {
920
const compatible = galleryExtensions.find(e => areSameExtensions(e.identifier, extension.identifier));
921
if (compatible) {
922
extension.gallery = compatible;
923
this._onChange.fire({ extension });
924
}
925
}
926
}
927
928
private onUninstallExtension(identifier: IExtensionIdentifier): void {
929
const extension = this.installed.filter(e => areSameExtensions(e.identifier, identifier))[0];
930
if (extension) {
931
const uninstalling = this.uninstalling.filter(e => areSameExtensions(e.identifier, identifier))[0] || extension;
932
this.uninstalling = [uninstalling, ...this.uninstalling.filter(e => !areSameExtensions(e.identifier, identifier))];
933
this._onChange.fire(uninstalling ? { extension: uninstalling } : undefined);
934
}
935
}
936
937
private onDidUninstallExtension({ identifier, error }: DidUninstallExtensionEvent): void {
938
const uninstalled = this.uninstalling.find(e => areSameExtensions(e.identifier, identifier)) || this.installed.find(e => areSameExtensions(e.identifier, identifier));
939
this.uninstalling = this.uninstalling.filter(e => !areSameExtensions(e.identifier, identifier));
940
if (!error) {
941
this.installed = this.installed.filter(e => !areSameExtensions(e.identifier, identifier));
942
}
943
if (uninstalled) {
944
this._onChange.fire({ extension: uninstalled });
945
}
946
}
947
948
private onEnablementChanged(platformExtensions: readonly IPlatformExtension[]) {
949
const extensions = this.local.filter(e => platformExtensions.some(p => areSameExtensions(e.identifier, p.identifier)));
950
for (const extension of extensions) {
951
if (extension.local) {
952
const enablementState = this.extensionEnablementService.getEnablementState(extension.local);
953
if (enablementState !== extension.enablementState) {
954
extension.enablementState = enablementState;
955
this._onChange.fire({ extension });
956
}
957
}
958
}
959
}
960
961
getExtensionState(extension: Extension): ExtensionState {
962
if (extension.gallery && this.installing.some(e => !!e.gallery && areSameExtensions(e.gallery.identifier, extension.gallery!.identifier))) {
963
return ExtensionState.Installing;
964
}
965
if (this.uninstalling.some(e => areSameExtensions(e.identifier, extension.identifier))) {
966
return ExtensionState.Uninstalling;
967
}
968
const local = this.installed.filter(e => e === extension || (e.gallery && extension.gallery && areSameExtensions(e.gallery.identifier, extension.gallery.identifier)))[0];
969
return local ? ExtensionState.Installed : ExtensionState.Uninstalled;
970
}
971
}
972
973
export class ExtensionsWorkbenchService extends Disposable implements IExtensionsWorkbenchService, IURLHandler {
974
975
private static readonly UpdatesCheckInterval = 1000 * 60 * 60 * 12; // 12 hours
976
declare readonly _serviceBrand: undefined;
977
978
private hasOutdatedExtensionsContextKey: IContextKey<boolean>;
979
980
private readonly localExtensions: Extensions | null = null;
981
private readonly remoteExtensions: Extensions | null = null;
982
private readonly webExtensions: Extensions | null = null;
983
private readonly extensionsServers: Extensions[] = [];
984
985
private updatesCheckDelayer: ThrottledDelayer<void>;
986
private autoUpdateDelayer: ThrottledDelayer<void>;
987
988
private readonly _onChange = this._register(new Emitter<IExtension | undefined>());
989
get onChange(): Event<IExtension | undefined> { return this._onChange.event; }
990
991
private extensionsNotification: IExtensionsNotification & { readonly key: string } | undefined;
992
private readonly _onDidChangeExtensionsNotification = new Emitter<IExtensionsNotification | undefined>();
993
readonly onDidChangeExtensionsNotification = this._onDidChangeExtensionsNotification.event;
994
995
private readonly _onReset = new Emitter<void>();
996
get onReset() { return this._onReset.event; }
997
998
private installing: IExtension[] = [];
999
private tasksInProgress: CancelablePromise<any>[] = [];
1000
1001
readonly whenInitialized: Promise<void>;
1002
1003
constructor(
1004
@IInstantiationService private readonly instantiationService: IInstantiationService,
1005
@IEditorService private readonly editorService: IEditorService,
1006
@IWorkbenchExtensionManagementService private readonly extensionManagementService: IWorkbenchExtensionManagementService,
1007
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
1008
@IExtensionGalleryManifestService private readonly extensionGalleryManifestService: IExtensionGalleryManifestService,
1009
@IConfigurationService private readonly configurationService: IConfigurationService,
1010
@ITelemetryService private readonly telemetryService: ITelemetryService,
1011
@INotificationService private readonly notificationService: INotificationService,
1012
@IURLService urlService: IURLService,
1013
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
1014
@IHostService private readonly hostService: IHostService,
1015
@IProgressService private readonly progressService: IProgressService,
1016
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
1017
@ILanguageService private readonly languageService: ILanguageService,
1018
@IIgnoredExtensionsManagementService private readonly extensionsSyncManagementService: IIgnoredExtensionsManagementService,
1019
@IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService,
1020
@IProductService private readonly productService: IProductService,
1021
@IContextKeyService contextKeyService: IContextKeyService,
1022
@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,
1023
@ILogService private readonly logService: ILogService,
1024
@IExtensionService private readonly extensionService: IExtensionService,
1025
@ILocaleService private readonly localeService: ILocaleService,
1026
@ILifecycleService private readonly lifecycleService: ILifecycleService,
1027
@IFileService private readonly fileService: IFileService,
1028
@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,
1029
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
1030
@IStorageService private readonly storageService: IStorageService,
1031
@IDialogService private readonly dialogService: IDialogService,
1032
@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
1033
@IUpdateService private readonly updateService: IUpdateService,
1034
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
1035
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
1036
@IViewsService private readonly viewsService: IViewsService,
1037
@IFileDialogService private readonly fileDialogService: IFileDialogService,
1038
@IQuickInputService private readonly quickInputService: IQuickInputService,
1039
@IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService,
1040
) {
1041
super();
1042
1043
this.hasOutdatedExtensionsContextKey = HasOutdatedExtensionsContext.bindTo(contextKeyService);
1044
if (extensionManagementServerService.localExtensionManagementServer) {
1045
this.localExtensions = this._register(instantiationService.createInstance(Extensions,
1046
extensionManagementServerService.localExtensionManagementServer,
1047
ext => this.getExtensionState(ext),
1048
ext => this.getRuntimeState(ext),
1049
!extensionManagementServerService.remoteExtensionManagementServer
1050
));
1051
this._register(this.localExtensions.onChange(e => this.onDidChangeExtensions(e?.extension)));
1052
this._register(this.localExtensions.onReset(e => this.reset()));
1053
this.extensionsServers.push(this.localExtensions);
1054
}
1055
if (extensionManagementServerService.remoteExtensionManagementServer) {
1056
this.remoteExtensions = this._register(instantiationService.createInstance(Extensions,
1057
extensionManagementServerService.remoteExtensionManagementServer,
1058
ext => this.getExtensionState(ext),
1059
ext => this.getRuntimeState(ext),
1060
true
1061
));
1062
this._register(this.remoteExtensions.onChange(e => this.onDidChangeExtensions(e?.extension)));
1063
this._register(this.remoteExtensions.onReset(e => this.reset()));
1064
this.extensionsServers.push(this.remoteExtensions);
1065
}
1066
if (extensionManagementServerService.webExtensionManagementServer) {
1067
this.webExtensions = this._register(instantiationService.createInstance(Extensions,
1068
extensionManagementServerService.webExtensionManagementServer,
1069
ext => this.getExtensionState(ext),
1070
ext => this.getRuntimeState(ext),
1071
!(extensionManagementServerService.remoteExtensionManagementServer || extensionManagementServerService.localExtensionManagementServer)
1072
));
1073
this._register(this.webExtensions.onChange(e => this.onDidChangeExtensions(e?.extension)));
1074
this._register(this.webExtensions.onReset(e => this.reset()));
1075
this.extensionsServers.push(this.webExtensions);
1076
}
1077
1078
this.updatesCheckDelayer = new ThrottledDelayer<void>(ExtensionsWorkbenchService.UpdatesCheckInterval);
1079
this.autoUpdateDelayer = new ThrottledDelayer<void>(1000);
1080
this._register(toDisposable(() => {
1081
this.updatesCheckDelayer.cancel();
1082
this.autoUpdateDelayer.cancel();
1083
}));
1084
1085
urlService.registerHandler(this);
1086
1087
this.whenInitialized = this.initialize();
1088
}
1089
1090
private async initialize(): Promise<void> {
1091
// initialize local extensions
1092
await Promise.all([this.queryLocal(), this.extensionService.whenInstalledExtensionsRegistered()]);
1093
if (this._store.isDisposed) {
1094
return;
1095
}
1096
this.onDidChangeRunningExtensions(this.extensionService.extensions, []);
1097
this._register(this.extensionService.onDidChangeExtensions(({ added, removed }) => this.onDidChangeRunningExtensions(added, removed)));
1098
1099
await this.lifecycleService.when(LifecyclePhase.Eventually);
1100
if (this._store.isDisposed) {
1101
return;
1102
}
1103
1104
this.initializeAutoUpdate();
1105
this.updateExtensionsNotificaiton();
1106
this.reportInstalledExtensionsTelemetry();
1107
this._register(this.storageService.onDidChangeValue(StorageScope.PROFILE, EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY, this._store)(e => this.onDidDismissedNotificationsValueChange()));
1108
this._register(this.storageService.onDidChangeValue(StorageScope.APPLICATION, EXTENSIONS_AUTO_UPDATE_KEY, this._store)(e => this.onDidSelectedExtensionToAutoUpdateValueChange()));
1109
this._register(this.storageService.onDidChangeValue(StorageScope.APPLICATION, EXTENSIONS_DONOT_AUTO_UPDATE_KEY, this._store)(e => this.onDidSelectedExtensionToAutoUpdateValueChange()));
1110
this._register(Event.debounce(this.onChange, () => undefined, 100)(() => {
1111
this.updateExtensionsNotificaiton();
1112
this.reportProgressFromOtherSources();
1113
}));
1114
}
1115
1116
private initializeAutoUpdate(): void {
1117
// Register listeners for auto updates
1118
this._register(this.configurationService.onDidChangeConfiguration(e => {
1119
if (e.affectsConfiguration(AutoUpdateConfigurationKey)) {
1120
if (this.isAutoUpdateEnabled()) {
1121
this.eventuallyAutoUpdateExtensions();
1122
}
1123
}
1124
if (e.affectsConfiguration(AutoCheckUpdatesConfigurationKey)) {
1125
if (this.isAutoCheckUpdatesEnabled()) {
1126
this.checkForUpdates(`Enabled auto check updates`);
1127
}
1128
}
1129
}));
1130
this._register(this.extensionEnablementService.onEnablementChanged(platformExtensions => {
1131
if (this.getAutoUpdateValue() === 'onlyEnabledExtensions' && platformExtensions.some(e => this.extensionEnablementService.isEnabled(e))) {
1132
this.checkForUpdates('Extension enablement changed');
1133
}
1134
}));
1135
this._register(Event.debounce(this.onChange, () => undefined, 100)(() => this.hasOutdatedExtensionsContextKey.set(this.outdated.length > 0)));
1136
this._register(this.updateService.onStateChange(e => {
1137
if ((e.type === StateType.CheckingForUpdates && e.explicit) || e.type === StateType.AvailableForDownload || e.type === StateType.Downloaded) {
1138
this.telemetryService.publicLog2<{}, {
1139
owner: 'sandy081';
1140
comment: 'Report when update check is triggered on product update';
1141
}>('extensions:updatecheckonproductupdate');
1142
if (this.isAutoCheckUpdatesEnabled()) {
1143
this.checkForUpdates('Product update');
1144
}
1145
}
1146
}));
1147
1148
this._register(this.allowedExtensionsService.onDidChangeAllowedExtensionsConfigValue(() => {
1149
if (this.isAutoCheckUpdatesEnabled()) {
1150
this.checkForUpdates('Allowed extensions changed');
1151
}
1152
}));
1153
1154
// Update AutoUpdate Contexts
1155
this.hasOutdatedExtensionsContextKey.set(this.outdated.length > 0);
1156
1157
// Check for updates
1158
this.eventuallyCheckForUpdates(true);
1159
1160
if (isWeb) {
1161
this.syncPinnedBuiltinExtensions();
1162
// Always auto update builtin extensions in web
1163
if (!this.isAutoUpdateEnabled()) {
1164
this.autoUpdateBuiltinExtensions();
1165
}
1166
}
1167
1168
this.registerAutoRestartListener();
1169
this._register(this.configurationService.onDidChangeConfiguration(e => {
1170
if (e.affectsConfiguration(AutoRestartConfigurationKey)) {
1171
this.registerAutoRestartListener();
1172
}
1173
}));
1174
}
1175
1176
private isAutoUpdateEnabled(): boolean {
1177
return this.getAutoUpdateValue() !== false;
1178
}
1179
1180
getAutoUpdateValue(): AutoUpdateConfigurationValue {
1181
const autoUpdate = this.configurationService.getValue<AutoUpdateConfigurationValue>(AutoUpdateConfigurationKey);
1182
if (<any>autoUpdate === 'onlySelectedExtensions') {
1183
return false;
1184
}
1185
return isBoolean(autoUpdate) || autoUpdate === 'onlyEnabledExtensions' ? autoUpdate : true;
1186
}
1187
1188
async updateAutoUpdateForAllExtensions(isAutoUpdateEnabled: boolean): Promise<void> {
1189
const wasAutoUpdateEnabled = this.isAutoUpdateEnabled();
1190
if (wasAutoUpdateEnabled === isAutoUpdateEnabled) {
1191
return;
1192
}
1193
1194
const result = await this.dialogService.confirm({
1195
title: nls.localize('confirmEnableDisableAutoUpdate', "Auto Update Extensions"),
1196
message: isAutoUpdateEnabled
1197
? nls.localize('confirmEnableAutoUpdate', "Do you want to enable auto update for all extensions?")
1198
: nls.localize('confirmDisableAutoUpdate', "Do you want to disable auto update for all extensions?"),
1199
detail: nls.localize('confirmEnableDisableAutoUpdateDetail', "This will reset any auto update settings you have set for individual extensions."),
1200
});
1201
if (!result.confirmed) {
1202
return;
1203
}
1204
1205
// Reset extensions enabled for auto update first to prevent them from being updated
1206
this.setEnabledAutoUpdateExtensions([]);
1207
1208
await this.configurationService.updateValue(AutoUpdateConfigurationKey, isAutoUpdateEnabled);
1209
1210
this.setDisabledAutoUpdateExtensions([]);
1211
await this.updateExtensionsPinnedState(!isAutoUpdateEnabled);
1212
this._onChange.fire(undefined);
1213
}
1214
1215
private readonly autoRestartListenerDisposable = this._register(new MutableDisposable());
1216
private registerAutoRestartListener(): void {
1217
this.autoRestartListenerDisposable.value = undefined;
1218
if (this.configurationService.getValue(AutoRestartConfigurationKey) === true) {
1219
this.autoRestartListenerDisposable.value = this.hostService.onDidChangeFocus(focus => {
1220
if (!focus && this.configurationService.getValue(AutoRestartConfigurationKey) === true) {
1221
this.updateRunningExtensions(undefined, true);
1222
}
1223
});
1224
}
1225
}
1226
1227
private reportInstalledExtensionsTelemetry() {
1228
const extensionIds = this.installed.filter(extension =>
1229
!extension.isBuiltin &&
1230
(extension.enablementState === EnablementState.EnabledWorkspace ||
1231
extension.enablementState === EnablementState.EnabledGlobally))
1232
.map(extension => ExtensionIdentifier.toKey(extension.identifier.id));
1233
this.telemetryService.publicLog2<InstalledExtensionsEvent, ExtensionsLoadClassification>('installedExtensions', { extensionIds: new TelemetryTrustedValue(extensionIds.join(';')), count: extensionIds.length });
1234
}
1235
1236
private async onDidChangeRunningExtensions(added: ReadonlyArray<IExtensionDescription>, removed: ReadonlyArray<IExtensionDescription>): Promise<void> {
1237
const changedExtensions: IExtension[] = [];
1238
const extensionsToFetch: IExtensionDescription[] = [];
1239
for (const desc of added) {
1240
const extension = this.installed.find(e => areSameExtensions({ id: desc.identifier.value, uuid: desc.uuid }, e.identifier));
1241
if (extension) {
1242
changedExtensions.push(extension);
1243
} else {
1244
extensionsToFetch.push(desc);
1245
}
1246
}
1247
const workspaceExtensions: IExtensionDescription[] = [];
1248
for (const desc of removed) {
1249
if (this.workspaceContextService.isInsideWorkspace(desc.extensionLocation)) {
1250
workspaceExtensions.push(desc);
1251
} else {
1252
extensionsToFetch.push(desc);
1253
}
1254
}
1255
if (extensionsToFetch.length) {
1256
const extensions = await this.getExtensions(extensionsToFetch.map(e => ({ id: e.identifier.value, uuid: e.uuid })), CancellationToken.None);
1257
changedExtensions.push(...extensions);
1258
}
1259
if (workspaceExtensions.length) {
1260
const extensions = await this.getResourceExtensions(workspaceExtensions.map(e => e.extensionLocation), true);
1261
changedExtensions.push(...extensions);
1262
}
1263
for (const changedExtension of changedExtensions) {
1264
this._onChange.fire(changedExtension);
1265
}
1266
}
1267
1268
private updateExtensionsPinnedState(pinned: boolean): Promise<void> {
1269
return this.progressService.withProgress({
1270
location: ProgressLocation.Extensions,
1271
title: nls.localize('updatingExtensions', "Updating Extensions Auto Update State"),
1272
}, () => this.extensionManagementService.resetPinnedStateForAllUserExtensions(pinned));
1273
}
1274
1275
private reset(): void {
1276
for (const task of this.tasksInProgress) {
1277
task.cancel();
1278
}
1279
this.tasksInProgress = [];
1280
this.installing = [];
1281
this.onDidChangeExtensions();
1282
this._onReset.fire();
1283
}
1284
1285
private onDidChangeExtensions(extension?: IExtension): void {
1286
this._installed = undefined;
1287
this._local = undefined;
1288
this._onChange.fire(extension);
1289
}
1290
1291
private _local: IExtension[] | undefined;
1292
get local(): IExtension[] {
1293
if (!this._local) {
1294
if (this.extensionsServers.length === 1) {
1295
this._local = this.installed;
1296
} else {
1297
this._local = [];
1298
const byId = groupByExtension(this.installed, r => r.identifier);
1299
for (const extensions of byId) {
1300
this._local.push(this.getPrimaryExtension(extensions));
1301
}
1302
}
1303
}
1304
return this._local;
1305
}
1306
1307
private _installed: IExtension[] | undefined;
1308
get installed(): IExtension[] {
1309
if (!this._installed) {
1310
this._installed = [];
1311
for (const extensions of this.extensionsServers) {
1312
for (const extension of extensions.local) {
1313
this._installed.push(extension);
1314
}
1315
}
1316
}
1317
return this._installed;
1318
}
1319
1320
get outdated(): IExtension[] {
1321
return this.installed.filter(e => e.outdated && e.local && e.state === ExtensionState.Installed);
1322
}
1323
1324
async queryLocal(server?: IExtensionManagementServer): Promise<IExtension[]> {
1325
if (server) {
1326
if (this.localExtensions && this.extensionManagementServerService.localExtensionManagementServer === server) {
1327
return this.localExtensions.queryInstalled(this.getProductVersion());
1328
}
1329
if (this.remoteExtensions && this.extensionManagementServerService.remoteExtensionManagementServer === server) {
1330
return this.remoteExtensions.queryInstalled(this.getProductVersion());
1331
}
1332
if (this.webExtensions && this.extensionManagementServerService.webExtensionManagementServer === server) {
1333
return this.webExtensions.queryInstalled(this.getProductVersion());
1334
}
1335
}
1336
1337
if (this.localExtensions) {
1338
try {
1339
await this.localExtensions.queryInstalled(this.getProductVersion());
1340
}
1341
catch (error) {
1342
this.logService.error(error);
1343
}
1344
}
1345
if (this.remoteExtensions) {
1346
try {
1347
await this.remoteExtensions.queryInstalled(this.getProductVersion());
1348
}
1349
catch (error) {
1350
this.logService.error(error);
1351
}
1352
}
1353
if (this.webExtensions) {
1354
try {
1355
await this.webExtensions.queryInstalled(this.getProductVersion());
1356
}
1357
catch (error) {
1358
this.logService.error(error);
1359
}
1360
}
1361
return this.local;
1362
}
1363
1364
queryGallery(token: CancellationToken): Promise<IPager<IExtension>>;
1365
queryGallery(options: IQueryOptions, token: CancellationToken): Promise<IPager<IExtension>>;
1366
async queryGallery(arg1: any, arg2?: any): Promise<IPager<IExtension>> {
1367
if (!this.galleryService.isEnabled()) {
1368
return singlePagePager([]);
1369
}
1370
1371
const options: IQueryOptions = CancellationToken.isCancellationToken(arg1) ? {} : arg1;
1372
const token: CancellationToken = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2;
1373
options.text = options.text ? this.resolveQueryText(options.text) : options.text;
1374
options.includePreRelease = isUndefined(options.includePreRelease) ? this.extensionManagementService.preferPreReleases : options.includePreRelease;
1375
1376
const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest();
1377
const pager = await this.galleryService.query(options, token);
1378
this.syncInstalledExtensionsWithGallery(pager.firstPage);
1379
return {
1380
firstPage: pager.firstPage.map(gallery => this.fromGallery(gallery, extensionsControlManifest)),
1381
total: pager.total,
1382
pageSize: pager.pageSize,
1383
getPage: async (pageIndex, token) => {
1384
const page = await pager.getPage(pageIndex, token);
1385
this.syncInstalledExtensionsWithGallery(page);
1386
return page.map(gallery => this.fromGallery(gallery, extensionsControlManifest));
1387
}
1388
};
1389
}
1390
1391
getExtensions(extensionInfos: IExtensionInfo[], token: CancellationToken): Promise<IExtension[]>;
1392
getExtensions(extensionInfos: IExtensionInfo[], options: IExtensionQueryOptions, token: CancellationToken): Promise<IExtension[]>;
1393
async getExtensions(extensionInfos: IExtensionInfo[], arg1: any, arg2?: any): Promise<IExtension[]> {
1394
if (!this.galleryService.isEnabled()) {
1395
return [];
1396
}
1397
1398
extensionInfos.forEach(e => e.preRelease = e.preRelease ?? this.extensionManagementService.preferPreReleases);
1399
const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest();
1400
const galleryExtensions = await this.galleryService.getExtensions(extensionInfos, arg1, arg2);
1401
this.syncInstalledExtensionsWithGallery(galleryExtensions);
1402
return galleryExtensions.map(gallery => this.fromGallery(gallery, extensionsControlManifest));
1403
}
1404
1405
async getResourceExtensions(locations: URI[], isWorkspaceScoped: boolean): Promise<IExtension[]> {
1406
const resourceExtensions = await this.extensionManagementService.getExtensions(locations);
1407
return resourceExtensions.map(resourceExtension => this.getInstalledExtensionMatchingLocation(resourceExtension.location)
1408
?? this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, undefined, { resourceExtension, isWorkspaceScoped }));
1409
}
1410
1411
private onDidDismissedNotificationsValueChange(): void {
1412
if (
1413
this.dismissedNotificationsValue !== this.getDismissedNotificationsValue() /* This checks if current window changed the value or not */
1414
) {
1415
this._dismissedNotificationsValue = undefined;
1416
this.updateExtensionsNotificaiton();
1417
}
1418
}
1419
1420
private updateExtensionsNotificaiton(): void {
1421
const computedNotificiations = this.computeExtensionsNotifications();
1422
const dismissedNotifications: string[] = [];
1423
1424
let extensionsNotification: IExtensionsNotification & { key: string } | undefined;
1425
if (computedNotificiations.length) {
1426
// populate dismissed notifications with the ones that are still valid
1427
for (const dismissedNotification of this.getDismissedNotifications()) {
1428
if (computedNotificiations.some(e => e.key === dismissedNotification)) {
1429
dismissedNotifications.push(dismissedNotification);
1430
}
1431
}
1432
if (!dismissedNotifications.includes(computedNotificiations[0].key)) {
1433
extensionsNotification = {
1434
message: computedNotificiations[0].message,
1435
severity: computedNotificiations[0].severity,
1436
extensions: computedNotificiations[0].extensions,
1437
key: computedNotificiations[0].key,
1438
dismiss: () => {
1439
this.setDismissedNotifications([...this.getDismissedNotifications(), computedNotificiations[0].key]);
1440
this.updateExtensionsNotificaiton();
1441
},
1442
};
1443
}
1444
}
1445
this.setDismissedNotifications(dismissedNotifications);
1446
1447
if (this.extensionsNotification?.key !== extensionsNotification?.key) {
1448
this.extensionsNotification = extensionsNotification;
1449
this._onDidChangeExtensionsNotification.fire(this.extensionsNotification);
1450
}
1451
}
1452
1453
private computeExtensionsNotifications(): Array<Omit<IExtensionsNotification, 'dismiss'> & { key: string }> {
1454
const computedNotificiations: Array<Omit<IExtensionsNotification, 'dismiss'> & { key: string }> = [];
1455
1456
const disallowedExtensions = this.local.filter(e => e.enablementState === EnablementState.DisabledByAllowlist);
1457
if (disallowedExtensions.length) {
1458
computedNotificiations.push({
1459
message: this.configurationService.inspect(AllowedExtensionsConfigKey).policy
1460
? nls.localize('disallowed extensions by policy', "Some extensions are disabled because they are not allowed by your system administrator.")
1461
: nls.localize('disallowed extensions', "Some extensions are disabled because they are configured not to be allowed."),
1462
severity: Severity.Warning,
1463
extensions: disallowedExtensions,
1464
key: 'disallowedExtensions:' + disallowedExtensions.sort((a, b) => a.identifier.id.localeCompare(b.identifier.id)).map(e => e.identifier.id.toLowerCase()).join('-'),
1465
});
1466
}
1467
1468
const invalidExtensions = this.local.filter(e => e.enablementState === EnablementState.DisabledByInvalidExtension && !e.isWorkspaceScoped);
1469
if (invalidExtensions.length) {
1470
if (invalidExtensions.some(e => e.local && e.local.manifest.engines?.vscode &&
1471
(!isEngineValid(e.local.manifest.engines.vscode, this.productService.version, this.productService.date) || areApiProposalsCompatible([...e.local.manifest.enabledApiProposals ?? []]))
1472
)) {
1473
computedNotificiations.push({
1474
message: nls.localize('incompatibleExtensions', "Some extensions are disabled due to version incompatibility. Review and update them."),
1475
severity: Severity.Warning,
1476
extensions: invalidExtensions,
1477
key: 'incompatibleExtensions:' + invalidExtensions.sort((a, b) => a.identifier.id.localeCompare(b.identifier.id)).map(e => `${e.identifier.id.toLowerCase()}@${e.local?.manifest.version}`).join('-'),
1478
});
1479
} else {
1480
computedNotificiations.push({
1481
message: nls.localize('invalidExtensions', "Invalid extensions detected. Review them."),
1482
severity: Severity.Warning,
1483
extensions: invalidExtensions,
1484
key: 'invalidExtensions:' + invalidExtensions.sort((a, b) => a.identifier.id.localeCompare(b.identifier.id)).map(e => `${e.identifier.id.toLowerCase()}@${e.local?.manifest.version}`).join('-'),
1485
});
1486
}
1487
}
1488
1489
const deprecatedExtensions = this.local.filter(e => !!e.deprecationInfo && e.local && this.extensionEnablementService.isEnabled(e.local));
1490
if (deprecatedExtensions.length) {
1491
computedNotificiations.push({
1492
message: nls.localize('deprecated extensions', "Deprecated extensions detected. Review them and migrate to alternatives."),
1493
severity: Severity.Warning,
1494
extensions: deprecatedExtensions,
1495
key: 'deprecatedExtensions:' + deprecatedExtensions.sort((a, b) => a.identifier.id.localeCompare(b.identifier.id)).map(e => e.identifier.id.toLowerCase()).join('-'),
1496
});
1497
}
1498
1499
return computedNotificiations;
1500
}
1501
1502
getExtensionsNotification(): IExtensionsNotification | undefined {
1503
return this.extensionsNotification;
1504
}
1505
1506
private resolveQueryText(text: string): string {
1507
text = text.replace(/@web/g, `tag:"${WEB_EXTENSION_TAG}"`);
1508
1509
const extensionRegex = /\bext:([^\s]+)\b/g;
1510
if (extensionRegex.test(text)) {
1511
text = text.replace(extensionRegex, (m, ext) => {
1512
1513
// Get curated keywords
1514
const lookup = this.productService.extensionKeywords || {};
1515
const keywords = lookup[ext] || [];
1516
1517
// Get mode name
1518
const languageId = this.languageService.guessLanguageIdByFilepathOrFirstLine(URI.file(`.${ext}`));
1519
const languageName = languageId && this.languageService.getLanguageName(languageId);
1520
const languageTag = languageName ? ` tag:"${languageName}"` : '';
1521
1522
// Construct a rich query
1523
return `tag:"__ext_${ext}" tag:"__ext_.${ext}" ${keywords.map(tag => `tag:"${tag}"`).join(' ')}${languageTag} tag:"${ext}"`;
1524
});
1525
}
1526
return text.substr(0, 350);
1527
}
1528
1529
private fromGallery(gallery: IGalleryExtension, extensionsControlManifest: IExtensionsControlManifest): IExtension {
1530
let extension = this.getInstalledExtensionMatchingGallery(gallery);
1531
if (!extension) {
1532
extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, gallery, undefined);
1533
(<Extension>extension).setExtensionsControlManifest(extensionsControlManifest);
1534
}
1535
return extension;
1536
}
1537
1538
private getInstalledExtensionMatchingGallery(gallery: IGalleryExtension): IExtension | null {
1539
for (const installed of this.local) {
1540
if (installed.identifier.uuid) { // Installed from Gallery
1541
if (installed.identifier.uuid === gallery.identifier.uuid) {
1542
return installed;
1543
}
1544
} else if (installed.local?.source !== 'resource') {
1545
if (areSameExtensions(installed.identifier, gallery.identifier)) { // Installed from other sources
1546
return installed;
1547
}
1548
}
1549
}
1550
return null;
1551
}
1552
1553
private getInstalledExtensionMatchingLocation(location: URI): IExtension | null {
1554
return this.local.find(e => e.local && this.uriIdentityService.extUri.isEqualOrParent(location, e.local?.location)) ?? null;
1555
}
1556
1557
async open(extension: IExtension | string, options?: IExtensionEditorOptions): Promise<void> {
1558
if (typeof extension === 'string') {
1559
const id = extension;
1560
extension = this.installed.find(e => areSameExtensions(e.identifier, { id })) ?? (await this.getExtensions([{ id: extension }], CancellationToken.None))[0];
1561
}
1562
if (!extension) {
1563
throw new Error(`Extension not found. ${extension}`);
1564
}
1565
await this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), options, options?.sideByside ? SIDE_GROUP : ACTIVE_GROUP);
1566
}
1567
1568
async openSearch(searchValue: string, preserveFoucs?: boolean): Promise<void> {
1569
const viewPaneContainer = (await this.viewsService.openViewContainer(VIEWLET_ID, true))?.getViewPaneContainer() as IExtensionsViewPaneContainer;
1570
viewPaneContainer.search(searchValue);
1571
if (!preserveFoucs) {
1572
viewPaneContainer.focus();
1573
}
1574
}
1575
1576
getExtensionRuntimeStatus(extension: IExtension): IExtensionRuntimeStatus | undefined {
1577
const extensionsStatus = this.extensionService.getExtensionsStatus();
1578
for (const id of Object.keys(extensionsStatus)) {
1579
if (areSameExtensions({ id }, extension.identifier)) {
1580
return extensionsStatus[id];
1581
}
1582
}
1583
return undefined;
1584
}
1585
1586
async updateRunningExtensions(message = nls.localize('restart', "Changing extension enablement"), auto: boolean = false): Promise<void> {
1587
const toAdd: ILocalExtension[] = [];
1588
const toRemove: string[] = [];
1589
1590
const extensionsToCheck = [...this.local];
1591
for (const extension of extensionsToCheck) {
1592
const runtimeState = extension.runtimeState;
1593
if (!runtimeState || runtimeState.action !== ExtensionRuntimeActionType.RestartExtensions) {
1594
continue;
1595
}
1596
if (extension.state === ExtensionState.Uninstalled) {
1597
toRemove.push(extension.identifier.id);
1598
continue;
1599
}
1600
if (!extension.local) {
1601
continue;
1602
}
1603
const isEnabled = this.extensionEnablementService.isEnabled(extension.local);
1604
if (isEnabled) {
1605
const runningExtension = this.extensionService.extensions.find(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier));
1606
if (runningExtension) {
1607
toRemove.push(runningExtension.identifier.value);
1608
}
1609
toAdd.push(extension.local);
1610
} else {
1611
toRemove.push(extension.identifier.id);
1612
}
1613
}
1614
1615
for (const extension of this.extensionService.extensions) {
1616
if (extension.isUnderDevelopment) {
1617
continue;
1618
}
1619
if (extensionsToCheck.some(e => areSameExtensions({ id: extension.identifier.value, uuid: extension.uuid }, e.local?.identifier ?? e.identifier))) {
1620
continue;
1621
}
1622
// Extension is running but doesn't exist locally. Remove it from running extensions.
1623
toRemove.push(extension.identifier.value);
1624
}
1625
1626
if (toAdd.length || toRemove.length) {
1627
if (await this.extensionService.stopExtensionHosts(message, auto)) {
1628
await this.extensionService.startExtensionHosts({ toAdd, toRemove });
1629
if (auto) {
1630
this.notificationService.notify({
1631
severity: Severity.Info,
1632
message: nls.localize('extensionsAutoRestart', "Extensions were auto restarted to enable updates."),
1633
priority: NotificationPriority.SILENT
1634
});
1635
}
1636
type ExtensionsAutoRestartClassification = {
1637
owner: 'sandy081';
1638
comment: 'Report when extensions are auto restarted';
1639
count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of extensions auto restarted' };
1640
auto: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the restart was triggered automatically' };
1641
};
1642
type ExtensionsAutoRestartEvent = {
1643
count: number;
1644
auto: boolean;
1645
};
1646
this.telemetryService.publicLog2<ExtensionsAutoRestartEvent, ExtensionsAutoRestartClassification>('extensions:autorestart', { count: toAdd.length + toRemove.length, auto });
1647
}
1648
}
1649
}
1650
1651
private getRuntimeState(extension: IExtension): ExtensionRuntimeState | undefined {
1652
const isUninstalled = extension.state === ExtensionState.Uninstalled;
1653
const runningExtension = this.extensionService.extensions.find(e => areSameExtensions({ id: e.identifier.value }, extension.identifier));
1654
const reloadAction = this.extensionManagementServerService.remoteExtensionManagementServer ? ExtensionRuntimeActionType.ReloadWindow : ExtensionRuntimeActionType.RestartExtensions;
1655
const reloadActionLabel = reloadAction === ExtensionRuntimeActionType.ReloadWindow ? nls.localize('reload', "reload window") : nls.localize('restart extensions', "restart extensions");
1656
1657
if (isUninstalled) {
1658
const canRemoveRunningExtension = runningExtension && this.extensionService.canRemoveExtension(runningExtension);
1659
const isSameExtensionRunning = runningExtension
1660
&& (!extension.server || extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension)))
1661
&& (!extension.resourceExtension || this.uriIdentityService.extUri.isEqual(extension.resourceExtension.location, runningExtension.extensionLocation));
1662
if (!canRemoveRunningExtension && isSameExtensionRunning && !runningExtension.isUnderDevelopment) {
1663
return { action: reloadAction, reason: nls.localize('postUninstallTooltip', "Please {0} to complete the uninstallation of this extension.", reloadActionLabel) };
1664
}
1665
return undefined;
1666
}
1667
if (extension.local) {
1668
const isSameExtensionRunning = runningExtension && extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension));
1669
const isEnabled = this.extensionEnablementService.isEnabled(extension.local);
1670
1671
// Extension is running
1672
if (runningExtension) {
1673
if (isEnabled) {
1674
// No Reload is required if extension can run without reload
1675
if (this.extensionService.canAddExtension(toExtensionDescription(extension.local))) {
1676
return undefined;
1677
}
1678
const runningExtensionServer = this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension));
1679
1680
if (isSameExtensionRunning) {
1681
// Different version or target platform of same extension is running. Requires reload to run the current version
1682
if (!runningExtension.isUnderDevelopment && (extension.version !== runningExtension.version || extension.local.targetPlatform !== runningExtension.targetPlatform)) {
1683
const productCurrentVersion = this.getProductCurrentVersion();
1684
const productUpdateVersion = this.getProductUpdateVersion();
1685
if (productUpdateVersion
1686
&& !isEngineValid(extension.local.manifest.engines.vscode, productCurrentVersion.version, productCurrentVersion.date)
1687
&& isEngineValid(extension.local.manifest.engines.vscode, productUpdateVersion.version, productUpdateVersion.date)
1688
) {
1689
const state = this.updateService.state;
1690
if (state.type === StateType.AvailableForDownload) {
1691
return { action: ExtensionRuntimeActionType.DownloadUpdate, reason: nls.localize('postUpdateDownloadTooltip', "Please update {0} to enable the updated extension.", this.productService.nameLong) };
1692
}
1693
if (state.type === StateType.Downloaded) {
1694
return { action: ExtensionRuntimeActionType.ApplyUpdate, reason: nls.localize('postUpdateUpdateTooltip', "Please update {0} to enable the updated extension.", this.productService.nameLong) };
1695
}
1696
if (state.type === StateType.Ready) {
1697
return { action: ExtensionRuntimeActionType.QuitAndInstall, reason: nls.localize('postUpdateRestartTooltip', "Please restart {0} to enable the updated extension.", this.productService.nameLong) };
1698
}
1699
return undefined;
1700
}
1701
return { action: reloadAction, reason: nls.localize('postUpdateTooltip', "Please {0} to enable the updated extension.", reloadActionLabel) };
1702
}
1703
1704
if (this.extensionsServers.length > 1) {
1705
const extensionInOtherServer = this.installed.filter(e => areSameExtensions(e.identifier, extension.identifier) && e.server !== extension.server)[0];
1706
if (extensionInOtherServer) {
1707
// This extension prefers to run on UI/Local side but is running in remote
1708
if (runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.localExtensionManagementServer) {
1709
return { action: reloadAction, reason: nls.localize('enable locally', "Please {0} to enable this extension locally.", reloadActionLabel) };
1710
}
1711
1712
// This extension prefers to run on Workspace/Remote side but is running in local
1713
if (runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.remoteExtensionManagementServer) {
1714
return { action: reloadAction, reason: nls.localize('enable remote', "Please {0} to enable this extension in {1}.", reloadActionLabel, this.extensionManagementServerService.remoteExtensionManagementServer?.label) };
1715
}
1716
}
1717
}
1718
1719
} else {
1720
1721
if (extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) {
1722
// This extension prefers to run on UI/Local side but is running in remote
1723
if (this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local.manifest)) {
1724
return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) };
1725
}
1726
}
1727
if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) {
1728
// This extension prefers to run on Workspace/Remote side but is running in local
1729
if (this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local.manifest)) {
1730
return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) };
1731
}
1732
}
1733
}
1734
return undefined;
1735
} else {
1736
if (isSameExtensionRunning) {
1737
return { action: reloadAction, reason: nls.localize('postDisableTooltip', "Please {0} to disable this extension.", reloadActionLabel) };
1738
}
1739
}
1740
return undefined;
1741
}
1742
1743
// Extension is not running
1744
else {
1745
if (isEnabled && !this.extensionService.canAddExtension(toExtensionDescription(extension.local))) {
1746
return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) };
1747
}
1748
1749
const otherServer = extension.server ? extension.server === this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer : null;
1750
if (otherServer && extension.enablementState === EnablementState.DisabledByExtensionKind) {
1751
const extensionInOtherServer = this.local.filter(e => areSameExtensions(e.identifier, extension.identifier) && e.server === otherServer)[0];
1752
// Same extension in other server exists and
1753
if (extensionInOtherServer && extensionInOtherServer.local && this.extensionEnablementService.isEnabled(extensionInOtherServer.local)) {
1754
return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) };
1755
}
1756
}
1757
}
1758
}
1759
return undefined;
1760
}
1761
1762
private getPrimaryExtension(extensions: IExtension[]): IExtension {
1763
if (extensions.length === 1) {
1764
return extensions[0];
1765
}
1766
1767
const enabledExtensions = extensions.filter(e => e.local && this.extensionEnablementService.isEnabled(e.local));
1768
if (enabledExtensions.length === 1) {
1769
return enabledExtensions[0];
1770
}
1771
1772
const extensionsToChoose = enabledExtensions.length ? enabledExtensions : extensions;
1773
const manifest = extensionsToChoose.find(e => e.local && e.local.manifest)?.local?.manifest;
1774
1775
// Manifest is not found which should not happen.
1776
// In which case return the first extension.
1777
if (!manifest) {
1778
return extensionsToChoose[0];
1779
}
1780
1781
const extensionKinds = this.extensionManifestPropertiesService.getExtensionKind(manifest);
1782
1783
let extension = extensionsToChoose.find(extension => {
1784
for (const extensionKind of extensionKinds) {
1785
switch (extensionKind) {
1786
case 'ui':
1787
/* UI extension is chosen only if it is installed locally */
1788
if (extension.server === this.extensionManagementServerService.localExtensionManagementServer) {
1789
return true;
1790
}
1791
return false;
1792
case 'workspace':
1793
/* Choose remote workspace extension if exists */
1794
if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) {
1795
return true;
1796
}
1797
return false;
1798
case 'web':
1799
/* Choose web extension if exists */
1800
if (extension.server === this.extensionManagementServerService.webExtensionManagementServer) {
1801
return true;
1802
}
1803
return false;
1804
}
1805
}
1806
return false;
1807
});
1808
1809
if (!extension && this.extensionManagementServerService.localExtensionManagementServer) {
1810
extension = extensionsToChoose.find(extension => {
1811
for (const extensionKind of extensionKinds) {
1812
switch (extensionKind) {
1813
case 'workspace':
1814
/* Choose local workspace extension if exists */
1815
if (extension.server === this.extensionManagementServerService.localExtensionManagementServer) {
1816
return true;
1817
}
1818
return false;
1819
case 'web':
1820
/* Choose local web extension if exists */
1821
if (extension.server === this.extensionManagementServerService.localExtensionManagementServer) {
1822
return true;
1823
}
1824
return false;
1825
}
1826
}
1827
return false;
1828
});
1829
}
1830
1831
if (!extension && this.extensionManagementServerService.webExtensionManagementServer) {
1832
extension = extensionsToChoose.find(extension => {
1833
for (const extensionKind of extensionKinds) {
1834
switch (extensionKind) {
1835
case 'web':
1836
/* Choose web extension if exists */
1837
if (extension.server === this.extensionManagementServerService.webExtensionManagementServer) {
1838
return true;
1839
}
1840
return false;
1841
}
1842
}
1843
return false;
1844
});
1845
}
1846
1847
if (!extension && this.extensionManagementServerService.remoteExtensionManagementServer) {
1848
extension = extensionsToChoose.find(extension => {
1849
for (const extensionKind of extensionKinds) {
1850
switch (extensionKind) {
1851
case 'web':
1852
/* Choose remote web extension if exists */
1853
if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) {
1854
return true;
1855
}
1856
return false;
1857
}
1858
}
1859
return false;
1860
});
1861
}
1862
1863
return extension || extensions[0];
1864
}
1865
1866
private getExtensionState(extension: Extension): ExtensionState {
1867
if (this.installing.some(i => areSameExtensions(i.identifier, extension.identifier) && (!extension.server || i.server === extension.server))) {
1868
return ExtensionState.Installing;
1869
}
1870
if (this.remoteExtensions) {
1871
const state = this.remoteExtensions.getExtensionState(extension);
1872
if (state !== ExtensionState.Uninstalled) {
1873
return state;
1874
}
1875
}
1876
if (this.webExtensions) {
1877
const state = this.webExtensions.getExtensionState(extension);
1878
if (state !== ExtensionState.Uninstalled) {
1879
return state;
1880
}
1881
}
1882
if (this.localExtensions) {
1883
return this.localExtensions.getExtensionState(extension);
1884
}
1885
return ExtensionState.Uninstalled;
1886
}
1887
1888
async checkForUpdates(reason?: string, onlyBuiltin?: boolean): Promise<void> {
1889
if (reason) {
1890
this.logService.trace(`[Extensions]: Checking for updates. Reason: ${reason}`);
1891
} else {
1892
this.logService.trace(`[Extensions]: Checking for updates`);
1893
}
1894
if (!this.galleryService.isEnabled()) {
1895
return;
1896
}
1897
const extensions: Extensions[] = [];
1898
if (this.localExtensions) {
1899
extensions.push(this.localExtensions);
1900
}
1901
if (this.remoteExtensions) {
1902
extensions.push(this.remoteExtensions);
1903
}
1904
if (this.webExtensions) {
1905
extensions.push(this.webExtensions);
1906
}
1907
if (!extensions.length) {
1908
return;
1909
}
1910
const infos: IExtensionInfo[] = [];
1911
for (const installed of this.local) {
1912
if (onlyBuiltin && !installed.isBuiltin) {
1913
// Skip if check updates only for builtin extensions and current extension is not builtin.
1914
continue;
1915
}
1916
if (installed.isBuiltin && !installed.local?.pinned && (installed.type === ExtensionType.System || !installed.local?.identifier.uuid)) {
1917
// Skip checking updates for a builtin extension if it is a system extension or if it does not has Marketplace identifier
1918
continue;
1919
}
1920
if (installed.local?.source === 'resource') {
1921
continue;
1922
}
1923
infos.push({ ...installed.identifier, preRelease: !!installed.local?.preRelease });
1924
}
1925
if (infos.length) {
1926
const targetPlatform = await extensions[0].server.extensionManagementService.getTargetPlatform();
1927
type GalleryServiceUpdatesCheckClassification = {
1928
owner: 'sandy081';
1929
comment: 'Report when a request is made to check for updates of extensions';
1930
count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of extensions to check update' };
1931
};
1932
type GalleryServiceUpdatesCheckEvent = {
1933
count: number;
1934
};
1935
this.telemetryService.publicLog2<GalleryServiceUpdatesCheckEvent, GalleryServiceUpdatesCheckClassification>('galleryService:checkingForUpdates', {
1936
count: infos.length,
1937
});
1938
this.logService.trace(`Checking updates for extensions`, infos.map(e => e.id).join(', '));
1939
const galleryExtensions = await this.galleryService.getExtensions(infos, { targetPlatform, compatible: true, productVersion: this.getProductVersion() }, CancellationToken.None);
1940
if (galleryExtensions.length) {
1941
await this.syncInstalledExtensionsWithGallery(galleryExtensions, infos);
1942
}
1943
}
1944
}
1945
1946
async updateAll(): Promise<InstallExtensionResult[]> {
1947
const toUpdate: InstallExtensionInfo[] = [];
1948
this.outdated.forEach((extension) => {
1949
if (extension.gallery) {
1950
toUpdate.push({
1951
extension: extension.gallery,
1952
options: {
1953
operation: InstallOperation.Update,
1954
installPreReleaseVersion: extension.local?.isPreReleaseVersion,
1955
profileLocation: this.userDataProfileService.currentProfile.extensionsResource,
1956
isApplicationScoped: extension.local?.isApplicationScoped,
1957
context: { [EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT]: true }
1958
}
1959
});
1960
}
1961
});
1962
return this.extensionManagementService.installGalleryExtensions(toUpdate);
1963
}
1964
1965
async downloadVSIX(extensionId: string, versionKind: 'prerelease' | 'release' | 'any'): Promise<void> {
1966
let version: IGalleryExtensionVersion | undefined;
1967
if (versionKind === 'any') {
1968
version = await this.pickVersionToDownload(extensionId);
1969
if (!version) {
1970
return;
1971
}
1972
}
1973
1974
const extensionInfo = version ? { id: extensionId, version: version.version } : { id: extensionId, preRelease: versionKind === 'prerelease' };
1975
const queryOptions: IExtensionQueryOptions = version ? {} : { compatible: true };
1976
1977
let [galleryExtension] = await this.galleryService.getExtensions([extensionInfo], queryOptions, CancellationToken.None);
1978
if (!galleryExtension) {
1979
throw new Error(nls.localize('extension not found', "Extension '{0}' not found.", extensionId));
1980
}
1981
1982
let targetPlatform = galleryExtension.properties.targetPlatform;
1983
const options = [];
1984
for (const targetPlatform of version?.targetPlatforms ?? galleryExtension.allTargetPlatforms) {
1985
if (targetPlatform !== TargetPlatform.UNKNOWN && targetPlatform !== TargetPlatform.UNIVERSAL) {
1986
options.push({
1987
label: targetPlatform === TargetPlatform.UNDEFINED ? nls.localize('allplatforms', "All Platforms") : TargetPlatformToString(targetPlatform),
1988
id: targetPlatform
1989
});
1990
}
1991
}
1992
if (options.length > 1) {
1993
const message = nls.localize('platform placeholder', "Please select the platform for which you want to download the VSIX");
1994
const option = await this.quickInputService.pick(options.sort((a, b) => a.label.localeCompare(b.label)), { placeHolder: message });
1995
if (!option) {
1996
return;
1997
}
1998
targetPlatform = option.id;
1999
}
2000
2001
if (targetPlatform !== galleryExtension.properties.targetPlatform) {
2002
[galleryExtension] = await this.galleryService.getExtensions([extensionInfo], { ...queryOptions, targetPlatform }, CancellationToken.None);
2003
}
2004
2005
const result = await this.fileDialogService.showOpenDialog({
2006
title: nls.localize('download title', "Select folder to download the VSIX"),
2007
canSelectFiles: false,
2008
canSelectFolders: true,
2009
canSelectMany: false,
2010
openLabel: nls.localize('download', "Download"),
2011
});
2012
2013
if (!result?.[0]) {
2014
return;
2015
}
2016
2017
this.progressService.withProgress({ location: ProgressLocation.Notification }, async progress => {
2018
try {
2019
progress.report({ message: nls.localize('downloading...', "Downloading VSIX...") });
2020
const name = `${galleryExtension.identifier.id}-${galleryExtension.version}${targetPlatform !== TargetPlatform.UNDEFINED && targetPlatform !== TargetPlatform.UNIVERSAL && targetPlatform !== TargetPlatform.UNKNOWN ? `-${targetPlatform}` : ''}.vsix`;
2021
await this.galleryService.download(galleryExtension, this.uriIdentityService.extUri.joinPath(result[0], name), InstallOperation.None);
2022
this.notificationService.info(nls.localize('download.completed', "Successfully downloaded the VSIX"));
2023
} catch (error) {
2024
this.notificationService.error(nls.localize('download.failed', "Error while downloading the VSIX: {0}", getErrorMessage(error)));
2025
}
2026
});
2027
}
2028
2029
private async pickVersionToDownload(extensionId: string): Promise<IGalleryExtensionVersion | undefined> {
2030
const allVersions = await this.galleryService.getAllVersions({ id: extensionId });
2031
if (!allVersions.length) {
2032
await this.dialogService.info(nls.localize('no versions', "This extension has no other versions."));
2033
return;
2034
}
2035
2036
const picks = allVersions.map((v, i) => {
2037
return {
2038
id: v.version,
2039
label: v.version,
2040
description: `${fromNow(new Date(Date.parse(v.date)), true)}${v.isPreReleaseVersion ? ` (${nls.localize('pre-release', "pre-release")})` : ''}`,
2041
ariaLabel: `${v.isPreReleaseVersion ? 'Pre-Release version' : 'Release version'} ${v.version}`,
2042
data: v,
2043
};
2044
});
2045
const pick = await this.quickInputService.pick(picks,
2046
{
2047
placeHolder: nls.localize('selectVersion', "Select Version to Download"),
2048
matchOnDetail: true
2049
});
2050
return pick?.data;
2051
}
2052
2053
private async syncInstalledExtensionsWithGallery(gallery: IGalleryExtension[], flagExtensionsMissingFromGallery?: IExtensionInfo[]): Promise<void> {
2054
const extensions: Extensions[] = [];
2055
if (this.localExtensions) {
2056
extensions.push(this.localExtensions);
2057
}
2058
if (this.remoteExtensions) {
2059
extensions.push(this.remoteExtensions);
2060
}
2061
if (this.webExtensions) {
2062
extensions.push(this.webExtensions);
2063
}
2064
if (!extensions.length) {
2065
return;
2066
}
2067
await Promise.allSettled(extensions.map(extensions => extensions.syncInstalledExtensionsWithGallery(gallery, this.getProductVersion(), flagExtensionsMissingFromGallery)));
2068
if (this.outdated.length) {
2069
this.logService.info(`Auto updating outdated extensions.`, this.outdated.map(e => e.identifier.id).join(', '));
2070
this.eventuallyAutoUpdateExtensions();
2071
}
2072
}
2073
2074
private isAutoCheckUpdatesEnabled(): boolean {
2075
return this.configurationService.getValue(AutoCheckUpdatesConfigurationKey);
2076
}
2077
2078
private eventuallyCheckForUpdates(immediate = false): void {
2079
this.updatesCheckDelayer.cancel();
2080
this.updatesCheckDelayer.trigger(async () => {
2081
if (this.isAutoCheckUpdatesEnabled()) {
2082
await this.checkForUpdates();
2083
}
2084
this.eventuallyCheckForUpdates();
2085
}, immediate ? 0 : this.getUpdatesCheckInterval()).then(undefined, err => null);
2086
}
2087
2088
private getUpdatesCheckInterval(): number {
2089
if (this.productService.quality === 'insider' && this.getProductUpdateVersion()) {
2090
return 1000 * 60 * 60 * 1; // 1 hour
2091
}
2092
return ExtensionsWorkbenchService.UpdatesCheckInterval;
2093
}
2094
2095
private eventuallyAutoUpdateExtensions(): void {
2096
this.autoUpdateDelayer.trigger(() => this.autoUpdateExtensions())
2097
.then(undefined, err => null);
2098
}
2099
2100
private async autoUpdateBuiltinExtensions(): Promise<void> {
2101
await this.checkForUpdates(undefined, true);
2102
const toUpdate = this.outdated.filter(e => e.isBuiltin);
2103
await Promises.settled(toUpdate.map(e => this.install(e, e.local?.preRelease ? { installPreReleaseVersion: true } : undefined)));
2104
}
2105
2106
private async syncPinnedBuiltinExtensions(): Promise<void> {
2107
const infos: IExtensionInfo[] = [];
2108
for (const installed of this.local) {
2109
if (installed.isBuiltin && installed.local?.pinned && installed.local?.identifier.uuid) {
2110
infos.push({ ...installed.identifier, version: installed.version });
2111
}
2112
}
2113
if (infos.length) {
2114
const galleryExtensions = await this.galleryService.getExtensions(infos, CancellationToken.None);
2115
if (galleryExtensions.length) {
2116
await this.syncInstalledExtensionsWithGallery(galleryExtensions);
2117
}
2118
}
2119
}
2120
2121
private async autoUpdateExtensions(): Promise<void> {
2122
const toUpdate: IExtension[] = [];
2123
const disabledAutoUpdate = [];
2124
const consentRequired = [];
2125
for (const extension of this.outdated) {
2126
if (!this.shouldAutoUpdateExtension(extension)) {
2127
disabledAutoUpdate.push(extension.identifier.id);
2128
continue;
2129
}
2130
if (await this.shouldRequireConsentToUpdate(extension)) {
2131
consentRequired.push(extension.identifier.id);
2132
continue;
2133
}
2134
toUpdate.push(extension);
2135
}
2136
2137
if (disabledAutoUpdate.length) {
2138
this.logService.trace('Auto update disabled for extensions', disabledAutoUpdate.join(', '));
2139
}
2140
2141
if (consentRequired.length) {
2142
this.logService.info('Auto update consent required for extensions', consentRequired.join(', '));
2143
}
2144
2145
if (!toUpdate.length) {
2146
return;
2147
}
2148
2149
const productVersion = this.getProductVersion();
2150
await Promises.settled(toUpdate.map(e => this.install(e, e.local?.preRelease ? { installPreReleaseVersion: true, productVersion } : { productVersion })));
2151
}
2152
2153
private getProductVersion(): IProductVersion {
2154
return this.getProductUpdateVersion() ?? this.getProductCurrentVersion();
2155
}
2156
2157
private getProductCurrentVersion(): IProductVersion {
2158
return { version: this.productService.version, date: this.productService.date };
2159
}
2160
2161
private getProductUpdateVersion(): IProductVersion | undefined {
2162
switch (this.updateService.state.type) {
2163
case StateType.AvailableForDownload:
2164
case StateType.Downloaded:
2165
case StateType.Updating:
2166
case StateType.Ready: {
2167
const version = this.updateService.state.update.productVersion;
2168
if (version && semver.valid(version)) {
2169
return { version, date: this.updateService.state.update.timestamp ? new Date(this.updateService.state.update.timestamp).toISOString() : undefined };
2170
}
2171
}
2172
}
2173
return undefined;
2174
}
2175
2176
private shouldAutoUpdateExtension(extension: IExtension): boolean {
2177
if (extension.deprecationInfo?.disallowInstall) {
2178
return false;
2179
}
2180
2181
const autoUpdateValue = this.getAutoUpdateValue();
2182
2183
if (autoUpdateValue === false) {
2184
const extensionsToAutoUpdate = this.getEnabledAutoUpdateExtensions();
2185
const extensionId = extension.identifier.id.toLowerCase();
2186
if (extensionsToAutoUpdate.includes(extensionId)) {
2187
return true;
2188
}
2189
if (this.isAutoUpdateEnabledForPublisher(extension.publisher) && !extensionsToAutoUpdate.includes(`-${extensionId}`)) {
2190
return true;
2191
}
2192
return false;
2193
}
2194
2195
if (extension.pinned) {
2196
return false;
2197
}
2198
2199
const disabledAutoUpdateExtensions = this.getDisabledAutoUpdateExtensions();
2200
if (disabledAutoUpdateExtensions.includes(extension.identifier.id.toLowerCase())) {
2201
return false;
2202
}
2203
2204
if (autoUpdateValue === true) {
2205
return true;
2206
}
2207
2208
if (autoUpdateValue === 'onlyEnabledExtensions') {
2209
return extension.enablementState !== EnablementState.DisabledGlobally && extension.enablementState !== EnablementState.DisabledWorkspace;
2210
}
2211
2212
return false;
2213
}
2214
2215
async shouldRequireConsentToUpdate(extension: IExtension): Promise<string | undefined> {
2216
if (!extension.outdated) {
2217
return;
2218
}
2219
2220
if (!extension.gallery || !extension.local) {
2221
return;
2222
}
2223
2224
if (extension.local.identifier.uuid && extension.local.identifier.uuid !== extension.gallery.identifier.uuid) {
2225
return nls.localize('consentRequiredToUpdateRepublishedExtension', "The marketplace metadata of this extension changed, likely due to a re-publish.");
2226
}
2227
2228
if (!extension.local.manifest.engines.vscode || extension.local.manifest.main || extension.local.manifest.browser) {
2229
return;
2230
}
2231
2232
if (isDefined(extension.gallery.properties?.executesCode)) {
2233
if (!extension.gallery.properties.executesCode) {
2234
return;
2235
}
2236
} else {
2237
const manifest = extension instanceof Extension
2238
? await extension.getGalleryManifest()
2239
: await this.galleryService.getManifest(extension.gallery, CancellationToken.None);
2240
if (!manifest?.main && !manifest?.browser) {
2241
return;
2242
}
2243
}
2244
2245
return nls.localize('consentRequiredToUpdate', "The update for {0} extension introduces executable code, which is not present in the currently installed version.", extension.displayName);
2246
}
2247
2248
isAutoUpdateEnabledFor(extensionOrPublisher: IExtension | string): boolean {
2249
if (isString(extensionOrPublisher)) {
2250
if (EXTENSION_IDENTIFIER_REGEX.test(extensionOrPublisher)) {
2251
throw new Error('Expected publisher string, found extension identifier');
2252
}
2253
if (this.isAutoUpdateEnabled()) {
2254
return true;
2255
}
2256
return this.isAutoUpdateEnabledForPublisher(extensionOrPublisher);
2257
}
2258
return this.shouldAutoUpdateExtension(extensionOrPublisher);
2259
}
2260
2261
private isAutoUpdateEnabledForPublisher(publisher: string): boolean {
2262
const publishersToAutoUpdate = this.getPublishersToAutoUpdate();
2263
return publishersToAutoUpdate.includes(publisher.toLowerCase());
2264
}
2265
2266
async updateAutoUpdateEnablementFor(extensionOrPublisher: IExtension | string, enable: boolean): Promise<void> {
2267
if (this.isAutoUpdateEnabled()) {
2268
if (isString(extensionOrPublisher)) {
2269
throw new Error('Expected extension, found publisher string');
2270
}
2271
const disabledAutoUpdateExtensions = this.getDisabledAutoUpdateExtensions();
2272
const extensionId = extensionOrPublisher.identifier.id.toLowerCase();
2273
const extensionIndex = disabledAutoUpdateExtensions.indexOf(extensionId);
2274
if (enable) {
2275
if (extensionIndex !== -1) {
2276
disabledAutoUpdateExtensions.splice(extensionIndex, 1);
2277
}
2278
}
2279
else {
2280
if (extensionIndex === -1) {
2281
disabledAutoUpdateExtensions.push(extensionId);
2282
}
2283
}
2284
this.setDisabledAutoUpdateExtensions(disabledAutoUpdateExtensions);
2285
if (enable && extensionOrPublisher.local && extensionOrPublisher.pinned) {
2286
await this.extensionManagementService.updateMetadata(extensionOrPublisher.local, { pinned: false });
2287
}
2288
this._onChange.fire(extensionOrPublisher);
2289
}
2290
2291
else {
2292
const enabledAutoUpdateExtensions = this.getEnabledAutoUpdateExtensions();
2293
if (isString(extensionOrPublisher)) {
2294
if (EXTENSION_IDENTIFIER_REGEX.test(extensionOrPublisher)) {
2295
throw new Error('Expected publisher string, found extension identifier');
2296
}
2297
extensionOrPublisher = extensionOrPublisher.toLowerCase();
2298
if (this.isAutoUpdateEnabledFor(extensionOrPublisher) !== enable) {
2299
if (enable) {
2300
enabledAutoUpdateExtensions.push(extensionOrPublisher);
2301
} else {
2302
if (enabledAutoUpdateExtensions.includes(extensionOrPublisher)) {
2303
enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(extensionOrPublisher), 1);
2304
}
2305
}
2306
}
2307
this.setEnabledAutoUpdateExtensions(enabledAutoUpdateExtensions);
2308
for (const e of this.installed) {
2309
if (e.publisher.toLowerCase() === extensionOrPublisher) {
2310
this._onChange.fire(e);
2311
}
2312
}
2313
} else {
2314
const extensionId = extensionOrPublisher.identifier.id.toLowerCase();
2315
const enableAutoUpdatesForPublisher = this.isAutoUpdateEnabledFor(extensionOrPublisher.publisher.toLowerCase());
2316
const enableAutoUpdatesForExtension = enabledAutoUpdateExtensions.includes(extensionId);
2317
const disableAutoUpdatesForExtension = enabledAutoUpdateExtensions.includes(`-${extensionId}`);
2318
2319
if (enable) {
2320
if (disableAutoUpdatesForExtension) {
2321
enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(`-${extensionId}`), 1);
2322
}
2323
if (enableAutoUpdatesForPublisher) {
2324
if (enableAutoUpdatesForExtension) {
2325
enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(extensionId), 1);
2326
}
2327
} else {
2328
if (!enableAutoUpdatesForExtension) {
2329
enabledAutoUpdateExtensions.push(extensionId);
2330
}
2331
}
2332
}
2333
// Disable Auto Updates
2334
else {
2335
if (enableAutoUpdatesForExtension) {
2336
enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(extensionId), 1);
2337
}
2338
if (enableAutoUpdatesForPublisher) {
2339
if (!disableAutoUpdatesForExtension) {
2340
enabledAutoUpdateExtensions.push(`-${extensionId}`);
2341
}
2342
} else {
2343
if (disableAutoUpdatesForExtension) {
2344
enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(`-${extensionId}`), 1);
2345
}
2346
}
2347
}
2348
this.setEnabledAutoUpdateExtensions(enabledAutoUpdateExtensions);
2349
this._onChange.fire(extensionOrPublisher);
2350
}
2351
}
2352
2353
if (enable) {
2354
this.autoUpdateExtensions();
2355
}
2356
}
2357
2358
private onDidSelectedExtensionToAutoUpdateValueChange(): void {
2359
if (
2360
this.enabledAuotUpdateExtensionsValue !== this.getEnabledAutoUpdateExtensionsValue() /* This checks if current window changed the value or not */
2361
|| this.disabledAutoUpdateExtensionsValue !== this.getDisabledAutoUpdateExtensionsValue() /* This checks if current window changed the value or not */
2362
) {
2363
const userExtensions = this.installed.filter(e => !e.isBuiltin);
2364
const groupBy = (extensions: IExtension[]): IExtension[][] => {
2365
const shouldAutoUpdate: IExtension[] = [];
2366
const shouldNotAutoUpdate: IExtension[] = [];
2367
for (const extension of extensions) {
2368
if (this.shouldAutoUpdateExtension(extension)) {
2369
shouldAutoUpdate.push(extension);
2370
} else {
2371
shouldNotAutoUpdate.push(extension);
2372
}
2373
}
2374
return [shouldAutoUpdate, shouldNotAutoUpdate];
2375
};
2376
2377
const [wasShouldAutoUpdate, wasShouldNotAutoUpdate] = groupBy(userExtensions);
2378
this._enabledAutoUpdateExtensionsValue = undefined;
2379
this._disabledAutoUpdateExtensionsValue = undefined;
2380
const [shouldAutoUpdate, shouldNotAutoUpdate] = groupBy(userExtensions);
2381
2382
for (const e of wasShouldAutoUpdate ?? []) {
2383
if (shouldNotAutoUpdate?.includes(e)) {
2384
this._onChange.fire(e);
2385
}
2386
}
2387
for (const e of wasShouldNotAutoUpdate ?? []) {
2388
if (shouldAutoUpdate?.includes(e)) {
2389
this._onChange.fire(e);
2390
}
2391
}
2392
}
2393
}
2394
2395
async canInstall(extension: IExtension): Promise<true | IMarkdownString> {
2396
if (!(extension instanceof Extension)) {
2397
return new MarkdownString().appendText(nls.localize('not an extension', "The provided object is not an extension."));
2398
}
2399
2400
if (extension.isMalicious) {
2401
return new MarkdownString().appendText(nls.localize('malicious', "This extension is reported to be problematic."));
2402
}
2403
2404
if (extension.deprecationInfo?.disallowInstall) {
2405
return new MarkdownString().appendText(nls.localize('disallowed', "This extension is disallowed to be installed."));
2406
}
2407
2408
if (extension.gallery) {
2409
if (!extension.gallery.isSigned && shouldRequireRepositorySignatureFor(extension.private, await this.extensionGalleryManifestService.getExtensionGalleryManifest())) {
2410
return new MarkdownString().appendText(nls.localize('not signed', "This extension is not signed."));
2411
}
2412
2413
const localResult = this.localExtensions ? await this.localExtensions.canInstall(extension.gallery) : undefined;
2414
if (localResult === true) {
2415
return true;
2416
}
2417
2418
const remoteResult = this.remoteExtensions ? await this.remoteExtensions.canInstall(extension.gallery) : undefined;
2419
if (remoteResult === true) {
2420
return true;
2421
}
2422
2423
const webResult = this.webExtensions ? await this.webExtensions.canInstall(extension.gallery) : undefined;
2424
if (webResult === true) {
2425
return true;
2426
}
2427
2428
return localResult ?? remoteResult ?? webResult ?? new MarkdownString().appendText(nls.localize('cannot be installed', "Cannot install the '{0}' extension because it is not available in this setup.", extension.displayName ?? extension.identifier.id));
2429
}
2430
2431
if (extension.resourceExtension && await this.extensionManagementService.canInstall(extension.resourceExtension) === true) {
2432
return true;
2433
}
2434
2435
return new MarkdownString().appendText(nls.localize('cannot be installed', "Cannot install the '{0}' extension because it is not available in this setup.", extension.displayName ?? extension.identifier.id));
2436
}
2437
2438
async install(arg: string | URI | IExtension, installOptions: InstallExtensionOptions = {}, progressLocation?: ProgressLocation | string): Promise<IExtension> {
2439
let installable: URI | IGalleryExtension | IResourceExtension | undefined;
2440
let extension: IExtension | undefined;
2441
let servers: IExtensionManagementServer[] | undefined;
2442
2443
if (arg instanceof URI) {
2444
installable = arg;
2445
} else {
2446
let installableInfo: IExtensionInfo | undefined;
2447
let gallery: IGalleryExtension | undefined;
2448
2449
// Install by id
2450
if (isString(arg)) {
2451
extension = this.local.find(e => areSameExtensions(e.identifier, { id: arg }));
2452
if (!extension?.isBuiltin) {
2453
installableInfo = { id: arg, version: installOptions.version, preRelease: installOptions.installPreReleaseVersion ?? this.extensionManagementService.preferPreReleases };
2454
}
2455
}
2456
// Install by gallery
2457
else if (arg.gallery) {
2458
extension = arg;
2459
gallery = arg.gallery;
2460
if (installOptions.version && installOptions.version !== gallery?.version) {
2461
installableInfo = { id: extension.identifier.id, version: installOptions.version };
2462
}
2463
}
2464
// Install by resource
2465
else if (arg.resourceExtension) {
2466
extension = arg;
2467
installable = arg.resourceExtension;
2468
}
2469
2470
if (installableInfo) {
2471
const targetPlatform = extension?.server ? await extension.server.extensionManagementService.getTargetPlatform() : undefined;
2472
gallery = (await this.galleryService.getExtensions([installableInfo], { targetPlatform }, CancellationToken.None)).at(0);
2473
}
2474
2475
if (!extension && gallery) {
2476
extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, gallery, undefined);
2477
(<Extension>extension).setExtensionsControlManifest(await this.extensionManagementService.getExtensionsControlManifest());
2478
}
2479
2480
if (extension?.isMalicious) {
2481
throw new Error(nls.localize('malicious', "This extension is reported to be problematic."));
2482
}
2483
2484
if (gallery) {
2485
// If requested to install everywhere
2486
// then install the extension in all the servers where it is not installed
2487
if (installOptions.installEverywhere) {
2488
servers = [];
2489
const installableServers = await this.extensionManagementService.getInstallableServers(gallery);
2490
for (const extensionsServer of this.extensionsServers) {
2491
if (installableServers.includes(extensionsServer.server) && !extensionsServer.local.find(e => areSameExtensions(e.identifier, gallery.identifier))) {
2492
servers.push(extensionsServer.server);
2493
}
2494
}
2495
}
2496
// If requested to enable and extension is already installed
2497
// Check if the extension is disabled because of extension kind
2498
// If so, install the extension in the server that is compatible.
2499
else if (installOptions.enable && extension?.local) {
2500
servers = [];
2501
if (extension.enablementState === EnablementState.DisabledByExtensionKind) {
2502
const [installableServer] = await this.extensionManagementService.getInstallableServers(gallery);
2503
if (installableServer) {
2504
servers.push(installableServer);
2505
}
2506
}
2507
}
2508
}
2509
2510
if (!servers || servers.length) {
2511
if (!installable) {
2512
if (!gallery) {
2513
const id = isString(arg) ? arg : (<IExtension>arg).identifier.id;
2514
const manifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();
2515
const reportIssueUri = manifest ? getExtensionGalleryManifestResourceUri(manifest, ExtensionGalleryResourceType.ContactSupportUri) : undefined;
2516
const reportIssueMessage = reportIssueUri ? nls.localize('report issue', "If this issue persists, please report it at {0}", reportIssueUri.toString()) : '';
2517
if (installOptions.version) {
2518
const message = nls.localize('not found version', "The extension '{0}' cannot be installed because the requested version '{1}' was not found.", id, installOptions.version);
2519
throw new ExtensionManagementError(reportIssueMessage ? `${message} ${reportIssueMessage}` : message, ExtensionManagementErrorCode.NotFound);
2520
} else {
2521
const message = nls.localize('not found', "The extension '{0}' cannot be installed because it was not found.", id);
2522
throw new ExtensionManagementError(reportIssueMessage ? `${message} ${reportIssueMessage}` : message, ExtensionManagementErrorCode.NotFound);
2523
}
2524
}
2525
installable = gallery;
2526
}
2527
if (installOptions.version) {
2528
installOptions.installGivenVersion = true;
2529
}
2530
if (extension?.isWorkspaceScoped) {
2531
installOptions.isWorkspaceScoped = true;
2532
}
2533
}
2534
}
2535
2536
if (installable) {
2537
if (installOptions.justification) {
2538
const syncCheck = isUndefined(installOptions.isMachineScoped) && this.userDataSyncEnablementService.isEnabled() && this.userDataSyncEnablementService.isResourceEnabled(SyncResource.Extensions);
2539
const buttons: IPromptButton<boolean>[] = [];
2540
buttons.push({
2541
label: isString(installOptions.justification) || !installOptions.justification.action
2542
? nls.localize({ key: 'installButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Install Extension")
2543
: nls.localize({ key: 'installButtonLabelWithAction', comment: ['&& denotes a mnemonic'] }, "&&Install Extension and {0}", installOptions.justification.action), run: () => true
2544
});
2545
if (!extension) {
2546
buttons.push({ label: nls.localize('open', "Open Extension"), run: () => { this.open(extension!); return false; } });
2547
}
2548
const result = await this.dialogService.prompt<boolean>({
2549
title: nls.localize('installExtensionTitle', "Install Extension"),
2550
message: extension ? nls.localize('installExtensionMessage', "Would you like to install '{0}' extension from '{1}'?", extension.displayName, extension.publisherDisplayName) : nls.localize('installVSIXMessage', "Would you like to install the extension?"),
2551
detail: isString(installOptions.justification) ? installOptions.justification : installOptions.justification.reason,
2552
cancelButton: true,
2553
buttons,
2554
checkbox: syncCheck ? {
2555
label: nls.localize('sync extension', "Sync this extension"),
2556
checked: true,
2557
} : undefined,
2558
});
2559
if (!result.result) {
2560
throw new CancellationError();
2561
}
2562
if (syncCheck) {
2563
installOptions.isMachineScoped = !result.checkboxChecked;
2564
}
2565
}
2566
if (installable instanceof URI) {
2567
extension = await this.doInstall(undefined, () => this.installFromVSIX(installable, installOptions), progressLocation);
2568
} else if (extension) {
2569
if (extension.resourceExtension) {
2570
extension = await this.doInstall(extension, () => this.extensionManagementService.installResourceExtension(installable as IResourceExtension, installOptions), progressLocation);
2571
} else {
2572
extension = await this.doInstall(extension, () => this.installFromGallery(extension!, installable as IGalleryExtension, installOptions, servers), progressLocation);
2573
}
2574
}
2575
}
2576
2577
if (!extension) {
2578
throw new Error(nls.localize('unknown', "Unable to install extension"));
2579
}
2580
2581
if (installOptions.enable) {
2582
if (extension.enablementState === EnablementState.DisabledWorkspace || extension.enablementState === EnablementState.DisabledGlobally) {
2583
if (installOptions.justification) {
2584
const result = await this.dialogService.confirm({
2585
title: nls.localize('enableExtensionTitle', "Enable Extension"),
2586
message: nls.localize('enableExtensionMessage', "Would you like to enable '{0}' extension?", extension.displayName),
2587
detail: isString(installOptions.justification) ? installOptions.justification : installOptions.justification.reason,
2588
primaryButton: isString(installOptions.justification) ? nls.localize({ key: 'enableButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Enable Extension") : nls.localize({ key: 'enableButtonLabelWithAction', comment: ['&& denotes a mnemonic'] }, "&&Enable Extension and {0}", installOptions.justification.action),
2589
});
2590
if (!result.confirmed) {
2591
throw new CancellationError();
2592
}
2593
}
2594
await this.setEnablement(extension, extension.enablementState === EnablementState.DisabledWorkspace ? EnablementState.EnabledWorkspace : EnablementState.EnabledGlobally);
2595
}
2596
await this.waitUntilExtensionIsEnabled(extension);
2597
}
2598
2599
return extension;
2600
}
2601
2602
async installInServer(extension: IExtension, server: IExtensionManagementServer, installOptions?: InstallOptions): Promise<void> {
2603
await this.doInstall(extension, async () => {
2604
const local = extension.local;
2605
if (!local) {
2606
throw new Error('Extension not found');
2607
}
2608
if (!extension.gallery) {
2609
extension = (await this.getExtensions([{ ...extension.identifier, preRelease: local.preRelease }], CancellationToken.None))[0] ?? extension;
2610
}
2611
if (extension.gallery) {
2612
return server.extensionManagementService.installFromGallery(extension.gallery, { installPreReleaseVersion: local.preRelease, ...installOptions });
2613
}
2614
2615
const targetPlatform = await server.extensionManagementService.getTargetPlatform();
2616
if (!isTargetPlatformCompatible(local.targetPlatform, [local.targetPlatform], targetPlatform)) {
2617
throw new Error(nls.localize('incompatible', "Can't install '{0}' extension because it is not compatible.", extension.identifier.id));
2618
}
2619
2620
const vsix = await this.extensionManagementService.zip(local);
2621
try {
2622
return await server.extensionManagementService.install(vsix);
2623
} finally {
2624
try {
2625
await this.fileService.del(vsix);
2626
} catch (error) {
2627
this.logService.error(error);
2628
}
2629
}
2630
});
2631
}
2632
2633
canSetLanguage(extension: IExtension): boolean {
2634
if (!isWeb) {
2635
return false;
2636
}
2637
2638
if (!extension.gallery) {
2639
return false;
2640
}
2641
2642
const locale = getLocale(extension.gallery);
2643
if (!locale) {
2644
return false;
2645
}
2646
2647
return true;
2648
}
2649
2650
async setLanguage(extension: IExtension): Promise<void> {
2651
if (!this.canSetLanguage(extension)) {
2652
throw new Error('Can not set language');
2653
}
2654
const locale = getLocale(extension.gallery!);
2655
if (locale === language) {
2656
return;
2657
}
2658
const localizedLanguageName = extension.gallery?.properties?.localizedLanguages?.[0];
2659
return this.localeService.setLocale({ id: locale, galleryExtension: extension.gallery, extensionId: extension.identifier.id, label: localizedLanguageName ?? extension.displayName });
2660
}
2661
2662
setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise<void> {
2663
extensions = Array.isArray(extensions) ? extensions : [extensions];
2664
return this.promptAndSetEnablement(extensions, enablementState);
2665
}
2666
2667
async uninstall(e: IExtension): Promise<void> {
2668
const extension = e.local ? e : this.local.find(local => areSameExtensions(local.identifier, e.identifier));
2669
if (!extension?.local) {
2670
throw new Error('Missing local');
2671
}
2672
2673
if (extension.local.isApplicationScoped && this.userDataProfilesService.profiles.length > 1) {
2674
const { confirmed } = await this.dialogService.confirm({
2675
title: nls.localize('uninstallApplicationScoped', "Uninstall Extension"),
2676
type: Severity.Info,
2677
message: nls.localize('uninstallApplicationScopedMessage', "Would you like to Uninstall {0} from all profiles?", extension.displayName),
2678
primaryButton: nls.localize('uninstallAllProfiles', "Uninstall (All Profiles)")
2679
});
2680
if (!confirmed) {
2681
throw new CancellationError();
2682
}
2683
}
2684
2685
const extensionsToUninstall: UninstallExtensionInfo[] = [{ extension: extension.local }];
2686
for (const packExtension of this.getAllPackedExtensions(extension, this.local)) {
2687
if (packExtension.local && !extensionsToUninstall.some(e => areSameExtensions(e.extension.identifier, packExtension.identifier))) {
2688
extensionsToUninstall.push({ extension: packExtension.local });
2689
}
2690
}
2691
2692
const dependents: ILocalExtension[] = [];
2693
let extensionsFromAllProfiles: [ILocalExtension, URI][] | undefined;
2694
for (const { extension } of extensionsToUninstall) {
2695
const installedExtensions: [ILocalExtension, URI | undefined][] = [];
2696
if (extension.isApplicationScoped && this.userDataProfilesService.profiles.length > 1) {
2697
if (!extensionsFromAllProfiles) {
2698
extensionsFromAllProfiles = [];
2699
await Promise.allSettled(this.userDataProfilesService.profiles.map(async profile => {
2700
const installed = await this.extensionManagementService.getInstalled(ExtensionType.User, profile.extensionsResource);
2701
for (const local of installed) {
2702
extensionsFromAllProfiles?.push([local, profile.extensionsResource]);
2703
}
2704
}));
2705
}
2706
installedExtensions.push(...extensionsFromAllProfiles);
2707
} else {
2708
for (const { local } of this.local) {
2709
if (local) {
2710
installedExtensions.push([local, undefined]);
2711
}
2712
}
2713
}
2714
for (const [local, profileLocation] of installedExtensions) {
2715
if (areSameExtensions(local.identifier, extension.identifier)) {
2716
continue;
2717
}
2718
if (!local.manifest.extensionDependencies || local.manifest.extensionDependencies.length === 0) {
2719
continue;
2720
}
2721
if (extension.manifest.extensionPack?.some(id => areSameExtensions({ id }, local.identifier))) {
2722
continue;
2723
}
2724
if (dependents.some(d => d.manifest.extensionPack?.some(id => areSameExtensions({ id }, local.identifier)))) {
2725
continue;
2726
}
2727
if (local.manifest.extensionDependencies.some(dep => areSameExtensions(extension.identifier, { id: dep }))) {
2728
dependents.push(local);
2729
extensionsToUninstall.push({ extension: local, options: { profileLocation } });
2730
}
2731
}
2732
}
2733
2734
if (dependents.length) {
2735
const { result } = await this.dialogService.prompt({
2736
title: nls.localize('uninstallDependents', "Uninstall Extension with Dependents"),
2737
type: Severity.Warning,
2738
message: this.getErrorMessageForUninstallingAnExtensionWithDependents(extension, dependents),
2739
buttons: [{
2740
label: nls.localize('uninstallAll', "Uninstall All"),
2741
run: () => true
2742
}],
2743
cancelButton: {
2744
run: () => false
2745
}
2746
});
2747
if (!result) {
2748
throw new CancellationError();
2749
}
2750
}
2751
2752
return this.withProgress({
2753
location: ProgressLocation.Extensions,
2754
title: nls.localize('uninstallingExtension', 'Uninstalling extension...'),
2755
source: `${extension.identifier.id}`
2756
}, () => this.extensionManagementService.uninstallExtensions(extensionsToUninstall).then(() => undefined));
2757
}
2758
2759
private getAllPackedExtensions(extension: IExtension, installed: IExtension[], checked: IExtension[] = []): IExtension[] {
2760
if (checked.some(e => areSameExtensions(e.identifier, extension.identifier))) {
2761
return [];
2762
}
2763
checked.push(extension);
2764
const extensionsPack = extension.extensionPack ?? [];
2765
if (extensionsPack.length) {
2766
const packedExtensions: IExtension[] = [];
2767
for (const i of installed) {
2768
if (!i.isBuiltin && extensionsPack.some(id => areSameExtensions({ id }, i.identifier))) {
2769
packedExtensions.push(i);
2770
}
2771
}
2772
const packOfPackedExtensions: IExtension[] = [];
2773
for (const packedExtension of packedExtensions) {
2774
packOfPackedExtensions.push(...this.getAllPackedExtensions(packedExtension, installed, checked));
2775
}
2776
return [...packedExtensions, ...packOfPackedExtensions];
2777
}
2778
return [];
2779
}
2780
2781
private getErrorMessageForUninstallingAnExtensionWithDependents(extension: IExtension, dependents: ILocalExtension[]): string {
2782
if (dependents.length === 1) {
2783
return nls.localize('singleDependentUninstallError', "Cannot uninstall '{0}' extension alone. '{1}' extension depends on this. Do you want to uninstall all these extensions?", extension.displayName, dependents[0].manifest.displayName);
2784
}
2785
if (dependents.length === 2) {
2786
return nls.localize('twoDependentsUninstallError', "Cannot uninstall '{0}' extension alone. '{1}' and '{2}' extensions depend on this. Do you want to uninstall all these extensions?",
2787
extension.displayName, dependents[0].manifest.displayName, dependents[1].manifest.displayName);
2788
}
2789
return nls.localize('multipleDependentsUninstallError', "Cannot uninstall '{0}' extension alone. '{1}', '{2}' and other extensions depend on this. Do you want to uninstall all these extensions?",
2790
extension.displayName, dependents[0].manifest.displayName, dependents[1].manifest.displayName);
2791
}
2792
2793
isExtensionIgnoredToSync(extension: IExtension): boolean {
2794
return extension.local ? !this.isInstalledExtensionSynced(extension.local)
2795
: this.extensionsSyncManagementService.hasToNeverSyncExtension(extension.identifier.id);
2796
}
2797
2798
async togglePreRelease(extension: IExtension): Promise<void> {
2799
if (!extension.local) {
2800
return;
2801
}
2802
if (extension.preRelease !== extension.isPreReleaseVersion) {
2803
await this.extensionManagementService.updateMetadata(extension.local, { preRelease: !extension.preRelease });
2804
return;
2805
}
2806
await this.install(extension, { installPreReleaseVersion: !extension.preRelease, preRelease: !extension.preRelease });
2807
}
2808
2809
async toggleExtensionIgnoredToSync(extension: IExtension): Promise<void> {
2810
const extensionsIncludingPackedExtensions = [extension, ...this.getAllPackedExtensions(extension, this.local)];
2811
// Updated in sync to prevent race conditions
2812
for (const e of extensionsIncludingPackedExtensions) {
2813
const isIgnored = this.isExtensionIgnoredToSync(e);
2814
if (e.local && isIgnored && e.local.isMachineScoped) {
2815
await this.extensionManagementService.updateMetadata(e.local, { isMachineScoped: false });
2816
} else {
2817
await this.extensionsSyncManagementService.updateIgnoredExtensions(e.identifier.id, !isIgnored);
2818
}
2819
}
2820
await this.userDataAutoSyncService.triggerSync(['IgnoredExtensionsUpdated']);
2821
}
2822
2823
async toggleApplyExtensionToAllProfiles(extension: IExtension): Promise<void> {
2824
const extensionsIncludingPackedExtensions = [extension, ...this.getAllPackedExtensions(extension, this.local)];
2825
const allExtensionServers = this.getAllExtensionServers();
2826
await Promise.allSettled(extensionsIncludingPackedExtensions.map(async e => {
2827
if (!e.local || isApplicationScopedExtension(e.local.manifest) || e.isBuiltin) {
2828
return;
2829
}
2830
const isApplicationScoped = e.local.isApplicationScoped;
2831
await Promise.all(allExtensionServers.map(async extensionServer => {
2832
const local = extensionServer.local.find(local => areSameExtensions(e.identifier, local.identifier))?.local;
2833
if (local && local.isApplicationScoped === isApplicationScoped) {
2834
await this.extensionManagementService.toggleApplicationScope(local, this.userDataProfileService.currentProfile.extensionsResource);
2835
}
2836
}));
2837
}));
2838
}
2839
2840
private getAllExtensionServers(): Extensions[] {
2841
const extensions: Extensions[] = [];
2842
if (this.localExtensions) {
2843
extensions.push(this.localExtensions);
2844
}
2845
if (this.remoteExtensions) {
2846
extensions.push(this.remoteExtensions);
2847
}
2848
if (this.webExtensions) {
2849
extensions.push(this.webExtensions);
2850
}
2851
return extensions;
2852
}
2853
2854
private isInstalledExtensionSynced(extension: ILocalExtension): boolean {
2855
if (extension.isMachineScoped) {
2856
return false;
2857
}
2858
if (this.extensionsSyncManagementService.hasToAlwaysSyncExtension(extension.identifier.id)) {
2859
return true;
2860
}
2861
return !this.extensionsSyncManagementService.hasToNeverSyncExtension(extension.identifier.id);
2862
}
2863
2864
private doInstall(extension: IExtension | undefined, installTask: () => Promise<ILocalExtension>, progressLocation?: ProgressLocation | string): Promise<IExtension> {
2865
const title = extension ? nls.localize('installing named extension', "Installing '{0}' extension...", extension.displayName) : nls.localize('installing extension', 'Installing extension...');
2866
return this.withProgress({
2867
location: progressLocation ?? ProgressLocation.Extensions,
2868
title
2869
}, async () => {
2870
try {
2871
if (extension) {
2872
this.installing.push(extension);
2873
this._onChange.fire(extension);
2874
}
2875
const local = await installTask();
2876
return await this.waitAndGetInstalledExtension(local.identifier);
2877
} finally {
2878
if (extension) {
2879
this.installing = this.installing.filter(e => e !== extension);
2880
// Trigger the change without passing the extension because it is replaced by a new instance.
2881
this._onChange.fire(undefined);
2882
}
2883
}
2884
});
2885
}
2886
2887
private async installFromVSIX(vsix: URI, installOptions: InstallOptions): Promise<ILocalExtension> {
2888
const manifest = await this.extensionManagementService.getManifest(vsix);
2889
const existingExtension = this.local.find(local => areSameExtensions(local.identifier, { id: getGalleryExtensionId(manifest.publisher, manifest.name) }));
2890
if (existingExtension) {
2891
installOptions = installOptions || {};
2892
if (existingExtension.latestVersion === manifest.version) {
2893
installOptions.pinned = installOptions.pinned ?? (existingExtension.local?.pinned || !this.shouldAutoUpdateExtension(existingExtension));
2894
} else {
2895
installOptions.installGivenVersion = true;
2896
}
2897
}
2898
return this.extensionManagementService.installVSIX(vsix, manifest, installOptions);
2899
}
2900
2901
private installFromGallery(extension: IExtension, gallery: IGalleryExtension, installOptions: InstallExtensionOptions, servers: IExtensionManagementServer[] | undefined): Promise<ILocalExtension> {
2902
installOptions = installOptions ?? {};
2903
installOptions.pinned = installOptions.pinned ?? (extension.local?.pinned || !this.shouldAutoUpdateExtension(extension));
2904
if (extension.local && !servers) {
2905
installOptions.productVersion = this.getProductVersion();
2906
installOptions.operation = InstallOperation.Update;
2907
return this.extensionManagementService.updateFromGallery(gallery, extension.local, installOptions);
2908
} else {
2909
return this.extensionManagementService.installFromGallery(gallery, installOptions, servers);
2910
}
2911
}
2912
2913
private async waitAndGetInstalledExtension(identifier: IExtensionIdentifier): Promise<IExtension> {
2914
let installedExtension = this.local.find(local => areSameExtensions(local.identifier, identifier));
2915
if (!installedExtension) {
2916
await Event.toPromise(Event.filter(this.onChange, e => !!e && this.local.some(local => areSameExtensions(local.identifier, identifier))));
2917
}
2918
installedExtension = this.local.find(local => areSameExtensions(local.identifier, identifier));
2919
if (!installedExtension) {
2920
// This should not happen
2921
throw new Error('Extension should have been installed');
2922
}
2923
return installedExtension;
2924
}
2925
2926
private async waitUntilExtensionIsEnabled(extension: IExtension): Promise<void> {
2927
if (this.extensionService.extensions.find(e => ExtensionIdentifier.equals(e.identifier, extension.identifier.id))) {
2928
return;
2929
}
2930
if (!extension.local || !this.extensionService.canAddExtension(toExtensionDescription(extension.local))) {
2931
return;
2932
}
2933
await new Promise<void>((c, e) => {
2934
const disposable = this.extensionService.onDidChangeExtensions(() => {
2935
try {
2936
if (this.extensionService.extensions.find(e => ExtensionIdentifier.equals(e.identifier, extension.identifier.id))) {
2937
disposable.dispose();
2938
c();
2939
}
2940
} catch (error) {
2941
e(error);
2942
}
2943
});
2944
});
2945
}
2946
2947
private promptAndSetEnablement(extensions: IExtension[], enablementState: EnablementState): Promise<any> {
2948
const enable = enablementState === EnablementState.EnabledGlobally || enablementState === EnablementState.EnabledWorkspace;
2949
if (enable) {
2950
const allDependenciesAndPackedExtensions = this.getExtensionsRecursively(extensions, this.local, enablementState, { dependencies: true, pack: true });
2951
return this.checkAndSetEnablement(extensions, allDependenciesAndPackedExtensions, enablementState);
2952
} else {
2953
const packedExtensions = this.getExtensionsRecursively(extensions, this.local, enablementState, { dependencies: false, pack: true });
2954
if (packedExtensions.length) {
2955
return this.checkAndSetEnablement(extensions, packedExtensions, enablementState);
2956
}
2957
return this.checkAndSetEnablement(extensions, [], enablementState);
2958
}
2959
}
2960
2961
private async checkAndSetEnablement(extensions: IExtension[], otherExtensions: IExtension[], enablementState: EnablementState): Promise<any> {
2962
const allExtensions = [...extensions, ...otherExtensions];
2963
const enable = enablementState === EnablementState.EnabledGlobally || enablementState === EnablementState.EnabledWorkspace;
2964
if (!enable) {
2965
for (const extension of extensions) {
2966
const dependents = this.getDependentsAfterDisablement(extension, allExtensions, this.local);
2967
if (dependents.length) {
2968
const { result } = await this.dialogService.prompt({
2969
title: nls.localize('disableDependents', "Disable Extension with Dependents"),
2970
type: Severity.Warning,
2971
message: this.getDependentsErrorMessageForDisablement(extension, allExtensions, dependents),
2972
buttons: [{
2973
label: nls.localize('disable all', 'Disable All'),
2974
run: () => true
2975
}],
2976
cancelButton: {
2977
run: () => false
2978
}
2979
});
2980
if (!result) {
2981
throw new CancellationError();
2982
}
2983
await this.checkAndSetEnablement(dependents, [extension], enablementState);
2984
}
2985
}
2986
}
2987
return this.doSetEnablement(allExtensions, enablementState);
2988
}
2989
2990
private getExtensionsRecursively(extensions: IExtension[], installed: IExtension[], enablementState: EnablementState, options: { dependencies: boolean; pack: boolean }, checked: IExtension[] = []): IExtension[] {
2991
const toCheck = extensions.filter(e => checked.indexOf(e) === -1);
2992
if (toCheck.length) {
2993
for (const extension of toCheck) {
2994
checked.push(extension);
2995
}
2996
const extensionsToEanbleOrDisable = installed.filter(i => {
2997
if (checked.indexOf(i) !== -1) {
2998
return false;
2999
}
3000
const enable = enablementState === EnablementState.EnabledGlobally || enablementState === EnablementState.EnabledWorkspace;
3001
const isExtensionEnabled = i.enablementState === EnablementState.EnabledGlobally || i.enablementState === EnablementState.EnabledWorkspace;
3002
if (enable === isExtensionEnabled) {
3003
return false;
3004
}
3005
return (enable || !i.isBuiltin) // Include all Extensions for enablement and only non builtin extensions for disablement
3006
&& (options.dependencies || options.pack)
3007
&& extensions.some(extension =>
3008
(options.dependencies && extension.dependencies.some(id => areSameExtensions({ id }, i.identifier)))
3009
|| (options.pack && extension.extensionPack.some(id => areSameExtensions({ id }, i.identifier)))
3010
);
3011
});
3012
if (extensionsToEanbleOrDisable.length) {
3013
extensionsToEanbleOrDisable.push(...this.getExtensionsRecursively(extensionsToEanbleOrDisable, installed, enablementState, options, checked));
3014
}
3015
return extensionsToEanbleOrDisable;
3016
}
3017
return [];
3018
}
3019
3020
private getDependentsAfterDisablement(extension: IExtension, extensionsToDisable: IExtension[], installed: IExtension[]): IExtension[] {
3021
return installed.filter(i => {
3022
if (i.dependencies.length === 0) {
3023
return false;
3024
}
3025
if (i === extension) {
3026
return false;
3027
}
3028
if (!this.extensionEnablementService.isEnabledEnablementState(i.enablementState)) {
3029
return false;
3030
}
3031
if (extensionsToDisable.indexOf(i) !== -1) {
3032
return false;
3033
}
3034
return i.dependencies.some(dep => [extension, ...extensionsToDisable].some(d => areSameExtensions(d.identifier, { id: dep })));
3035
});
3036
}
3037
3038
private getDependentsErrorMessageForDisablement(extension: IExtension, allDisabledExtensions: IExtension[], dependents: IExtension[]): string {
3039
for (const e of [extension, ...allDisabledExtensions]) {
3040
const dependentsOfTheExtension = dependents.filter(d => d.dependencies.some(id => areSameExtensions({ id }, e.identifier)));
3041
if (dependentsOfTheExtension.length) {
3042
return this.getErrorMessageForDisablingAnExtensionWithDependents(e, dependentsOfTheExtension);
3043
}
3044
}
3045
return '';
3046
}
3047
3048
private getErrorMessageForDisablingAnExtensionWithDependents(extension: IExtension, dependents: IExtension[]): string {
3049
if (dependents.length === 1) {
3050
return nls.localize('singleDependentError', "Cannot disable '{0}' extension alone. '{1}' extension depends on this. Do you want to disable all these extensions?", extension.displayName, dependents[0].displayName);
3051
}
3052
if (dependents.length === 2) {
3053
return nls.localize('twoDependentsError', "Cannot disable '{0}' extension alone. '{1}' and '{2}' extensions depend on this. Do you want to disable all these extensions?",
3054
extension.displayName, dependents[0].displayName, dependents[1].displayName);
3055
}
3056
return nls.localize('multipleDependentsError', "Cannot disable '{0}' extension alone. '{1}', '{2}' and other extensions depend on this. Do you want to disable all these extensions?",
3057
extension.displayName, dependents[0].displayName, dependents[1].displayName);
3058
}
3059
3060
private async doSetEnablement(extensions: IExtension[], enablementState: EnablementState): Promise<boolean[]> {
3061
return await this.extensionEnablementService.setEnablement(extensions.map(e => e.local!), enablementState);
3062
}
3063
3064
// Current service reports progress when installing/uninstalling extensions
3065
// This is to report progress for other sources of extension install/uninstall changes
3066
// Since we cannot differentiate between the two, we report progress for all extension install/uninstall changes
3067
private _activityCallBack: ((value: void) => void) | undefined;
3068
private reportProgressFromOtherSources(): void {
3069
if (this.installed.some(e => e.state === ExtensionState.Installing || e.state === ExtensionState.Uninstalling)) {
3070
if (!this._activityCallBack) {
3071
this.withProgress({ location: ProgressLocation.Extensions }, () => new Promise(resolve => this._activityCallBack = resolve));
3072
}
3073
} else {
3074
this._activityCallBack?.();
3075
this._activityCallBack = undefined;
3076
}
3077
}
3078
3079
private withProgress<T>(options: IProgressOptions, task: () => Promise<T>): Promise<T> {
3080
return this.progressService.withProgress(options, async () => {
3081
const cancelableTask = createCancelablePromise(() => task());
3082
this.tasksInProgress.push(cancelableTask);
3083
try {
3084
return await cancelableTask;
3085
} finally {
3086
const index = this.tasksInProgress.indexOf(cancelableTask);
3087
if (index !== -1) {
3088
this.tasksInProgress.splice(index, 1);
3089
}
3090
}
3091
});
3092
}
3093
3094
private onError(err: any): void {
3095
if (isCancellationError(err)) {
3096
return;
3097
}
3098
3099
const message = err && err.message || '';
3100
3101
if (/getaddrinfo ENOTFOUND|getaddrinfo ENOENT|connect EACCES|connect ECONNREFUSED/.test(message)) {
3102
return;
3103
}
3104
3105
this.notificationService.error(err);
3106
}
3107
3108
handleURL(uri: URI, options?: IOpenURLOptions): Promise<boolean> {
3109
if (!/^extension/.test(uri.path)) {
3110
return Promise.resolve(false);
3111
}
3112
3113
this.onOpenExtensionUrl(uri);
3114
return Promise.resolve(true);
3115
}
3116
3117
private onOpenExtensionUrl(uri: URI): void {
3118
const match = /^extension\/([^/]+)$/.exec(uri.path);
3119
3120
if (!match) {
3121
return;
3122
}
3123
3124
const extensionId = match[1];
3125
3126
this.queryLocal().then(async local => {
3127
let extension = local.find(local => areSameExtensions(local.identifier, { id: extensionId }));
3128
if (!extension) {
3129
[extension] = await this.getExtensions([{ id: extensionId }], { source: 'uri' }, CancellationToken.None);
3130
}
3131
if (extension) {
3132
await this.hostService.focus(mainWindow);
3133
await this.open(extension);
3134
}
3135
}).then(undefined, error => this.onError(error));
3136
}
3137
3138
private getPublishersToAutoUpdate(): string[] {
3139
return this.getEnabledAutoUpdateExtensions().filter(id => !EXTENSION_IDENTIFIER_REGEX.test(id));
3140
}
3141
3142
getEnabledAutoUpdateExtensions(): string[] {
3143
try {
3144
const parsedValue = JSON.parse(this.enabledAuotUpdateExtensionsValue);
3145
if (Array.isArray(parsedValue)) {
3146
return parsedValue;
3147
}
3148
} catch (e) { /* Ignore */ }
3149
return [];
3150
}
3151
3152
private setEnabledAutoUpdateExtensions(enabledAutoUpdateExtensions: string[]): void {
3153
this.enabledAuotUpdateExtensionsValue = JSON.stringify(enabledAutoUpdateExtensions);
3154
}
3155
3156
private _enabledAutoUpdateExtensionsValue: string | undefined;
3157
private get enabledAuotUpdateExtensionsValue(): string {
3158
if (!this._enabledAutoUpdateExtensionsValue) {
3159
this._enabledAutoUpdateExtensionsValue = this.getEnabledAutoUpdateExtensionsValue();
3160
}
3161
3162
return this._enabledAutoUpdateExtensionsValue;
3163
}
3164
3165
private set enabledAuotUpdateExtensionsValue(enabledAuotUpdateExtensionsValue: string) {
3166
if (this.enabledAuotUpdateExtensionsValue !== enabledAuotUpdateExtensionsValue) {
3167
this._enabledAutoUpdateExtensionsValue = enabledAuotUpdateExtensionsValue;
3168
this.setEnabledAutoUpdateExtensionsValue(enabledAuotUpdateExtensionsValue);
3169
}
3170
}
3171
3172
private getEnabledAutoUpdateExtensionsValue(): string {
3173
return this.storageService.get(EXTENSIONS_AUTO_UPDATE_KEY, StorageScope.APPLICATION, '[]');
3174
}
3175
3176
private setEnabledAutoUpdateExtensionsValue(value: string): void {
3177
this.storageService.store(EXTENSIONS_AUTO_UPDATE_KEY, value, StorageScope.APPLICATION, StorageTarget.USER);
3178
}
3179
3180
getDisabledAutoUpdateExtensions(): string[] {
3181
try {
3182
const parsedValue = JSON.parse(this.disabledAutoUpdateExtensionsValue);
3183
if (Array.isArray(parsedValue)) {
3184
return parsedValue;
3185
}
3186
} catch (e) { /* Ignore */ }
3187
return [];
3188
}
3189
3190
private setDisabledAutoUpdateExtensions(disabledAutoUpdateExtensions: string[]): void {
3191
this.disabledAutoUpdateExtensionsValue = JSON.stringify(disabledAutoUpdateExtensions);
3192
}
3193
3194
private _disabledAutoUpdateExtensionsValue: string | undefined;
3195
private get disabledAutoUpdateExtensionsValue(): string {
3196
if (!this._disabledAutoUpdateExtensionsValue) {
3197
this._disabledAutoUpdateExtensionsValue = this.getDisabledAutoUpdateExtensionsValue();
3198
}
3199
3200
return this._disabledAutoUpdateExtensionsValue;
3201
}
3202
3203
private set disabledAutoUpdateExtensionsValue(disabledAutoUpdateExtensionsValue: string) {
3204
if (this.disabledAutoUpdateExtensionsValue !== disabledAutoUpdateExtensionsValue) {
3205
this._disabledAutoUpdateExtensionsValue = disabledAutoUpdateExtensionsValue;
3206
this.setDisabledAutoUpdateExtensionsValue(disabledAutoUpdateExtensionsValue);
3207
}
3208
}
3209
3210
private getDisabledAutoUpdateExtensionsValue(): string {
3211
return this.storageService.get(EXTENSIONS_DONOT_AUTO_UPDATE_KEY, StorageScope.APPLICATION, '[]');
3212
}
3213
3214
private setDisabledAutoUpdateExtensionsValue(value: string): void {
3215
this.storageService.store(EXTENSIONS_DONOT_AUTO_UPDATE_KEY, value, StorageScope.APPLICATION, StorageTarget.USER);
3216
}
3217
3218
private getDismissedNotifications(): string[] {
3219
try {
3220
const parsedValue = JSON.parse(this.dismissedNotificationsValue);
3221
if (Array.isArray(parsedValue)) {
3222
return parsedValue;
3223
}
3224
} catch (e) { /* Ignore */ }
3225
return [];
3226
}
3227
3228
private setDismissedNotifications(dismissedNotifications: string[]): void {
3229
this.dismissedNotificationsValue = JSON.stringify(dismissedNotifications);
3230
}
3231
3232
private _dismissedNotificationsValue: string | undefined;
3233
private get dismissedNotificationsValue(): string {
3234
if (!this._dismissedNotificationsValue) {
3235
this._dismissedNotificationsValue = this.getDismissedNotificationsValue();
3236
}
3237
3238
return this._dismissedNotificationsValue;
3239
}
3240
3241
private set dismissedNotificationsValue(dismissedNotificationsValue: string) {
3242
if (this.dismissedNotificationsValue !== dismissedNotificationsValue) {
3243
this._dismissedNotificationsValue = dismissedNotificationsValue;
3244
this.setDismissedNotificationsValue(dismissedNotificationsValue);
3245
}
3246
}
3247
3248
private getDismissedNotificationsValue(): string {
3249
return this.storageService.get(EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY, StorageScope.PROFILE, '[]');
3250
}
3251
3252
private setDismissedNotificationsValue(value: string): void {
3253
this.storageService.store(EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY, value, StorageScope.PROFILE, StorageTarget.USER);
3254
}
3255
3256
}
3257
3258