Skip to content

Commit b0e30fb

Browse files
jeanp413mustard-mh
andauthored
Do some refactor and cleanup (#43)
* Do some refactor and cleanup * Fix workspaceStatusStreaming dispose Co-authored-by: Huiwen <[email protected]>
1 parent c8eaaf0 commit b0e30fb

30 files changed

+1227
-801
lines changed

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@
111111
"command": "gitpod.installLocalExtensions",
112112
"title": "Gitpod: Install Local Extensions...",
113113
"enablement": "gitpod.inWorkspace == true"
114+
},
115+
{
116+
"command": "gitpod.signIn",
117+
"category": "Gitpod",
118+
"title": "Sign In"
114119
}
115120
],
116121
"menus": {

src/authentication.ts renamed to src/authentication/authentication.ts

Lines changed: 40 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55

66
import * as vscode from 'vscode';
77
import { v4 as uuid } from 'uuid';
8-
import Keychain from './common/keychain';
8+
import Keychain from '../common/keychain';
99
import GitpodServer from './gitpodServer';
10-
import { arrayEquals } from './common/utils';
11-
import { Disposable } from './common/dispose';
12-
import TelemetryReporter from './telemetryReporter';
13-
import { UserFlowTelemetry } from './common/telemetry';
14-
import { NotificationService } from './notification';
10+
import { arrayEquals } from '../common/utils';
11+
import { Disposable } from '../common/dispose';
12+
import { ITelemetryService, UserFlowTelemetry } from '../services/telemetryService';
13+
import { INotificationService } from '../services/notificationService';
14+
import { ILogService } from '../services/logService';
1515

1616
interface SessionData {
1717
id: string;
@@ -26,7 +26,6 @@ interface SessionData {
2626

2727
export default class GitpodAuthenticationProvider extends Disposable implements vscode.AuthenticationProvider {
2828
private _sessionChangeEmitter = new vscode.EventEmitter<vscode.AuthenticationProviderAuthenticationSessionsChangeEvent>();
29-
private _telemetry: TelemetryReporter;
3029

3130
private _gitpodServer!: GitpodServer;
3231
private _keychain!: Keychain;
@@ -38,14 +37,12 @@ export default class GitpodAuthenticationProvider extends Disposable implements
3837

3938
constructor(
4039
private readonly context: vscode.ExtensionContext,
41-
private readonly _logger: vscode.LogOutputChannel,
42-
telemetry: TelemetryReporter,
43-
private readonly notifications: NotificationService
40+
private readonly logService: ILogService,
41+
private readonly telemetryService: ITelemetryService,
42+
private readonly notificationService: INotificationService
4443
) {
4544
super();
4645

47-
this._telemetry = telemetry;
48-
4946
this.reconcile();
5047
this._register(vscode.workspace.onDidChangeConfiguration(e => {
5148
if (e.affectsConfiguration('gitpod.host')) {
@@ -67,9 +64,9 @@ export default class GitpodAuthenticationProvider extends Disposable implements
6764
this._serviceUrl = gitpodHostUrl.toString().replace(/\/$/, '');
6865
Object.assign(this.flow, { gitpodHost: this._serviceUrl });
6966
this._gitpodServer?.dispose();
70-
this._gitpodServer = new GitpodServer(this._serviceUrl, this._logger, this.notifications);
71-
this._keychain = new Keychain(this.context, `gitpod.auth.${gitpodHostUrl.hostname}`, this._logger);
72-
this._logger.info(`Started authentication provider for ${gitpodHost}`);
67+
this._gitpodServer = new GitpodServer(this._serviceUrl, this.logService, this.notificationService);
68+
this._keychain = new Keychain(this.context, `gitpod.auth.${gitpodHostUrl.hostname}`, this.logService);
69+
this.logService.info(`Started authentication provider for ${gitpodHost}`);
7370
}
7471

7572
get onDidChangeSessions() {
@@ -81,16 +78,16 @@ export default class GitpodAuthenticationProvider extends Disposable implements
8178
const sortedScopes = scopes?.sort() || [];
8279
const validScopes = await this.fetchValidScopes();
8380
const sortedFilteredScopes = sortedScopes.filter(s => !validScopes || validScopes.includes(s));
84-
this._logger.info(`Getting sessions for ${sortedScopes.length ? sortedScopes.join(',') : 'all scopes'}${sortedScopes.length !== sortedFilteredScopes.length ? `, but valid scopes are ${sortedFilteredScopes.join(',')}` : ''}...`);
81+
this.logService.info(`Getting sessions for ${sortedScopes.length ? sortedScopes.join(',') : 'all scopes'}${sortedScopes.length !== sortedFilteredScopes.length ? `, but valid scopes are ${sortedFilteredScopes.join(',')}` : ''}...`);
8582
if (sortedScopes.length !== sortedFilteredScopes.length) {
86-
this._logger.warn(`But valid scopes are ${sortedFilteredScopes.join(',')}, returning session with only valid scopes...`);
83+
this.logService.warn(`But valid scopes are ${sortedFilteredScopes.join(',')}, returning session with only valid scopes...`);
8784
}
8885
const sessions = await this._sessionsPromise;
8986
const finalSessions = sortedFilteredScopes.length
9087
? sessions.filter(session => arrayEquals([...session.scopes].sort(), sortedFilteredScopes))
9188
: sessions;
9289

93-
this._logger.info(`Got ${finalSessions.length} sessions for ${sortedFilteredScopes?.join(',') ?? 'all scopes'}...`);
90+
this.logService.info(`Got ${finalSessions.length} sessions for ${sortedFilteredScopes?.join(',') ?? 'all scopes'}...`);
9491
return finalSessions;
9592
}
9693

@@ -110,7 +107,7 @@ export default class GitpodAuthenticationProvider extends Disposable implements
110107
return this._validScopes;
111108
}
112109
} catch (e) {
113-
this._logger.error(`Error fetching endpoint ${endpoint}`, e);
110+
this.logService.error(`Error fetching endpoint ${endpoint}`, e);
114111
}
115112
return undefined;
116113
}
@@ -127,7 +124,7 @@ export default class GitpodAuthenticationProvider extends Disposable implements
127124
const matchesExisting = previousSessions.some(s => s.id === session.id);
128125
// Another window added a session to the keychain, add it to our state as well
129126
if (!matchesExisting) {
130-
this._logger.info('Adding session found in keychain');
127+
this.logService.info('Adding session found in keychain');
131128
added.push(session);
132129
}
133130
});
@@ -136,7 +133,7 @@ export default class GitpodAuthenticationProvider extends Disposable implements
136133
const matchesExisting = storedSessions.some(s => s.id === session.id);
137134
// Another window has logged out, remove from our state
138135
if (!matchesExisting) {
139-
this._logger.info('Removing session no longer found in keychain');
136+
this.logService.info('Removing session no longer found in keychain');
140137
removed.push(session);
141138
}
142139
});
@@ -149,12 +146,12 @@ export default class GitpodAuthenticationProvider extends Disposable implements
149146
private async readSessions(): Promise<vscode.AuthenticationSession[]> {
150147
let sessionData: SessionData[];
151148
try {
152-
this._logger.info('Reading sessions from keychain...');
149+
this.logService.info('Reading sessions from keychain...');
153150
const storedSessions = await this._keychain.getToken();
154151
if (!storedSessions) {
155152
return [];
156153
}
157-
this._logger.info('Got stored sessions!');
154+
this.logService.info('Got stored sessions!');
158155

159156
try {
160157
sessionData = JSON.parse(storedSessions);
@@ -163,7 +160,7 @@ export default class GitpodAuthenticationProvider extends Disposable implements
163160
throw e;
164161
}
165162
} catch (e) {
166-
this._logger.error(`Error reading token: ${e}`);
163+
this.logService.error(`Error reading token: ${e}`);
167164
return [];
168165
}
169166

@@ -175,16 +172,16 @@ export default class GitpodAuthenticationProvider extends Disposable implements
175172
let userInfo: { id: string; accountName: string } | undefined;
176173
try {
177174
userInfo = await this._gitpodServer.getUserInfo(session.accessToken);
178-
this._logger.info(`Verified session with the following scopes: ${scopesStr}`);
175+
this.logService.info(`Verified session with the following scopes: ${scopesStr}`);
179176
} catch (e) {
180177
// Remove sessions that return unauthorized response
181178
if (e.message === 'Unexpected server response: 401') {
182179
return undefined;
183180
}
184-
this._logger.error(`Error while verifying session with the following scopes: ${scopesStr}`, e);
181+
this.logService.error(`Error while verifying session with the following scopes: ${scopesStr}`, e);
185182
}
186183

187-
this._logger.trace(`Read the following session from the keychain with the following scopes: ${scopesStr}`);
184+
this.logService.trace(`Read the following session from the keychain with the following scopes: ${scopesStr}`);
188185
return {
189186
id: session.id,
190187
account: {
@@ -203,7 +200,7 @@ export default class GitpodAuthenticationProvider extends Disposable implements
203200
.map(p => (p as PromiseFulfilledResult<vscode.AuthenticationSession | undefined>).value)
204201
.filter(<T>(p?: T): p is T => Boolean(p));
205202

206-
this._logger.info(`Got ${verifiedSessions.length} verified sessions.`);
203+
this.logService.info(`Got ${verifiedSessions.length} verified sessions.`);
207204
if (verifiedSessions.length !== sessionData.length) {
208205
await this.storeSessions(verifiedSessions);
209206
}
@@ -212,10 +209,10 @@ export default class GitpodAuthenticationProvider extends Disposable implements
212209
}
213210

214211
private async storeSessions(sessions: vscode.AuthenticationSession[]): Promise<void> {
215-
this._logger.info(`Storing ${sessions.length} sessions...`);
212+
this.logService.info(`Storing ${sessions.length} sessions...`);
216213
this._sessionsPromise = Promise.resolve(sessions);
217214
await this._keychain.setToken(JSON.stringify(sessions));
218-
this._logger.info(`Stored ${sessions.length} sessions!`);
215+
this.logService.info(`Stored ${sessions.length} sessions!`);
219216
}
220217

221218
public async createSession(scopes: string[]): Promise<vscode.AuthenticationSession> {
@@ -226,10 +223,10 @@ export default class GitpodAuthenticationProvider extends Disposable implements
226223
const validScopes = await this.fetchValidScopes();
227224
const sortedFilteredScopes = sortedScopes.filter(s => !validScopes || validScopes.includes(s));
228225
if (sortedScopes.length !== sortedFilteredScopes.length) {
229-
this._logger.warn(`Creating session with only valid scopes ${sortedFilteredScopes.join(',')}, original scopes were ${sortedScopes.join(',')}`);
226+
this.logService.warn(`Creating session with only valid scopes ${sortedFilteredScopes.join(',')}, original scopes were ${sortedScopes.join(',')}`);
230227
}
231228
flow.scopes = JSON.stringify(sortedFilteredScopes);
232-
this._telemetry.sendUserFlowStatus('login', flow);
229+
this.telemetryService.sendUserFlowStatus('login', flow);
233230

234231
const scopeString = sortedFilteredScopes.join(' ');
235232
const token = await this._gitpodServer.login(scopeString, flow);
@@ -247,19 +244,19 @@ export default class GitpodAuthenticationProvider extends Disposable implements
247244

248245
this._sessionChangeEmitter.fire({ added: [session], removed: [], changed: [] });
249246

250-
this._logger.info('Login success!');
247+
this.logService.info('Login success!');
251248

252-
this._telemetry.sendUserFlowStatus('login_successful', flow);
249+
this.telemetryService.sendUserFlowStatus('login_successful', flow);
253250

254251
return session;
255252
} catch (e) {
256253
// If login was cancelled, do not notify user.
257254
if (e === 'Cancelled' || e.message === 'Cancelled') {
258-
this._telemetry.sendUserFlowStatus('login_cancelled', flow);
255+
this.telemetryService.sendUserFlowStatus('login_cancelled', flow);
259256
throw e;
260257
}
261-
this.notifications.showErrorMessage(`Sign in failed: ${e}`, { flow, id: 'login_failed' });
262-
this._logger.error(e);
258+
this.notificationService.showErrorMessage(`Sign in failed: ${e}`, { flow, id: 'login_failed' });
259+
this.logService.error(e);
263260
throw e;
264261
}
265262
}
@@ -277,8 +274,8 @@ export default class GitpodAuthenticationProvider extends Disposable implements
277274
public async removeSession(id: string) {
278275
const flow = { ...this.flow };
279276
try {
280-
this._telemetry.sendUserFlowStatus('logout', flow);
281-
this._logger.info(`Logging out of ${id}`);
277+
this.telemetryService.sendUserFlowStatus('logout', flow);
278+
this.logService.info(`Logging out of ${id}`);
282279

283280
const sessions = await this._sessionsPromise;
284281
const sessionIndex = sessions.findIndex(session => session.id === id);
@@ -291,12 +288,12 @@ export default class GitpodAuthenticationProvider extends Disposable implements
291288

292289
this._sessionChangeEmitter.fire({ added: [], removed: [session], changed: [] });
293290
} else {
294-
this._logger.error('Session not found');
291+
this.logService.error('Session not found');
295292
}
296-
this._telemetry.sendUserFlowStatus('logout_successful', flow);
293+
this.telemetryService.sendUserFlowStatus('logout_successful', flow);
297294
} catch (e) {
298-
this.notifications.showErrorMessage(`Sign out failed: ${e}`, { flow, id: 'logout_failed' });
299-
this._logger.error(e);
295+
this.notificationService.showErrorMessage(`Sign out failed: ${e}`, { flow, id: 'logout_failed' });
296+
this.logService.error(e);
300297
throw e;
301298
}
302299
}

src/gitpodServer.ts renamed to src/authentication/gitpodServer.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as vscode from 'vscode';
7-
import { PromiseAdapter, promiseFromEvent } from './common/utils';
8-
import { withServerApi } from './internalApi';
7+
import { PromiseAdapter, promiseFromEvent } from '../common/utils';
8+
import { withServerApi } from '../internalApi';
99
import pkceChallenge from 'pkce-challenge';
1010
import { v4 as uuid } from 'uuid';
11-
import { Disposable } from './common/dispose';
12-
import { NotificationService } from './notification';
13-
import { UserFlowTelemetry } from './common/telemetry';
11+
import { Disposable } from '../common/dispose';
12+
import { INotificationService } from '../services/notificationService';
13+
import { UserFlowTelemetry } from '../services/telemetryService';
14+
import { ILogService } from '../services/logService';
1415

1516
interface ExchangeTokenResponse {
1617
token_type: 'Bearer';
@@ -20,7 +21,7 @@ interface ExchangeTokenResponse {
2021
scope: string;
2122
}
2223

23-
async function getUserInfo(token: string, serviceUrl: string, logger: vscode.LogOutputChannel) {
24+
async function getUserInfo(token: string, serviceUrl: string, logger: ILogService) {
2425
const user = await withServerApi(token, serviceUrl, service => service.server.getLoggedInUser(), logger);
2526
return {
2627
id: user.id,
@@ -40,16 +41,16 @@ export default class GitpodServer extends Disposable {
4041

4142
constructor(
4243
serviceUrl: string,
43-
private readonly _logger: vscode.LogOutputChannel,
44-
private readonly notifications: NotificationService
44+
private readonly logService: ILogService,
45+
private readonly notificationService: INotificationService
4546
) {
4647
super();
4748

4849
this._serviceUrl = serviceUrl.replace(/\/$/, '');
4950
}
5051

5152
public async login(scopes: string, flow: UserFlowTelemetry): Promise<string> {
52-
this._logger.info(`Logging in for the following scopes: ${scopes}`);
53+
this.logService.info(`Logging in for the following scopes: ${scopes}`);
5354

5455
const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://gitpod.gitpod-desktop/complete-gitpod-auth`));
5556

@@ -108,12 +109,12 @@ export default class GitpodServer extends Disposable {
108109
const state = query.get('state');
109110

110111
if (!code) {
111-
this._logger.error('No code in response.');
112+
this.logService.error('No code in response.');
112113
return;
113114
}
114115

115116
if (!state) {
116-
this._logger.error('No state in response.');
117+
this.logService.error('No state in response.');
117118
return;
118119
}
119120

@@ -124,17 +125,17 @@ export default class GitpodServer extends Disposable {
124125
// 2. Before finishing 1, you trigger a sign in with a different set of scopes
125126
// In this scenario we should just return and wait for the next UriHandler event
126127
// to run as we are probably still waiting on the user to hit 'Continue'
127-
this._logger.info('Nonce not found in accepted nonces. Skipping this execution...');
128+
this.logService.info('Nonce not found in accepted nonces. Skipping this execution...');
128129
return;
129130
}
130131

131132
const verifier = this._pendingVerifiers.get(state);
132133
if (!verifier) {
133-
this._logger.error('Code verifier not found in memory.');
134+
this.logService.error('Code verifier not found in memory.');
134135
return;
135136
}
136137

137-
this._logger.info('Exchanging code for token...');
138+
this.logService.info('Exchanging code for token...');
138139

139140
const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://gitpod.gitpod-desktop${GitpodServer.AUTH_COMPLETE_PATH}`));
140141
try {
@@ -150,7 +151,7 @@ export default class GitpodServer extends Disposable {
150151
});
151152

152153
if (!exchangeTokenResponse.ok) {
153-
this.notifications.showErrorMessage(`Couldn't connect (token exchange): ${exchangeTokenResponse.statusText}, ${await exchangeTokenResponse.text()}`, { flow, id: 'failed_to_exchange' });
154+
this.notificationService.showErrorMessage(`Couldn't connect (token exchange): ${exchangeTokenResponse.statusText}, ${await exchangeTokenResponse.text()}`, { flow, id: 'failed_to_exchange' });
154155
reject(exchangeTokenResponse.statusText);
155156
return;
156157
}
@@ -160,13 +161,13 @@ export default class GitpodServer extends Disposable {
160161
const accessToken = JSON.parse(Buffer.from(jwtToken.split('.')[1], 'base64').toString())['jti'];
161162
resolve(accessToken);
162163
} catch (err) {
163-
this.notifications.showErrorMessage(`Couldn't connect (token exchange): ${err}`, { flow, id: 'failed_to_exchange' });
164+
this.notificationService.showErrorMessage(`Couldn't connect (token exchange): ${err}`, { flow, id: 'failed_to_exchange' });
164165
reject(err);
165166
}
166167
};
167168

168169
public getUserInfo(token: string): Promise<{ id: string; accountName: string }> {
169-
return getUserInfo(token, this._serviceUrl, this._logger);
170+
return getUserInfo(token, this._serviceUrl, this.logService);
170171
}
171172

172173
public handleUri(uri: vscode.Uri) {

src/commandManager.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Gitpod. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
8+
export interface Command {
9+
readonly id: string;
10+
11+
execute(...args: any[]): void | any;
12+
}
13+
14+
export class CommandManager {
15+
private readonly commands = new Map<string, vscode.Disposable>();
16+
17+
public register<T extends Command>(command: T): T {
18+
if (!this.commands.has(command.id)) {
19+
this.commands.set(command.id, vscode.commands.registerCommand(command.id, command.execute, command));
20+
}
21+
return command;
22+
}
23+
24+
public dispose() {
25+
for (const registration of this.commands.values()) {
26+
registration.dispose();
27+
}
28+
this.commands.clear();
29+
}
30+
}

0 commit comments

Comments
 (0)