/** * Copyright (c) 2020 TypeFox GmbH. All rights reserved. * Licensed under the GNU Affero General Public License (AGPL). * See License-AGPL.txt in the project root for license information. */ import { User, WorkspaceInfo, WorkspaceCreationResult, UserMessage, WorkspaceInstanceUser, WhitelistedRepository, WorkspaceImageBuild, AuthProviderInfo, Branding, CreateWorkspaceMode, Token, UserEnvVarValue, ResolvePluginsParams, PreparePluginUploadParams, ResolvedPlugins, Configuration, InstallPluginsParams, UninstallPluginParams, UserInfo, GitpodTokenType, GitpodToken, AuthProviderEntry } from './protocol'; import { JsonRpcProxy, JsonRpcServer } from './messaging/proxy-factory'; import { injectable, inject } from 'inversify'; import { Disposable } from 'vscode-jsonrpc'; import { HeadlessLogEvent } from './headless-workspace-log'; import { WorkspaceInstance, WorkspaceInstancePort } from './workspace-instance'; import { AdminServer } from './admin-protocol'; import { GitpodHostUrl } from './util/gitpod-host-url'; import { WebSocketConnectionProvider } from './messaging/browser/connection'; import { PermissionName } from './permission'; import { LicenseService } from './license-protocol'; export interface GitpodClient { onInstanceUpdate(instance: WorkspaceInstance): void; onWorkspaceImageBuildLogs: WorkspaceImageBuild.LogCallback; onHeadlessWorkspaceLogs(evt: HeadlessLogEvent): void; } export const GitpodServer = Symbol('GitpodServer'); export interface GitpodServer extends JsonRpcServer, AdminServer, LicenseService { // User related API getLoggedInUser(): Promise; updateLoggedInUser(user: Partial): Promise; getAuthProviders(): Promise; getOwnAuthProviders(): Promise; updateOwnAuthProvider(params: GitpodServer.UpdateOwnAuthProviderParams): Promise; deleteOwnAuthProvider(params: GitpodServer.DeleteOwnAuthProviderParams): Promise; getBranding(): Promise; getConfiguration(): Promise; getToken(query: GitpodServer.GetTokenSearchOptions): Promise; getPortAuthenticationToken(workspaceId: string): Promise; deleteAccount(): Promise; getClientRegion(): Promise; hasPermission(permission: PermissionName): Promise; // Query/retrieve workspaces getWorkspaces(options: GitpodServer.GetWorkspacesOptions): Promise; getWorkspaceOwner(workspaceId: string): Promise; getWorkspaceUsers(workspaceId: string): Promise; getFeaturedRepositories(): Promise; getWorkspace(id: string): Promise; isWorkspaceOwner(workspaceId: string): Promise; /** * Creates and starts a workspace for the given context URL. * @param options GitpodServer.CreateWorkspaceOptions * @return WorkspaceCreationResult */ createWorkspace(options: GitpodServer.CreateWorkspaceOptions): Promise; startWorkspace(id: string, options: {forceDefaultImage: boolean}): Promise; stopWorkspace(id: string): Promise; deleteWorkspace(id: string): Promise; setWorkspaceDescription(id: string, desc: string): Promise; controlAdmission(id: string, level: "owner" | "everyone"): Promise; updateWorkspaceUserPin(id: string, action: "pin" | "unpin" | "toggle"): Promise; sendHeartBeat(options: GitpodServer.SendHeartBeatOptions): Promise; watchWorkspaceImageBuildLogs(workspaceId: string): Promise; watchHeadlessWorkspaceLogs(workspaceId: string): Promise; isPrebuildAvailable(pwsid: string): Promise; // Workspace timeout setWorkspaceTimeout(workspaceId: string, duration: WorkspaceTimeoutDuration): Promise; getWorkspaceTimeout(workspaceId: string): Promise; sendHeartBeat(options: GitpodServer.SendHeartBeatOptions): Promise; updateWorkspaceUserPin(id: string, action: "pin" | "unpin" | "toggle"): Promise; // Port management getOpenPorts(workspaceId: string): Promise; openPort(workspaceId: string, port: WorkspaceInstancePort): Promise; closePort(workspaceId: string, port: number): Promise; // User messages getUserMessages(options: GitpodServer.GetUserMessagesOptions): Promise; updateUserMessages(options: GitpodServer.UpdateUserMessagesOptions): Promise; // User storage getUserStorageResource(options: GitpodServer.GetUserStorageResourceOptions): Promise; updateUserStorageResource(options: GitpodServer.UpdateUserStorageResourceOptions): Promise; // user env vars getEnvVars(): Promise; setEnvVar(variable: UserEnvVarValue): Promise; deleteEnvVar(variable: UserEnvVarValue): Promise; // Gitpod token getGitpodTokens(): Promise; generateNewGitpodToken(options: { name?: string, type: GitpodTokenType, scopes?: [] }): Promise; deleteGitpodToken(tokenHash: string): Promise; // misc sendFeedback(feedback: string): Promise; registerGithubApp(installationId: string): Promise; /** * Stores a new snapshot for the given workspace and bucketId * @return the snapshot id */ takeSnapshot(options: GitpodServer.TakeSnapshotOptions): Promise; /** * Returns the list of snapshots that exist for a workspace. */ getSnapshots(workspaceID: string): Promise; /** * stores/updates layout information for the given workspace */ storeLayout(workspaceId: string, layoutData: string): Promise; /** * retrieves layout information for the given workspace */ getLayout(workspaceId: string): Promise; /** * @param params * @returns promise resolves to an URL to be used for the upload */ preparePluginUpload(params: PreparePluginUploadParams): Promise resolvePlugins(workspaceId: string, params: ResolvePluginsParams): Promise; installUserPlugins(params: InstallPluginsParams): Promise; uninstallUserPlugin(params: UninstallPluginParams): Promise; } export const WorkspaceTimeoutValues = ["30m", "60m", "180m"] as const; export const createServiceMock = function(methods: Partial>): GitpodServiceImpl { return new GitpodServiceImpl(createServerMock(methods)); } export const createServerMock = function(methods: Partial>): JsonRpcProxy { methods.setClient = methods.setClient || (() => {}); methods.dispose = methods.dispose || (() => {}); return new Proxy>(methods as any as JsonRpcProxy, { get: (target: S, property: keyof S) => { const result = target[property]; if (!result) { throw new Error(`Method ${property} not implemented`); } return result; } }); } type WorkspaceTimeoutDurationTuple = typeof WorkspaceTimeoutValues; export type WorkspaceTimeoutDuration = WorkspaceTimeoutDurationTuple[number]; export interface SetWorkspaceTimeoutResult { resetTimeoutOnWorkspaces: string[] } export interface GetWorkspaceTimeoutResult { duration: WorkspaceTimeoutDuration canChange: boolean } export interface StartWorkspaceResult { instanceID: string workspaceURL?: string } export namespace GitpodServer { export interface GetWorkspacesOptions { limit?: number; searchString?: string; pinnedOnly?: boolean; } export interface GetAccountStatementOptions { date?: string; } export interface CreateWorkspaceOptions { contextUrl: string; mode?: CreateWorkspaceMode; } export interface TakeSnapshotOptions { workspaceId: string; layoutData?: string; } export interface GetUserMessagesOptions { readonly releaseNotes?: boolean; readonly workspaceInstanceId: string; } export interface UpdateUserMessagesOptions { readonly messageIds: string[]; } export interface GetUserStorageResourceOptions { readonly uri: string; } export interface UpdateUserStorageResourceOptions { readonly uri: string; readonly content: string; } export interface GetTokenSearchOptions { readonly host: string; } export interface SendHeartBeatOptions { readonly instanceId: string; readonly wasClosed?: boolean; readonly roundTripTime?: number; } export interface UpdateOwnAuthProviderParams { readonly entry: AuthProviderEntry.UpdateEntry | AuthProviderEntry.NewEntry } export interface DeleteOwnAuthProviderParams { readonly id: string } } export const GitpodServerPath = '/gitpod'; export const GitpodServerProxy = Symbol('GitpodServerProxy'); export type GitpodServerProxy = JsonRpcProxy; export class GitpodCompositeClient implements GitpodClient { protected clients: Partial[] = []; public registerClient(client: Partial): Disposable { this.clients.push(client); const index = this.clients.length; return { dispose: () => { this.clients.slice(index, 1); } } } onInstanceUpdate(instance: WorkspaceInstance): void { for (const client of this.clients) { if (client.onInstanceUpdate) { try { client.onInstanceUpdate(instance); } catch (error) { console.error(error) } } } } onWorkspaceImageBuildLogs(info: WorkspaceImageBuild.StateInfo, content: WorkspaceImageBuild.LogContent | undefined): void { for (const client of this.clients) { if (client.onWorkspaceImageBuildLogs) { try { client.onWorkspaceImageBuildLogs(info, content); } catch (error) { console.error(error) } } } } onHeadlessWorkspaceLogs(evt: HeadlessLogEvent): void { for (const client of this.clients) { if (client.onHeadlessWorkspaceLogs) { try { client.onHeadlessWorkspaceLogs(evt); } catch (error) { console.error(error) } } } } } export const GitpodService = Symbol('GitpodService'); export type GitpodService = GitpodServiceImpl @injectable() export class GitpodServiceImpl { protected compositeClient = new GitpodCompositeClient(); constructor(@inject(GitpodServer) public readonly server: JsonRpcProxy) { server.setClient(this.compositeClient); } public registerClient(client: Partial): Disposable { return this.compositeClient.registerClient(client); } } export function createGitpodService(serverUrl: string) { const url = new GitpodHostUrl(serverUrl) .asWebsocket() .withApi({ pathname: GitpodServerPath }); const connectionProvider = new WebSocketConnectionProvider(); const gitpodServer = connectionProvider.createProxy(url.toString()); return new GitpodServiceImpl(gitpodServer); }