Skip to content

Commit 6a1558f

Browse files
jeanp413mustard-mh
andauthored
Test public API (#25)
Co-authored-by: mustard <[email protected]>
1 parent 9e34892 commit 6a1558f

13 files changed

+206
-71
lines changed

.vscode/launch.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
"outFiles": [
1313
"${workspaceFolder}/out/**/*.js"
1414
],
15-
"preLaunchTask": "npm: watch"
15+
"preLaunchTask": "npm: watch",
16+
"env": {
17+
"TEST": "1",
18+
}
1619
}
1720
]
1821
}

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@
127127
"@types/js-yaml": "^4.0.5",
128128
"@types/mocha": "^9.1.1",
129129
"@types/node": "16.x",
130-
"@types/node-fetch": "^2.5.12",
131130
"@types/semver": "^7.3.10",
132131
"@types/ssh2": "^0.5.52",
133132
"@types/tmp": "^0.2.1",
@@ -149,13 +148,15 @@
149148
"webpack-cli": "^4.7.2"
150149
},
151150
"dependencies": {
151+
"@bufbuild/connect-web": "^0.2.1",
152152
"@gitpod/gitpod-protocol": "main",
153153
"@gitpod/local-app-api-grpcweb": "main",
154+
"@gitpod/public-api": "main",
154155
"@improbable-eng/grpc-web-node-http-transport": "^0.14.0",
155156
"analytics-node": "^6.2.0",
156157
"configcat-node": "^8.0.0",
157158
"js-yaml": "^4.1.0",
158-
"node-fetch": "2.6.7",
159+
"node-fetch-commonjs": "^3.2.4",
159160
"pkce-challenge": "^3.0.0",
160161
"semver": "^7.3.7",
161162
"ssh-config": "^4.1.6",

src/authentication.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import * as vscode from 'vscode';
77
import { v4 as uuid } from 'uuid';
8-
import fetch from 'node-fetch';
98
import Keychain from './common/keychain';
109
import GitpodServer from './gitpodServer';
1110
import Log from './common/logger';
@@ -106,9 +105,11 @@ export default class GitpodAuthenticationProvider extends Disposable implements
106105

107106
const endpoint = `${this._serviceUrl}/api/oauth/inspect?client=${vscode.env.uriScheme}-gitpod`;
108107
try {
109-
const resp = await fetch(endpoint, { timeout: 1500 });
108+
const controller = new AbortController();
109+
setTimeout(() => controller.abort(), 1500);
110+
const resp = await fetch(endpoint, { signal: controller.signal });
110111
if (resp.ok) {
111-
this._validScopes = await resp.json();
112+
this._validScopes = (await resp.json()) as string[];
112113
return this._validScopes;
113114
}
114115
} catch (e) {

src/experiments.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ export class ExperimentalSettings {
1818
private configcatClient: configcatcommon.IConfigCatClient;
1919
private extensionVersion: semver.SemVer;
2020

21-
constructor(key: string, extensionVersion: string, private logger: Log) {
21+
constructor(key: string, extensionVersion: string, private logger: Log, gitpodHost: string) {
2222
this.configcatClient = configcat.createClientWithLazyLoad(key, {
23+
baseUrl: new URL('/configcat', process.env['TEST'] ? 'https://gitpod-staging.com' : gitpodHost).href,
2324
logger: {
2425
debug(): void { },
2526
log(): void { },
@@ -33,6 +34,18 @@ export class ExperimentalSettings {
3334
this.extensionVersion = new semver.SemVer(extensionVersion);
3435
}
3536

37+
async getRaw<T>(
38+
configcatKey: string,
39+
userId: string,
40+
custom: {
41+
gitpodHost: string;
42+
[key: string]: string;
43+
}
44+
) {
45+
const user = userId ? new configcatcommon.User(userId, undefined, undefined, custom) : undefined;
46+
return (await this.configcatClient.getValueAsync(configcatKey, undefined, user)) as T | undefined;
47+
}
48+
3649
async get<T>(
3750
key: string,
3851
userId: string,

src/extension.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import * as os from 'os';
77
import * as vscode from 'vscode';
8+
import fetch, { Headers, Request, Response, AbortError, FetchError } from 'node-fetch-commonjs';
89
import GitpodAuthenticationProvider from './authentication';
910
import Log from './common/logger';
1011
import { UserFlowTelemetry } from './common/telemetry';
@@ -17,6 +18,16 @@ import RemoteConnector from './remoteConnector';
1718
import { SettingsSync } from './settingsSync';
1819
import TelemetryReporter from './telemetryReporter';
1920

21+
// connect-web uses fetch api, so we need to polyfill it
22+
if (!global.fetch) {
23+
global.fetch = fetch as any;
24+
global.Headers = Headers as any;
25+
global.Request = Request as any;
26+
global.Response = Response as any;
27+
(global as any).AbortError = AbortError as any;
28+
(global as any).FetchError = FetchError as any;
29+
}
30+
2031
const FIRST_INSTALL_KEY = 'gitpod-desktop.firstInstall';
2132

2233
let telemetry: TelemetryReporter;
@@ -31,8 +42,9 @@ export async function activate(context: vscode.ExtensionContext) {
3142

3243
const logger = new Log('Gitpod');
3344
logger.info(`${extensionId}/${packageJSON.version} (${os.release()} ${os.platform()} ${os.arch()}) vscode/${vscode.version} (${vscode.env.appName})`);
34-
35-
const experiments = new ExperimentalSettings(packageJSON.configcatKey, packageJSON.version, logger);
45+
46+
const gitpodHost = vscode.workspace.getConfiguration('gitpod').get<string>('host')!;
47+
const experiments = new ExperimentalSettings('gitpod', packageJSON.version, logger, gitpodHost);
3648
context.subscriptions.push(experiments);
3749

3850
telemetry = new TelemetryReporter(extensionId, packageJSON.version, packageJSON.segmentKey);

src/featureSupport.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*--------------------------------------------------------------------------------------------*/
55
import * as vscode from 'vscode';
66
import * as semver from 'semver';
7-
import fetch from 'node-fetch';
87
import Log from './common/logger';
98
import { retry } from './common/async';
109

@@ -55,7 +54,9 @@ async function getOrFetchVersionInfo(serviceUrl: string, logger: Log) {
5554
let gitpodRawVersion: string | undefined;
5655
try {
5756
gitpodRawVersion = await retry(async () => {
58-
const resp = await fetch(versionEndPoint, { timeout: 1500 });
57+
const controller = new AbortController();
58+
setTimeout(() => controller.abort(), 1500);
59+
const resp = await fetch(versionEndPoint, { signal: controller.signal });
5960
if (!resp.ok) {
6061
throw new Error(`Responded with ${resp.status} ${resp.statusText}`);
6162
}
@@ -104,7 +105,9 @@ export async function isOauthInspectSupported(gitpodHost: string,) {
104105
const serviceUrl = new URL(gitpodHost).toString().replace(/\/$/, '');
105106
const endpoint = `${serviceUrl}/api/oauth/inspect?client=${vscode.env.uriScheme}-gitpod`;
106107
try {
107-
const resp = await fetch(endpoint, { timeout: 1500 });
108+
const controller = new AbortController();
109+
setTimeout(() => controller.abort(), 1500);
110+
const resp = await fetch(endpoint, { signal: controller.signal });
108111
if (resp.ok) {
109112
return true;
110113
}

src/gitpodServer.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import * as vscode from 'vscode';
77
import Log from './common/logger';
8-
import fetch from 'node-fetch';
98
import { PromiseAdapter, promiseFromEvent } from './common/utils';
109
import { withServerApi } from './internalApi';
1110
import pkceChallenge from 'pkce-challenge';
@@ -157,7 +156,7 @@ export default class GitpodServer extends Disposable {
157156
return;
158157
}
159158

160-
const exchangeTokenData: ExchangeTokenResponse = await exchangeTokenResponse.json();
159+
const exchangeTokenData = (await exchangeTokenResponse.json()) as ExchangeTokenResponse;
161160
const jwtToken = exchangeTokenData.access_token;
162161
const accessToken = JSON.parse(Buffer.from(jwtToken.split('.')[1], 'base64').toString())['jti'];
163162
resolve(accessToken);

src/heartbeat.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { WorkspaceInfo } from '@gitpod/gitpod-protocol';
7+
import { Workspace, WorkspaceInstanceStatus_Phase } from '@gitpod/public-api/lib/gitpod/experimental/v1/workspaces_pb';
68
import * as vscode from 'vscode';
79
import { Disposable } from './common/dispose';
810
import Log from './common/logger';
911
import { withServerApi } from './internalApi';
12+
import { GitpodPublicApi } from './publicApi';
1013
import TelemetryReporter from './telemetryReporter';
1114

1215
export class HeartbeatManager extends Disposable {
@@ -22,6 +25,7 @@ export class HeartbeatManager extends Disposable {
2225
readonly workspaceId: string,
2326
readonly instanceId: string,
2427
private readonly accessToken: string,
28+
private readonly publicApi: GitpodPublicApi | undefined,
2529
private readonly logger: Log,
2630
private readonly telemetry: TelemetryReporter
2731
) {
@@ -98,9 +102,14 @@ export class HeartbeatManager extends Disposable {
98102
const suffix = wasClosed ? 'closed heartbeat' : 'heartbeat';
99103
try {
100104
await withServerApi(this.accessToken, this.gitpodHost, async service => {
101-
const workspaceInfo = await service.server.getWorkspace(this.workspaceId);
102-
this.isWorkspaceRunning = workspaceInfo.latestInstance?.status?.phase === 'running' && workspaceInfo.latestInstance?.id === this.instanceId;
105+
const workspaceInfo = this.publicApi
106+
? await this.publicApi.getWorkspace(this.workspaceId)
107+
: await service.server.getWorkspace(this.workspaceId);
108+
this.isWorkspaceRunning = this.publicApi
109+
? (workspaceInfo as Workspace)?.status?.instance?.status?.phase === WorkspaceInstanceStatus_Phase.RUNNING && (workspaceInfo as Workspace)?.status?.instance?.instanceId === this.instanceId
110+
: (workspaceInfo as WorkspaceInfo).latestInstance?.status?.phase === 'running' && (workspaceInfo as WorkspaceInfo).latestInstance?.id === this.instanceId;
103111
if (this.isWorkspaceRunning) {
112+
// TODO: use the public API
104113
await service.server.sendHeartBeat({ instanceId: this.instanceId, wasClosed });
105114
if (wasClosed) {
106115
this.telemetry.sendTelemetryEvent('ide_close_signal', { workspaceId: this.workspaceId, instanceId: this.instanceId, gitpodHost: this.gitpodHost, clientKind: 'vscode' });

src/publicApi.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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 { createConnectTransport, createPromiseClient, Interceptor, PromiseClient } from '@bufbuild/connect-web';
7+
import { WorkspacesService } from '@gitpod/public-api/lib/gitpod/experimental/v1/workspaces_connectweb';
8+
import { Workspace } from '@gitpod/public-api/lib/gitpod/experimental/v1/workspaces_pb';
9+
10+
export class GitpodPublicApi {
11+
12+
private workspaceService!: PromiseClient<typeof WorkspacesService>;
13+
14+
constructor() {
15+
}
16+
17+
async init(accessToken: string, gitpodHost: string) {
18+
const serviceUrl = new URL(gitpodHost);
19+
serviceUrl.hostname = `api.${serviceUrl.hostname}`;
20+
21+
const authInterceptor: Interceptor = (next) => async (req) => {
22+
req.header.set('Authorization', `Bearer ${accessToken}`);
23+
return await next(req);
24+
};
25+
26+
const transport = createConnectTransport({
27+
baseUrl: serviceUrl.toString(),
28+
interceptors: [authInterceptor],
29+
});
30+
31+
this.workspaceService = createPromiseClient(WorkspacesService, transport);
32+
}
33+
34+
async getWorkspace(workspaceId: string): Promise<Workspace | undefined> {
35+
const response = await this.workspaceService.getWorkspace({ workspaceId });
36+
return response.result;
37+
}
38+
39+
async getOwnerToken(workspaceId: string): Promise<string> {
40+
const response = await this.workspaceService.getOwnerToken({ workspaceId });
41+
return response.token;
42+
}
43+
}

src/releaseNotes.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import fetch, { Response } from 'node-fetch';
76
import * as vscode from 'vscode';
87
import { load } from 'js-yaml';
98
import { CacheHelper } from './common/cache';

0 commit comments

Comments
 (0)