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