Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/extensionManagement/electron-browser/extensionGalleryManifestService.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { CancellationToken } from '../../../../base/common/cancellation.js';
7
import { Emitter } from '../../../../base/common/event.js';
8
import { IHeaders } from '../../../../base/parts/request/common/request.js';
9
import { localize } from '../../../../nls.js';
10
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
11
import { IEnvironmentService } from '../../../../platform/environment/common/environment.js';
12
import { IExtensionGalleryManifestService, IExtensionGalleryManifest, ExtensionGalleryServiceUrlConfigKey, ExtensionGalleryManifestStatus } from '../../../../platform/extensionManagement/common/extensionGalleryManifest.js';
13
import { ExtensionGalleryManifestService as ExtensionGalleryManifestService } from '../../../../platform/extensionManagement/common/extensionGalleryManifestService.js';
14
import { resolveMarketplaceHeaders } from '../../../../platform/externalServices/common/marketplace.js';
15
import { IFileService } from '../../../../platform/files/common/files.js';
16
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
17
import { ISharedProcessService } from '../../../../platform/ipc/electron-browser/services.js';
18
import { ILogService } from '../../../../platform/log/common/log.js';
19
import { IProductService } from '../../../../platform/product/common/productService.js';
20
import { asJson, IRequestService } from '../../../../platform/request/common/request.js';
21
import { IStorageService } from '../../../../platform/storage/common/storage.js';
22
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
23
import { IDefaultAccountService } from '../../accounts/common/defaultAccount.js';
24
import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js';
25
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
26
import { IHostService } from '../../host/browser/host.js';
27
import { IDefaultAccount } from '../../../../base/common/defaultAccount.js';
28
29
export class WorkbenchExtensionGalleryManifestService extends ExtensionGalleryManifestService implements IExtensionGalleryManifestService {
30
31
private readonly commonHeadersPromise: Promise<IHeaders>;
32
private extensionGalleryManifest: IExtensionGalleryManifest | null = null;
33
34
private _onDidChangeExtensionGalleryManifest = this._register(new Emitter<IExtensionGalleryManifest | null>());
35
override readonly onDidChangeExtensionGalleryManifest = this._onDidChangeExtensionGalleryManifest.event;
36
37
private currentStatus: ExtensionGalleryManifestStatus = ExtensionGalleryManifestStatus.Unavailable;
38
override get extensionGalleryManifestStatus(): ExtensionGalleryManifestStatus { return this.currentStatus; }
39
private _onDidChangeExtensionGalleryManifestStatus = this._register(new Emitter<ExtensionGalleryManifestStatus>());
40
override readonly onDidChangeExtensionGalleryManifestStatus = this._onDidChangeExtensionGalleryManifestStatus.event;
41
42
constructor(
43
@IProductService productService: IProductService,
44
@IEnvironmentService environmentService: IEnvironmentService,
45
@IFileService fileService: IFileService,
46
@ITelemetryService telemetryService: ITelemetryService,
47
@IStorageService storageService: IStorageService,
48
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
49
@ISharedProcessService sharedProcessService: ISharedProcessService,
50
@IConfigurationService private readonly configurationService: IConfigurationService,
51
@IRequestService private readonly requestService: IRequestService,
52
@IDefaultAccountService private readonly defaultAccountService: IDefaultAccountService,
53
@ILogService private readonly logService: ILogService,
54
@IDialogService private readonly dialogService: IDialogService,
55
@IHostService private readonly hostService: IHostService,
56
) {
57
super(productService);
58
this.commonHeadersPromise = resolveMarketplaceHeaders(
59
productService.version,
60
productService,
61
environmentService,
62
configurationService,
63
fileService,
64
storageService,
65
telemetryService);
66
67
const channels = [sharedProcessService.getChannel('extensionGalleryManifest')];
68
const remoteConnection = remoteAgentService.getConnection();
69
if (remoteConnection) {
70
channels.push(remoteConnection.getChannel('extensionGalleryManifest'));
71
}
72
this.getExtensionGalleryManifest().then(manifest => {
73
channels.forEach(channel => channel.call('setExtensionGalleryManifest', [manifest]));
74
});
75
}
76
77
private extensionGalleryManifestPromise: Promise<void> | undefined;
78
override async getExtensionGalleryManifest(): Promise<IExtensionGalleryManifest | null> {
79
if (!this.extensionGalleryManifestPromise) {
80
this.extensionGalleryManifestPromise = this.doGetExtensionGalleryManifest();
81
}
82
await this.extensionGalleryManifestPromise;
83
return this.extensionGalleryManifest;
84
}
85
86
private async doGetExtensionGalleryManifest(): Promise<void> {
87
const defaultServiceUrl = this.productService.extensionsGallery?.serviceUrl;
88
if (!defaultServiceUrl) {
89
return;
90
}
91
92
const configuredServiceUrl = this.configurationService.getValue<string>(ExtensionGalleryServiceUrlConfigKey);
93
if (configuredServiceUrl) {
94
await this.handleDefaultAccountAccess(configuredServiceUrl);
95
this._register(this.defaultAccountService.onDidChangeDefaultAccount(() => this.handleDefaultAccountAccess(configuredServiceUrl)));
96
} else {
97
const defaultExtensionGalleryManifest = await super.getExtensionGalleryManifest();
98
this.update(defaultExtensionGalleryManifest);
99
}
100
101
this._register(this.configurationService.onDidChangeConfiguration(e => {
102
if (!e.affectsConfiguration(ExtensionGalleryServiceUrlConfigKey)) {
103
return;
104
}
105
this.requestRestart();
106
}));
107
}
108
109
private async handleDefaultAccountAccess(configuredServiceUrl: string): Promise<void> {
110
const account = await this.defaultAccountService.getDefaultAccount();
111
112
if (!account) {
113
this.logService.debug('[Marketplace] Enterprise marketplace configured but user not signed in');
114
this.update(null, ExtensionGalleryManifestStatus.RequiresSignIn);
115
} else if (!this.checkAccess(account)) {
116
this.logService.debug('[Marketplace] User signed in but lacks access to enterprise marketplace');
117
this.update(null, ExtensionGalleryManifestStatus.AccessDenied);
118
} else if (this.currentStatus !== ExtensionGalleryManifestStatus.Available) {
119
try {
120
const manifest = await this.getExtensionGalleryManifestFromServiceUrl(configuredServiceUrl);
121
this.update(manifest);
122
} catch (error) {
123
this.logService.error('[Marketplace] Error retrieving enterprise gallery manifest', error);
124
this.update(null, ExtensionGalleryManifestStatus.AccessDenied);
125
}
126
}
127
}
128
129
private update(manifest: IExtensionGalleryManifest | null, status?: ExtensionGalleryManifestStatus): void {
130
if (this.extensionGalleryManifest !== manifest) {
131
this.extensionGalleryManifest = manifest;
132
this._onDidChangeExtensionGalleryManifest.fire(manifest);
133
}
134
this.updateStatus(status ?? (this.extensionGalleryManifest ? ExtensionGalleryManifestStatus.Available : ExtensionGalleryManifestStatus.Unavailable));
135
}
136
137
private updateStatus(status: ExtensionGalleryManifestStatus): void {
138
if (this.currentStatus !== status) {
139
this.currentStatus = status;
140
this._onDidChangeExtensionGalleryManifestStatus.fire(status);
141
}
142
}
143
144
private checkAccess(account: IDefaultAccount): boolean {
145
this.logService.debug('[Marketplace] Checking Account SKU access for configured gallery', account.access_type_sku);
146
if (account.access_type_sku && this.productService.extensionsGallery?.accessSKUs?.includes(account.access_type_sku)) {
147
this.logService.debug('[Marketplace] Account has access to configured gallery');
148
return true;
149
}
150
this.logService.debug('[Marketplace] Checking enterprise account access for configured gallery', account.enterprise);
151
return account.enterprise;
152
}
153
154
private async requestRestart(): Promise<void> {
155
const confirmation = await this.dialogService.confirm({
156
message: localize('extensionGalleryManifestService.accountChange', "{0} is now configured to a different Marketplace. Please restart to apply the changes.", this.productService.nameLong),
157
primaryButton: localize({ key: 'restart', comment: ['&& denotes a mnemonic'] }, "&&Restart")
158
});
159
if (confirmation.confirmed) {
160
return this.hostService.restart();
161
}
162
}
163
164
private async getExtensionGalleryManifestFromServiceUrl(url: string): Promise<IExtensionGalleryManifest> {
165
const commonHeaders = await this.commonHeadersPromise;
166
const headers = {
167
...commonHeaders,
168
'Content-Type': 'application/json',
169
'Accept-Encoding': 'gzip',
170
};
171
172
try {
173
const context = await this.requestService.request({
174
type: 'GET',
175
url,
176
headers,
177
}, CancellationToken.None);
178
179
const extensionGalleryManifest = await asJson<IExtensionGalleryManifest>(context);
180
181
if (!extensionGalleryManifest) {
182
throw new Error('Unable to retrieve extension gallery manifest.');
183
}
184
185
return extensionGalleryManifest;
186
} catch (error) {
187
this.logService.error('[Marketplace] Error retrieving extension gallery manifest', error);
188
throw error;
189
}
190
}
191
}
192
193
registerSingleton(IExtensionGalleryManifestService, WorkbenchExtensionGalleryManifestService, InstantiationType.Eager);
194
195