mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
0bd0990e14
Related to PR: https://github.com/coder/coder/pull/20900
(cherry picked from commit f8d9a8046f)
2844 lines
74 KiB
TypeScript
2844 lines
74 KiB
TypeScript
/**
|
|
* @file Coder is starting to import the Coder API file into more and more
|
|
* external projects, as a "pseudo-SDK". We are not at a stage where we are
|
|
* ready to commit to maintaining a public SDK, but we need equivalent
|
|
* functionality in other places.
|
|
*
|
|
* Message somebody from Team Blueberry if you need more context, but so far,
|
|
* these projects are importing the file:
|
|
*
|
|
* - The Coder VS Code extension
|
|
* @see {@link https://github.com/coder/vscode-coder}
|
|
* - The Coder Backstage plugin
|
|
* @see {@link https://github.com/coder/backstage-plugins}
|
|
*
|
|
* It is important that this file not do any aliased imports, or else the other
|
|
* consumers could break (particularly for platforms that limit how much you can
|
|
* touch their configuration files, like Backstage). Relative imports are still
|
|
* safe, though.
|
|
*
|
|
* For example, `utils/delay` must be imported using `../utils/delay` instead.
|
|
*/
|
|
import globalAxios, { type AxiosInstance, isAxiosError } from "axios";
|
|
import type dayjs from "dayjs";
|
|
import userAgentParser from "ua-parser-js";
|
|
import { delay } from "../utils/delay";
|
|
import { OneWayWebSocket } from "../utils/OneWayWebSocket";
|
|
import { type FieldError, isApiError } from "./errors";
|
|
import type {
|
|
DeleteExternalAuthByIDResponse,
|
|
DynamicParametersRequest,
|
|
PostWorkspaceUsageRequest,
|
|
} from "./typesGenerated";
|
|
import * as TypesGen from "./typesGenerated";
|
|
|
|
const getMissingParameters = (
|
|
oldBuildParameters: TypesGen.WorkspaceBuildParameter[],
|
|
newBuildParameters: TypesGen.WorkspaceBuildParameter[],
|
|
templateParameters: TypesGen.TemplateVersionParameter[],
|
|
) => {
|
|
const missingParameters: TypesGen.TemplateVersionParameter[] = [];
|
|
const requiredParameters: TypesGen.TemplateVersionParameter[] = [];
|
|
|
|
for (const p of templateParameters) {
|
|
// It is mutable and required. Mutable values can be changed after so we
|
|
// don't need to ask them if they are not required.
|
|
const isMutableAndRequired = p.mutable && p.required;
|
|
// Is immutable, so we can check if it is its first time on the build
|
|
const isImmutable = !p.mutable;
|
|
|
|
if (isMutableAndRequired || isImmutable) {
|
|
requiredParameters.push(p);
|
|
}
|
|
}
|
|
|
|
for (const parameter of requiredParameters) {
|
|
// Check if there is a new value
|
|
let buildParameter = newBuildParameters.find(
|
|
(p) => p.name === parameter.name,
|
|
);
|
|
|
|
// If not, get the old one
|
|
if (!buildParameter) {
|
|
buildParameter = oldBuildParameters.find(
|
|
(p) => p.name === parameter.name,
|
|
);
|
|
}
|
|
|
|
// If there is a value from the new or old one, it is not missed
|
|
if (buildParameter) {
|
|
continue;
|
|
}
|
|
|
|
missingParameters.push(parameter);
|
|
}
|
|
|
|
// Check if parameter "options" changed and we can't use old build parameters.
|
|
for (const templateParameter of templateParameters) {
|
|
if (templateParameter.options.length === 0) {
|
|
continue;
|
|
}
|
|
// For multi-select, extra steps are necessary to JSON parse the value.
|
|
if (templateParameter.form_type === "multi-select") {
|
|
continue;
|
|
}
|
|
let buildParameter = newBuildParameters.find(
|
|
(p) => p.name === templateParameter.name,
|
|
);
|
|
|
|
// If not, get the old one
|
|
if (!buildParameter) {
|
|
buildParameter = oldBuildParameters.find(
|
|
(p) => p.name === templateParameter.name,
|
|
);
|
|
}
|
|
|
|
if (!buildParameter) {
|
|
continue;
|
|
}
|
|
|
|
const matchingOption = templateParameter.options.find(
|
|
(option) => option.value === buildParameter?.value,
|
|
);
|
|
if (!matchingOption) {
|
|
missingParameters.push(templateParameter);
|
|
}
|
|
}
|
|
|
|
return missingParameters;
|
|
};
|
|
|
|
/**
|
|
* Originally from codersdk/client.go.
|
|
* The below declaration is required to stop Knip from complaining.
|
|
* @public
|
|
*/
|
|
export const SessionTokenCookie = "coder_session_token";
|
|
|
|
/**
|
|
* @param agentId
|
|
* @returns {OneWayWebSocket} A OneWayWebSocket that emits Server-Sent Events.
|
|
*/
|
|
export const watchAgentMetadata = (
|
|
agentId: string,
|
|
): OneWayWebSocket<TypesGen.ServerSentEvent> => {
|
|
return new OneWayWebSocket({
|
|
apiRoute: `/api/v2/workspaceagents/${agentId}/watch-metadata-ws`,
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @returns {OneWayWebSocket} A OneWayWebSocket that emits Server-Sent Events.
|
|
*/
|
|
export const watchWorkspace = (
|
|
workspaceId: string,
|
|
): OneWayWebSocket<TypesGen.ServerSentEvent> => {
|
|
return new OneWayWebSocket({
|
|
apiRoute: `/api/v2/workspaces/${workspaceId}/watch-ws`,
|
|
});
|
|
};
|
|
|
|
export const watchAgentContainers = (
|
|
agentId: string,
|
|
): OneWayWebSocket<TypesGen.WorkspaceAgentListContainersResponse> => {
|
|
return new OneWayWebSocket({
|
|
apiRoute: `/api/v2/workspaceagents/${agentId}/containers/watch`,
|
|
});
|
|
};
|
|
|
|
type WatchInboxNotificationsParams = Readonly<{
|
|
read_status?: "read" | "unread" | "all";
|
|
}>;
|
|
|
|
export function watchInboxNotifications(
|
|
params?: WatchInboxNotificationsParams,
|
|
): OneWayWebSocket<TypesGen.GetInboxNotificationResponse> {
|
|
return new OneWayWebSocket({
|
|
apiRoute: "/api/v2/notifications/inbox/watch",
|
|
searchParams: params,
|
|
});
|
|
}
|
|
|
|
export const getURLWithSearchParams = (
|
|
basePath: string,
|
|
options?: SearchParamOptions,
|
|
): string => {
|
|
if (!options) {
|
|
return basePath;
|
|
}
|
|
|
|
const searchParams = new URLSearchParams();
|
|
for (const [key, value] of Object.entries(options)) {
|
|
if (value !== undefined && value !== "") {
|
|
searchParams.append(key, value.toString());
|
|
}
|
|
}
|
|
|
|
const searchString = searchParams.toString();
|
|
return searchString ? `${basePath}?${searchString}` : basePath;
|
|
};
|
|
|
|
// withDefaultFeatures sets all unspecified features to not_entitled and
|
|
// disabled.
|
|
export const withDefaultFeatures = (
|
|
fs: Partial<TypesGen.Entitlements["features"]>,
|
|
): TypesGen.Entitlements["features"] => {
|
|
for (const feature of TypesGen.FeatureNames) {
|
|
// Skip fields that are already filled.
|
|
if (fs[feature] !== undefined) {
|
|
continue;
|
|
}
|
|
|
|
fs[feature] = {
|
|
enabled: false,
|
|
entitlement: "not_entitled",
|
|
};
|
|
}
|
|
|
|
return fs as TypesGen.Entitlements["features"];
|
|
};
|
|
|
|
type WatchBuildLogsByTemplateVersionIdOptions = {
|
|
after?: number;
|
|
onMessage: (log: TypesGen.ProvisionerJobLog) => void;
|
|
onDone?: () => void;
|
|
onError: (error: Error) => void;
|
|
};
|
|
|
|
export const watchBuildLogsByTemplateVersionId = (
|
|
versionId: string,
|
|
{
|
|
onMessage,
|
|
onDone,
|
|
onError,
|
|
after,
|
|
}: WatchBuildLogsByTemplateVersionIdOptions,
|
|
) => {
|
|
const searchParams = new URLSearchParams({ follow: "true" });
|
|
if (after !== undefined) {
|
|
searchParams.append("after", after.toString());
|
|
}
|
|
|
|
const socket = createWebSocket(
|
|
`/api/v2/templateversions/${versionId}/logs`,
|
|
searchParams,
|
|
);
|
|
|
|
socket.addEventListener("message", (event) =>
|
|
onMessage(JSON.parse(event.data) as TypesGen.ProvisionerJobLog),
|
|
);
|
|
|
|
socket.addEventListener("error", () => {
|
|
onError(new Error("Connection for logs failed."));
|
|
socket.close();
|
|
});
|
|
|
|
socket.addEventListener("close", () => {
|
|
// When the socket closes, logs have finished streaming!
|
|
onDone?.();
|
|
});
|
|
|
|
return socket;
|
|
};
|
|
|
|
export const watchWorkspaceAgentLogs = (
|
|
agentId: string,
|
|
params?: WatchWorkspaceAgentLogsParams,
|
|
) => {
|
|
const searchParams = new URLSearchParams({
|
|
follow: "true",
|
|
after: params?.after?.toString() ?? "",
|
|
});
|
|
|
|
/**
|
|
* WebSocket compression in Safari (confirmed in 16.5) is broken when
|
|
* the server sends large messages. The following error is seen:
|
|
* WebSocket connection to 'wss://...' failed: The operation couldn't be completed.
|
|
*/
|
|
if (userAgentParser(navigator.userAgent).browser.name === "Safari") {
|
|
searchParams.set("no_compression", "");
|
|
}
|
|
|
|
return new OneWayWebSocket<TypesGen.WorkspaceAgentLog[]>({
|
|
apiRoute: `/api/v2/workspaceagents/${agentId}/logs`,
|
|
searchParams,
|
|
});
|
|
};
|
|
|
|
type WatchWorkspaceAgentLogsParams = {
|
|
after?: number;
|
|
};
|
|
|
|
type WatchBuildLogsByBuildIdOptions = {
|
|
after?: number;
|
|
onMessage: (log: TypesGen.ProvisionerJobLog) => void;
|
|
onDone?: () => void;
|
|
onError?: (error: Error) => void;
|
|
};
|
|
export const watchBuildLogsByBuildId = (
|
|
buildId: string,
|
|
{ onMessage, onDone, onError, after }: WatchBuildLogsByBuildIdOptions,
|
|
) => {
|
|
const searchParams = new URLSearchParams({ follow: "true" });
|
|
if (after !== undefined) {
|
|
searchParams.append("after", after.toString());
|
|
}
|
|
|
|
const socket = createWebSocket(
|
|
`/api/v2/workspacebuilds/${buildId}/logs`,
|
|
searchParams,
|
|
);
|
|
|
|
socket.addEventListener("message", (event) =>
|
|
onMessage(JSON.parse(event.data) as TypesGen.ProvisionerJobLog),
|
|
);
|
|
|
|
socket.addEventListener("error", () => {
|
|
if (socket.readyState === socket.CLOSED) {
|
|
return;
|
|
}
|
|
onError?.(new Error("Connection for logs failed."));
|
|
socket.close();
|
|
});
|
|
|
|
socket.addEventListener("close", () => {
|
|
// When the socket closes, logs have finished streaming!
|
|
onDone?.();
|
|
});
|
|
|
|
return socket;
|
|
};
|
|
|
|
// This is the base header that is used for several requests. This is defined as
|
|
// a readonly value, but only copies of it should be passed into the API calls,
|
|
// because Axios is able to mutate the headers
|
|
const BASE_CONTENT_TYPE_JSON = {
|
|
"Content-Type": "application/json",
|
|
} as const satisfies HeadersInit;
|
|
|
|
export type GetTemplatesOptions = Readonly<{
|
|
readonly deprecated?: boolean;
|
|
}>;
|
|
|
|
export type GetTemplatesQuery = Readonly<{
|
|
readonly q: string;
|
|
}>;
|
|
|
|
function normalizeGetTemplatesOptions(
|
|
options: GetTemplatesOptions | GetTemplatesQuery = {},
|
|
): Record<string, string> {
|
|
if ("q" in options) {
|
|
return options;
|
|
}
|
|
|
|
const params: Record<string, string> = {};
|
|
if (options.deprecated !== undefined) {
|
|
params.deprecated = String(options.deprecated);
|
|
}
|
|
return params;
|
|
}
|
|
|
|
type SearchParamOptions = TypesGen.Pagination & {
|
|
q?: string;
|
|
};
|
|
|
|
type RestartWorkspaceParameters = Readonly<{
|
|
workspace: TypesGen.Workspace;
|
|
buildParameters?: TypesGen.WorkspaceBuildParameter[];
|
|
}>;
|
|
|
|
export type DeleteWorkspaceOptions = Pick<
|
|
TypesGen.CreateWorkspaceBuildRequest,
|
|
"log_level" | "orphan"
|
|
>;
|
|
|
|
export type DeploymentConfig = Readonly<{
|
|
config: TypesGen.DeploymentValues;
|
|
options: TypesGen.SerpentOption[];
|
|
}>;
|
|
|
|
type Claims = {
|
|
license_expires: number;
|
|
account_type?: string;
|
|
account_id?: string;
|
|
trial: boolean;
|
|
all_features: boolean;
|
|
// feature_set is omitted on legacy licenses
|
|
feature_set?: string;
|
|
version: number;
|
|
features: Record<string, number>;
|
|
require_telemetry?: boolean;
|
|
};
|
|
|
|
export type GetLicensesResponse = Omit<TypesGen.License, "claims"> & {
|
|
claims: Claims;
|
|
expires_at: string;
|
|
};
|
|
|
|
export type InsightsParams = {
|
|
start_time: string;
|
|
end_time: string;
|
|
template_ids: string;
|
|
};
|
|
|
|
export type InsightsTemplateParams = InsightsParams & {
|
|
interval: "day" | "week";
|
|
};
|
|
|
|
export class MissingBuildParameters extends Error {
|
|
parameters: TypesGen.TemplateVersionParameter[] = [];
|
|
versionId: string;
|
|
|
|
constructor(
|
|
parameters: TypesGen.TemplateVersionParameter[],
|
|
versionId: string,
|
|
) {
|
|
super("Missing build parameters.");
|
|
this.parameters = parameters;
|
|
this.versionId = versionId;
|
|
}
|
|
}
|
|
|
|
export class ParameterValidationError extends Error {
|
|
constructor(
|
|
public readonly versionId: string,
|
|
public readonly validations: FieldError[],
|
|
) {
|
|
super("Parameters are not valid for new template version");
|
|
}
|
|
}
|
|
|
|
export type GetProvisionerJobsParams = {
|
|
status?: string;
|
|
limit?: number;
|
|
// IDs separated by comma
|
|
ids?: string;
|
|
};
|
|
|
|
export type GetProvisionerDaemonsParams = {
|
|
// IDs separated by comma
|
|
ids?: string;
|
|
// Stringified JSON Object
|
|
tags?: string;
|
|
limit?: number;
|
|
// Include offline provisioner daemons?
|
|
offline?: boolean;
|
|
};
|
|
|
|
/**
|
|
* This is the container for all API methods. It's split off to make it more
|
|
* clear where API methods should go, but it is eventually merged into the Api
|
|
* class with a more flat hierarchy
|
|
*
|
|
* All public methods should be defined as arrow functions to ensure that they
|
|
* can be passed around the React UI without losing their `this` context.
|
|
*
|
|
* This is one of the few cases where you have to worry about the difference
|
|
* between traditional methods and arrow function properties. Arrow functions
|
|
* disable JS's dynamic scope, and force all `this` references to resolve via
|
|
* lexical scope.
|
|
*/
|
|
class ApiMethods {
|
|
experimental: ExperimentalApiMethods;
|
|
|
|
constructor(protected readonly axios: AxiosInstance) {
|
|
this.experimental = new ExperimentalApiMethods(this.axios);
|
|
}
|
|
|
|
login = async (
|
|
email: string,
|
|
password: string,
|
|
): Promise<TypesGen.LoginWithPasswordResponse> => {
|
|
const payload = JSON.stringify({ email, password });
|
|
const response = await this.axios.post<TypesGen.LoginWithPasswordResponse>(
|
|
"/api/v2/users/login",
|
|
payload,
|
|
{ headers: { ...BASE_CONTENT_TYPE_JSON } },
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
convertToOAUTH = async (request: TypesGen.ConvertLoginRequest) => {
|
|
const response = await this.axios.post<TypesGen.OAuthConversionResponse>(
|
|
"/api/v2/users/me/convert-login",
|
|
request,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
logout = async (): Promise<void> => {
|
|
return this.axios.post("/api/v2/users/logout");
|
|
};
|
|
|
|
getAuthenticatedUser = async () => {
|
|
const response = await this.axios.get<TypesGen.User>("/api/v2/users/me");
|
|
return response.data;
|
|
};
|
|
|
|
getUserParameters = async (templateID: string) => {
|
|
const response = await this.axios.get<TypesGen.UserParameter[]>(
|
|
`/api/v2/users/me/autofill-parameters?template_id=${templateID}`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getAuthMethods = async (): Promise<TypesGen.AuthMethods> => {
|
|
const response = await this.axios.get<TypesGen.AuthMethods>(
|
|
"/api/v2/users/authmethods",
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getUserLoginType = async (): Promise<TypesGen.UserLoginType> => {
|
|
const response = await this.axios.get<TypesGen.UserLoginType>(
|
|
"/api/v2/users/me/login-type",
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
checkAuthorization = async <TResponse extends TypesGen.AuthorizationResponse>(
|
|
params: TypesGen.AuthorizationRequest,
|
|
) => {
|
|
const response = await this.axios.post<TResponse>(
|
|
"/api/v2/authcheck",
|
|
params,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getApiKey = async (): Promise<TypesGen.GenerateAPIKeyResponse> => {
|
|
const response = await this.axios.post<TypesGen.GenerateAPIKeyResponse>(
|
|
"/api/v2/users/me/keys",
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getTokens = async (
|
|
params: TypesGen.TokensFilter,
|
|
): Promise<TypesGen.APIKeyWithOwner[]> => {
|
|
const response = await this.axios.get<TypesGen.APIKeyWithOwner[]>(
|
|
"/api/v2/users/me/keys/tokens",
|
|
{ params },
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
deleteToken = async (keyId: string): Promise<void> => {
|
|
await this.axios.delete(`/api/v2/users/me/keys/${keyId}`);
|
|
};
|
|
|
|
createToken = async (
|
|
params: TypesGen.CreateTokenRequest,
|
|
): Promise<TypesGen.GenerateAPIKeyResponse> => {
|
|
const response = await this.axios.post(
|
|
"/api/v2/users/me/keys/tokens",
|
|
params,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getTokenConfig = async (): Promise<TypesGen.TokenConfig> => {
|
|
const response = await this.axios.get(
|
|
"/api/v2/users/me/keys/tokens/tokenconfig",
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getUsers = async (
|
|
options: TypesGen.UsersRequest,
|
|
signal?: AbortSignal,
|
|
): Promise<TypesGen.GetUsersResponse> => {
|
|
const url = getURLWithSearchParams("/api/v2/users", options);
|
|
const response = await this.axios.get<TypesGen.GetUsersResponse>(
|
|
url.toString(),
|
|
{ signal },
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
createOrganization = async (params: TypesGen.CreateOrganizationRequest) => {
|
|
const response = await this.axios.post<TypesGen.Organization>(
|
|
"/api/v2/organizations",
|
|
params,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
updateOrganization = async (
|
|
organization: string,
|
|
params: TypesGen.UpdateOrganizationRequest,
|
|
) => {
|
|
const response = await this.axios.patch<TypesGen.Organization>(
|
|
`/api/v2/organizations/${organization}`,
|
|
params,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
deleteOrganization = async (organization: string) => {
|
|
await this.axios.delete<TypesGen.Organization>(
|
|
`/api/v2/organizations/${organization}`,
|
|
);
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
getOrganization = async (
|
|
organization: string,
|
|
): Promise<TypesGen.Organization> => {
|
|
const response = await this.axios.get<TypesGen.Organization>(
|
|
`/api/v2/organizations/${organization}`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
getOrganizationMembers = async (organization: string) => {
|
|
const response = await this.axios.get<
|
|
TypesGen.OrganizationMemberWithUserData[]
|
|
>(`/api/v2/organizations/${organization}/members`);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
* @param options Pagination options
|
|
*/
|
|
getOrganizationPaginatedMembers = async (
|
|
organization: string,
|
|
options?: TypesGen.Pagination,
|
|
) => {
|
|
const url = getURLWithSearchParams(
|
|
`/api/v2/organizations/${organization}/paginated-members`,
|
|
options,
|
|
);
|
|
const response =
|
|
await this.axios.get<TypesGen.PaginatedMembersResponse>(url);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
getOrganizationRoles = async (organization: string) => {
|
|
const response = await this.axios.get<TypesGen.AssignableRoles[]>(
|
|
`/api/v2/organizations/${organization}/members/roles`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
updateOrganizationMemberRoles = async (
|
|
organization: string,
|
|
userId: string,
|
|
roles: TypesGen.SlimRole["name"][],
|
|
): Promise<TypesGen.User> => {
|
|
const response = await this.axios.put<TypesGen.User>(
|
|
`/api/v2/organizations/${organization}/members/${userId}/roles`,
|
|
{ roles },
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
createOrganizationRole = async (
|
|
organization: string,
|
|
role: TypesGen.Role,
|
|
): Promise<TypesGen.Role> => {
|
|
const response = await this.axios.post<TypesGen.Role>(
|
|
`/api/v2/organizations/${organization}/members/roles`,
|
|
role,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
updateOrganizationRole = async (
|
|
organization: string,
|
|
role: TypesGen.Role,
|
|
): Promise<TypesGen.Role> => {
|
|
const response = await this.axios.put<TypesGen.Role>(
|
|
`/api/v2/organizations/${organization}/members/roles`,
|
|
role,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
deleteOrganizationRole = async (organization: string, roleName: string) => {
|
|
await this.axios.delete(
|
|
`/api/v2/organizations/${organization}/members/roles/${roleName}`,
|
|
);
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
addOrganizationMember = async (organization: string, userId: string) => {
|
|
const response = await this.axios.post<TypesGen.OrganizationMember>(
|
|
`/api/v2/organizations/${organization}/members/${userId}`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
removeOrganizationMember = async (organization: string, userId: string) => {
|
|
await this.axios.delete(
|
|
`/api/v2/organizations/${organization}/members/${userId}`,
|
|
);
|
|
};
|
|
|
|
getOrganizations = async (): Promise<TypesGen.Organization[]> => {
|
|
const response = await this.axios.get<TypesGen.Organization[]>(
|
|
"/api/v2/organizations",
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
getMyOrganizations = async (): Promise<TypesGen.Organization[]> => {
|
|
const response = await this.axios.get<TypesGen.Organization[]>(
|
|
"/api/v2/users/me/organizations",
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
getProvisionerDaemonsByOrganization = async (
|
|
organization: string,
|
|
params?: GetProvisionerDaemonsParams,
|
|
): Promise<TypesGen.ProvisionerDaemon[]> => {
|
|
const response = await this.axios.get<TypesGen.ProvisionerDaemon[]>(
|
|
`/api/v2/organizations/${organization}/provisionerdaemons`,
|
|
{ params },
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
getProvisionerDaemonGroupsByOrganization = async (
|
|
organization: string,
|
|
): Promise<TypesGen.ProvisionerKeyDaemons[]> => {
|
|
const response = await this.axios.get<TypesGen.ProvisionerKeyDaemons[]>(
|
|
`/api/v2/organizations/${organization}/provisionerkeys/daemons`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
getOrganizationIdpSyncSettings =
|
|
async (): Promise<TypesGen.OrganizationSyncSettings> => {
|
|
const response = await this.axios.get<TypesGen.OrganizationSyncSettings>(
|
|
"/api/v2/settings/idpsync/organization",
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
patchOrganizationIdpSyncSettings = async (
|
|
data: TypesGen.OrganizationSyncSettings,
|
|
) => {
|
|
const response = await this.axios.patch<TypesGen.Response>(
|
|
"/api/v2/settings/idpsync/organization",
|
|
data,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param data
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
patchGroupIdpSyncSettings = async (
|
|
data: TypesGen.GroupSyncSettings,
|
|
organization: string,
|
|
) => {
|
|
const response = await this.axios.patch<TypesGen.Response>(
|
|
`/api/v2/organizations/${organization}/settings/idpsync/groups`,
|
|
data,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param data
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
patchRoleIdpSyncSettings = async (
|
|
data: TypesGen.RoleSyncSettings,
|
|
organization: string,
|
|
) => {
|
|
const response = await this.axios.patch<TypesGen.Response>(
|
|
`/api/v2/organizations/${organization}/settings/idpsync/roles`,
|
|
data,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
getGroupIdpSyncSettingsByOrganization = async (
|
|
organization: string,
|
|
): Promise<TypesGen.GroupSyncSettings> => {
|
|
const response = await this.axios.get<TypesGen.GroupSyncSettings>(
|
|
`/api/v2/organizations/${organization}/settings/idpsync/groups`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
getRoleIdpSyncSettingsByOrganization = async (
|
|
organization: string,
|
|
): Promise<TypesGen.RoleSyncSettings> => {
|
|
const response = await this.axios.get<TypesGen.RoleSyncSettings>(
|
|
`/api/v2/organizations/${organization}/settings/idpsync/roles`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
getDeploymentIdpSyncFieldValues = async (
|
|
field: string,
|
|
): Promise<readonly string[]> => {
|
|
const params = new URLSearchParams();
|
|
params.set("claimField", field);
|
|
const response = await this.axios.get<readonly string[]>(
|
|
`/api/v2/settings/idpsync/field-values?${params}`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
getOrganizationIdpSyncClaimFieldValues = async (
|
|
organization: string,
|
|
field: string,
|
|
) => {
|
|
const params = new URLSearchParams();
|
|
params.set("claimField", field);
|
|
const response = await this.axios.get<readonly string[]>(
|
|
`/api/v2/organizations/${organization}/settings/idpsync/field-values?${params}`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
getTemplate = async (templateId: string): Promise<TypesGen.Template> => {
|
|
const response = await this.axios.get<TypesGen.Template>(
|
|
`/api/v2/templates/${templateId}`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getTemplates = async (
|
|
options?: GetTemplatesOptions | GetTemplatesQuery,
|
|
): Promise<TypesGen.Template[]> => {
|
|
const params = normalizeGetTemplatesOptions(options);
|
|
const response = await this.axios.get<TypesGen.Template[]>(
|
|
"/api/v2/templates",
|
|
{ params },
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
getTemplatesByOrganization = async (
|
|
organization: string,
|
|
options?: GetTemplatesOptions,
|
|
): Promise<TypesGen.Template[]> => {
|
|
const params = normalizeGetTemplatesOptions(options);
|
|
const response = await this.axios.get<TypesGen.Template[]>(
|
|
`/api/v2/organizations/${organization}/templates`,
|
|
{ params },
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
getTemplateByName = async (
|
|
organization: string,
|
|
name: string,
|
|
): Promise<TypesGen.Template> => {
|
|
const response = await this.axios.get<TypesGen.Template>(
|
|
`/api/v2/organizations/${organization}/templates/${name}`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getTemplateVersion = async (
|
|
versionId: string,
|
|
): Promise<TypesGen.TemplateVersion> => {
|
|
const response = await this.axios.get<TypesGen.TemplateVersion>(
|
|
`/api/v2/templateversions/${versionId}`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getTemplateVersionResources = async (
|
|
versionId: string,
|
|
): Promise<TypesGen.WorkspaceResource[]> => {
|
|
const response = await this.axios.get<TypesGen.WorkspaceResource[]>(
|
|
`/api/v2/templateversions/${versionId}/resources`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getTemplateVersionVariables = async (
|
|
versionId: string,
|
|
): Promise<TypesGen.TemplateVersionVariable[]> => {
|
|
// Defined as separate variable to avoid wonky Prettier formatting because
|
|
// the type definition is so long
|
|
type VerArray = TypesGen.TemplateVersionVariable[];
|
|
|
|
const response = await this.axios.get<VerArray>(
|
|
`/api/v2/templateversions/${versionId}/variables`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getTemplateVersions = async (
|
|
templateId: string,
|
|
): Promise<TypesGen.TemplateVersion[]> => {
|
|
const response = await this.axios.get<TypesGen.TemplateVersion[]>(
|
|
`/api/v2/templates/${templateId}/versions`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
getTemplateVersionByName = async (
|
|
organization: string,
|
|
templateName: string,
|
|
versionName: string,
|
|
): Promise<TypesGen.TemplateVersion> => {
|
|
const response = await this.axios.get<TypesGen.TemplateVersion>(
|
|
`/api/v2/organizations/${organization}/templates/${templateName}/versions/${versionName}`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
getPreviousTemplateVersionByName = async (
|
|
organization: string,
|
|
templateName: string,
|
|
versionName: string,
|
|
) => {
|
|
try {
|
|
const response = await this.axios.get<TypesGen.TemplateVersion>(
|
|
`/api/v2/organizations/${organization}/templates/${templateName}/versions/${versionName}/previous`,
|
|
);
|
|
|
|
return response.data;
|
|
} catch (error) {
|
|
// When there is no previous version, like the first version of a
|
|
// template, the API returns 404 so in this case we can safely return
|
|
// undefined
|
|
const is404 =
|
|
isAxiosError(error) && error.response && error.response.status === 404;
|
|
|
|
if (is404) {
|
|
return undefined;
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
createTemplateVersion = async (
|
|
organization: string,
|
|
data: TypesGen.CreateTemplateVersionRequest,
|
|
): Promise<TypesGen.TemplateVersion> => {
|
|
const response = await this.axios.post<TypesGen.TemplateVersion>(
|
|
`/api/v2/organizations/${organization}/templateversions`,
|
|
data,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getTemplateVersionExternalAuth = async (
|
|
versionId: string,
|
|
): Promise<TypesGen.TemplateVersionExternalAuth[]> => {
|
|
const response = await this.axios.get(
|
|
`/api/v2/templateversions/${versionId}/external-auth`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getTemplateVersionDynamicParameters = async (
|
|
versionId: string,
|
|
data: TypesGen.DynamicParametersRequest,
|
|
): Promise<TypesGen.DynamicParametersResponse> => {
|
|
const response = await this.axios.post(
|
|
`/api/v2/templateversions/${versionId}/dynamic-parameters/evaluate`,
|
|
data,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
getTemplateVersionRichParameters = async (
|
|
versionId: string,
|
|
): Promise<TypesGen.TemplateVersionParameter[]> => {
|
|
const response = await this.axios.get(
|
|
`/api/v2/templateversions/${versionId}/rich-parameters`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
templateVersionDynamicParameters = (
|
|
versionId: string,
|
|
userId: string,
|
|
{
|
|
onMessage,
|
|
onError,
|
|
onClose,
|
|
}: {
|
|
onMessage: (response: TypesGen.DynamicParametersResponse) => void;
|
|
onError: (error: Error) => void;
|
|
onClose: () => void;
|
|
},
|
|
): WebSocket => {
|
|
const socket = createWebSocket(
|
|
`/api/v2/templateversions/${versionId}/dynamic-parameters`,
|
|
new URLSearchParams({ user_id: userId }),
|
|
);
|
|
|
|
socket.addEventListener("message", (event) =>
|
|
onMessage(JSON.parse(event.data) as TypesGen.DynamicParametersResponse),
|
|
);
|
|
|
|
socket.addEventListener("error", () => {
|
|
onError(new Error("Connection for dynamic parameters failed."));
|
|
socket.close();
|
|
});
|
|
|
|
socket.addEventListener("close", () => {
|
|
onClose();
|
|
});
|
|
|
|
return socket;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
createTemplate = async (
|
|
organization: string,
|
|
data: TypesGen.CreateTemplateRequest,
|
|
): Promise<TypesGen.Template> => {
|
|
const response = await this.axios.post(
|
|
`/api/v2/organizations/${organization}/templates`,
|
|
data,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
updateActiveTemplateVersion = async (
|
|
templateId: string,
|
|
data: TypesGen.UpdateActiveTemplateVersion,
|
|
) => {
|
|
const response = await this.axios.patch<TypesGen.Response>(
|
|
`/api/v2/templates/${templateId}/versions`,
|
|
data,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
patchTemplateVersion = async (
|
|
templateVersionId: string,
|
|
data: TypesGen.PatchTemplateVersionRequest,
|
|
) => {
|
|
const response = await this.axios.patch<TypesGen.TemplateVersion>(
|
|
`/api/v2/templateversions/${templateVersionId}`,
|
|
data,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
archiveTemplateVersion = async (templateVersionId: string) => {
|
|
const response = await this.axios.post<TypesGen.TemplateVersion>(
|
|
`/api/v2/templateversions/${templateVersionId}/archive`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
unarchiveTemplateVersion = async (templateVersionId: string) => {
|
|
const response = await this.axios.post<TypesGen.TemplateVersion>(
|
|
`/api/v2/templateversions/${templateVersionId}/unarchive`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* Downloads a template version as a tar or zip archive
|
|
* @param fileId The file ID from the template version's job
|
|
* @param format Optional format: "zip" for zip archive, empty/undefined for tar
|
|
* @returns Promise that resolves to a Blob containing the archive
|
|
*/
|
|
downloadTemplateVersion = async (
|
|
fileId: string,
|
|
format?: "zip",
|
|
): Promise<Blob> => {
|
|
const params = new URLSearchParams();
|
|
if (format) {
|
|
params.set("format", format);
|
|
}
|
|
|
|
const response = await this.axios.get(
|
|
`/api/v2/files/${fileId}?${params.toString()}`,
|
|
{
|
|
responseType: "blob",
|
|
},
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
updateTemplateMeta = async (
|
|
templateId: string,
|
|
data: TypesGen.UpdateTemplateMeta,
|
|
): Promise<TypesGen.Template | null> => {
|
|
const response = await this.axios.patch<TypesGen.Template>(
|
|
`/api/v2/templates/${templateId}`,
|
|
data,
|
|
);
|
|
|
|
// On 304 response there is no data payload.
|
|
if (response.status === 304) {
|
|
return null;
|
|
}
|
|
|
|
return response.data;
|
|
};
|
|
|
|
deleteTemplate = async (templateId: string): Promise<TypesGen.Template> => {
|
|
const response = await this.axios.delete<TypesGen.Template>(
|
|
`/api/v2/templates/${templateId}`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getWorkspace = async (
|
|
workspaceId: string,
|
|
params?: TypesGen.WorkspaceOptions,
|
|
): Promise<TypesGen.Workspace> => {
|
|
const response = await this.axios.get<TypesGen.Workspace>(
|
|
`/api/v2/workspaces/${workspaceId}`,
|
|
{ params },
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getWorkspaces = async (
|
|
req: TypesGen.WorkspacesRequest,
|
|
): Promise<TypesGen.WorkspacesResponse> => {
|
|
const url = getURLWithSearchParams("/api/v2/workspaces", req);
|
|
const response = await this.axios.get<TypesGen.WorkspacesResponse>(url);
|
|
return response.data;
|
|
};
|
|
|
|
getWorkspaceByOwnerAndName = async (
|
|
username: string,
|
|
workspaceName: string,
|
|
params?: TypesGen.WorkspaceOptions,
|
|
): Promise<TypesGen.Workspace> => {
|
|
const response = await this.axios.get<TypesGen.Workspace>(
|
|
`/api/v2/users/${username}/workspace/${workspaceName}`,
|
|
{ params },
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getWorkspaceBuildByNumber = async (
|
|
username: string,
|
|
workspaceName: string,
|
|
buildNumber: number,
|
|
): Promise<TypesGen.WorkspaceBuild> => {
|
|
const response = await this.axios.get<TypesGen.WorkspaceBuild>(
|
|
`/api/v2/users/${username}/workspace/${workspaceName}/builds/${buildNumber}`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
waitForBuild = (build: TypesGen.WorkspaceBuild) => {
|
|
return new Promise<TypesGen.ProvisionerJob | undefined>((res, reject) => {
|
|
void (async () => {
|
|
let latestJobInfo: TypesGen.ProvisionerJob | undefined;
|
|
|
|
while (
|
|
!["succeeded", "canceled"].some((status) =>
|
|
latestJobInfo?.status.includes(status),
|
|
)
|
|
) {
|
|
const { job } = await this.getWorkspaceBuildByNumber(
|
|
build.workspace_owner_name,
|
|
build.workspace_name,
|
|
build.build_number,
|
|
);
|
|
|
|
latestJobInfo = job;
|
|
if (latestJobInfo.status === "failed") {
|
|
return reject(latestJobInfo);
|
|
}
|
|
|
|
await delay(1000);
|
|
}
|
|
|
|
return res(latestJobInfo);
|
|
})();
|
|
});
|
|
};
|
|
|
|
postWorkspaceBuild = async (
|
|
workspaceId: string,
|
|
data: TypesGen.CreateWorkspaceBuildRequest,
|
|
): Promise<TypesGen.WorkspaceBuild> => {
|
|
const response = await this.axios.post(
|
|
`/api/v2/workspaces/${workspaceId}/builds`,
|
|
data,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
getTemplateVersionPresets = async (
|
|
templateVersionId: string,
|
|
): Promise<TypesGen.Preset[] | null> => {
|
|
const response = await this.axios.get<TypesGen.Preset[]>(
|
|
`/api/v2/templateversions/${templateVersionId}/presets`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
startWorkspace = (
|
|
workspaceId: string,
|
|
templateVersionId: string,
|
|
logLevel?: TypesGen.ProvisionerLogLevel,
|
|
buildParameters?: TypesGen.WorkspaceBuildParameter[],
|
|
) => {
|
|
return this.postWorkspaceBuild(workspaceId, {
|
|
transition: "start",
|
|
template_version_id: templateVersionId,
|
|
log_level: logLevel,
|
|
rich_parameter_values: buildParameters,
|
|
reason: "dashboard",
|
|
});
|
|
};
|
|
|
|
stopWorkspace = (
|
|
workspaceId: string,
|
|
logLevel?: TypesGen.ProvisionerLogLevel,
|
|
) => {
|
|
return this.postWorkspaceBuild(workspaceId, {
|
|
transition: "stop",
|
|
log_level: logLevel,
|
|
});
|
|
};
|
|
|
|
deleteWorkspace = (workspaceId: string, options?: DeleteWorkspaceOptions) => {
|
|
return this.postWorkspaceBuild(workspaceId, {
|
|
transition: "delete",
|
|
...options,
|
|
});
|
|
};
|
|
|
|
cancelWorkspaceBuild = async (
|
|
workspaceBuildId: TypesGen.WorkspaceBuild["id"],
|
|
params?: TypesGen.CancelWorkspaceBuildParams,
|
|
): Promise<TypesGen.Response> => {
|
|
const response = await this.axios.patch(
|
|
`/api/v2/workspacebuilds/${workspaceBuildId}/cancel`,
|
|
null,
|
|
{ params },
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
updateWorkspaceDormancy = async (
|
|
workspaceId: string,
|
|
dormant: boolean,
|
|
): Promise<TypesGen.Workspace> => {
|
|
const data: TypesGen.UpdateWorkspaceDormancy = { dormant };
|
|
const response = await this.axios.put(
|
|
`/api/v2/workspaces/${workspaceId}/dormant`,
|
|
data,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
updateWorkspaceAutomaticUpdates = async (
|
|
workspaceId: string,
|
|
automaticUpdates: TypesGen.AutomaticUpdates,
|
|
): Promise<void> => {
|
|
const req: TypesGen.UpdateWorkspaceAutomaticUpdatesRequest = {
|
|
automatic_updates: automaticUpdates,
|
|
};
|
|
|
|
const response = await this.axios.put(
|
|
`/api/v2/workspaces/${workspaceId}/autoupdates`,
|
|
req,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
restartWorkspace = async ({
|
|
workspace,
|
|
buildParameters,
|
|
}: RestartWorkspaceParameters): Promise<void> => {
|
|
const stopBuild = await this.stopWorkspace(workspace.id);
|
|
const awaitedStopBuild = await this.waitForBuild(stopBuild);
|
|
|
|
// If the restart is canceled halfway through, make sure we bail
|
|
if (awaitedStopBuild?.status === "canceled") {
|
|
return;
|
|
}
|
|
|
|
const startBuild = await this.startWorkspace(
|
|
workspace.id,
|
|
workspace.latest_build.template_version_id,
|
|
undefined,
|
|
buildParameters,
|
|
);
|
|
|
|
await this.waitForBuild(startBuild);
|
|
};
|
|
|
|
cancelTemplateVersionBuild = async (
|
|
templateVersionId: string,
|
|
): Promise<TypesGen.Response> => {
|
|
const response = await this.axios.patch(
|
|
`/api/v2/templateversions/${templateVersionId}/cancel`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
cancelTemplateVersionDryRun = async (
|
|
templateVersionId: string,
|
|
jobId: string,
|
|
): Promise<TypesGen.Response> => {
|
|
const response = await this.axios.patch(
|
|
`/api/v2/templateversions/${templateVersionId}/dry-run/${jobId}/cancel`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
createUser = async (
|
|
user: TypesGen.CreateUserRequestWithOrgs,
|
|
): Promise<TypesGen.User> => {
|
|
const response = await this.axios.post<TypesGen.User>(
|
|
"/api/v2/users",
|
|
user,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
createWorkspace = async (
|
|
userId: string,
|
|
workspace: TypesGen.CreateWorkspaceRequest,
|
|
): Promise<TypesGen.Workspace> => {
|
|
const response = await this.axios.post<TypesGen.Workspace>(
|
|
`/api/v2/users/${userId}/workspaces`,
|
|
workspace,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
patchWorkspace = async (
|
|
workspaceId: string,
|
|
data: TypesGen.UpdateWorkspaceRequest,
|
|
): Promise<void> => {
|
|
await this.axios.patch(`/api/v2/workspaces/${workspaceId}`, data);
|
|
};
|
|
|
|
getBuildInfo = async (): Promise<TypesGen.BuildInfoResponse> => {
|
|
const response = await this.axios.get("/api/v2/buildinfo");
|
|
return response.data;
|
|
};
|
|
|
|
getUpdateCheck = async (): Promise<TypesGen.UpdateCheckResponse> => {
|
|
const response = await this.axios.get("/api/v2/updatecheck");
|
|
return response.data;
|
|
};
|
|
|
|
putWorkspaceAutostart = async (
|
|
workspaceID: string,
|
|
autostart: TypesGen.UpdateWorkspaceAutostartRequest,
|
|
): Promise<void> => {
|
|
const payload = JSON.stringify(autostart);
|
|
await this.axios.put(
|
|
`/api/v2/workspaces/${workspaceID}/autostart`,
|
|
payload,
|
|
{ headers: { ...BASE_CONTENT_TYPE_JSON } },
|
|
);
|
|
};
|
|
|
|
putWorkspaceAutostop = async (
|
|
workspaceID: string,
|
|
ttl: TypesGen.UpdateWorkspaceTTLRequest,
|
|
): Promise<void> => {
|
|
const payload = JSON.stringify(ttl);
|
|
await this.axios.put(`/api/v2/workspaces/${workspaceID}/ttl`, payload, {
|
|
headers: { ...BASE_CONTENT_TYPE_JSON },
|
|
});
|
|
};
|
|
|
|
updateProfile = async (
|
|
userId: string,
|
|
data: TypesGen.UpdateUserProfileRequest,
|
|
): Promise<TypesGen.User> => {
|
|
const response = await this.axios.put(
|
|
`/api/v2/users/${userId}/profile`,
|
|
data,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
getAppearanceSettings =
|
|
async (): Promise<TypesGen.UserAppearanceSettings> => {
|
|
const response = await this.axios.get("/api/v2/users/me/appearance");
|
|
return response.data;
|
|
};
|
|
|
|
updateAppearanceSettings = async (
|
|
data: TypesGen.UpdateUserAppearanceSettingsRequest,
|
|
): Promise<TypesGen.UserAppearanceSettings> => {
|
|
const response = await this.axios.put("/api/v2/users/me/appearance", data);
|
|
return response.data;
|
|
};
|
|
|
|
getUserPreferenceSettings =
|
|
async (): Promise<TypesGen.UserPreferenceSettings> => {
|
|
const response = await this.axios.get("/api/v2/users/me/preferences");
|
|
return response.data;
|
|
};
|
|
|
|
updateUserPreferenceSettings = async (
|
|
req: TypesGen.UpdateUserPreferenceSettingsRequest,
|
|
): Promise<TypesGen.UserPreferenceSettings> => {
|
|
const response = await this.axios.put("/api/v2/users/me/preferences", req);
|
|
return response.data;
|
|
};
|
|
|
|
getUserQuietHoursSchedule = async (
|
|
userId: TypesGen.User["id"],
|
|
): Promise<TypesGen.UserQuietHoursScheduleResponse> => {
|
|
const response = await this.axios.get(
|
|
`/api/v2/users/${userId}/quiet-hours`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
updateUserQuietHoursSchedule = async (
|
|
userId: TypesGen.User["id"],
|
|
data: TypesGen.UpdateUserQuietHoursScheduleRequest,
|
|
): Promise<TypesGen.UserQuietHoursScheduleResponse> => {
|
|
const response = await this.axios.put(
|
|
`/api/v2/users/${userId}/quiet-hours`,
|
|
data,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
activateUser = async (
|
|
userId: TypesGen.User["id"],
|
|
): Promise<TypesGen.User> => {
|
|
const response = await this.axios.put<TypesGen.User>(
|
|
`/api/v2/users/${userId}/status/activate`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
suspendUser = async (userId: TypesGen.User["id"]): Promise<TypesGen.User> => {
|
|
const response = await this.axios.put<TypesGen.User>(
|
|
`/api/v2/users/${userId}/status/suspend`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
deleteUser = async (userId: TypesGen.User["id"]): Promise<void> => {
|
|
await this.axios.delete(`/api/v2/users/${userId}`);
|
|
};
|
|
|
|
// API definition:
|
|
// https://github.com/coder/coder/blob/db665e7261f3c24a272ccec48233a3e276878239/coderd/users.go#L33-L53
|
|
hasFirstUser = async (): Promise<boolean> => {
|
|
try {
|
|
// If it is success, it is true
|
|
await this.axios.get("/api/v2/users/first");
|
|
return true;
|
|
} catch (error) {
|
|
// If it returns a 404, it is false
|
|
if (isAxiosError(error) && error.response?.status === 404) {
|
|
return false;
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
createFirstUser = async (
|
|
req: TypesGen.CreateFirstUserRequest,
|
|
): Promise<TypesGen.CreateFirstUserResponse> => {
|
|
const response = await this.axios.post("/api/v2/users/first", req);
|
|
return response.data;
|
|
};
|
|
|
|
updateUserPassword = async (
|
|
userId: TypesGen.User["id"],
|
|
updatePassword: TypesGen.UpdateUserPasswordRequest,
|
|
): Promise<void> => {
|
|
await this.axios.put(`/api/v2/users/${userId}/password`, updatePassword);
|
|
};
|
|
|
|
validateUserPassword = async (
|
|
password: string,
|
|
): Promise<TypesGen.ValidateUserPasswordResponse> => {
|
|
const response = await this.axios.post("/api/v2/users/validate-password", {
|
|
password,
|
|
});
|
|
return response.data;
|
|
};
|
|
|
|
getRoles = async (): Promise<Array<TypesGen.AssignableRoles>> => {
|
|
const response = await this.axios.get<TypesGen.AssignableRoles[]>(
|
|
"/api/v2/users/roles",
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
updateUserRoles = async (
|
|
roles: TypesGen.SlimRole["name"][],
|
|
userId: TypesGen.User["id"],
|
|
): Promise<TypesGen.User> => {
|
|
const response = await this.axios.put<TypesGen.User>(
|
|
`/api/v2/users/${userId}/roles`,
|
|
{ roles },
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getUserSSHKey = async (userId = "me"): Promise<TypesGen.GitSSHKey> => {
|
|
const response = await this.axios.get<TypesGen.GitSSHKey>(
|
|
`/api/v2/users/${userId}/gitsshkey`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
regenerateUserSSHKey = async (userId = "me"): Promise<TypesGen.GitSSHKey> => {
|
|
const response = await this.axios.put<TypesGen.GitSSHKey>(
|
|
`/api/v2/users/${userId}/gitsshkey`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getWorkspaceBuilds = async (
|
|
workspaceId: string,
|
|
req?: TypesGen.WorkspaceBuildsRequest,
|
|
) => {
|
|
const response = await this.axios.get<TypesGen.WorkspaceBuild[]>(
|
|
getURLWithSearchParams(`/api/v2/workspaces/${workspaceId}/builds`, req),
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getWorkspaceBuildLogs = async (
|
|
buildId: string,
|
|
): Promise<TypesGen.ProvisionerJobLog[]> => {
|
|
const response = await this.axios.get<TypesGen.ProvisionerJobLog[]>(
|
|
`/api/v2/workspacebuilds/${buildId}/logs`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getWorkspaceAgentLogs = async (
|
|
agentID: string,
|
|
): Promise<TypesGen.WorkspaceAgentLog[]> => {
|
|
const response = await this.axios.get<TypesGen.WorkspaceAgentLog[]>(
|
|
`/api/v2/workspaceagents/${agentID}/logs`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
putWorkspaceExtension = async (
|
|
workspaceId: string,
|
|
newDeadline: dayjs.Dayjs,
|
|
): Promise<void> => {
|
|
await this.axios.put(`/api/v2/workspaces/${workspaceId}/extend`, {
|
|
deadline: newDeadline,
|
|
});
|
|
};
|
|
|
|
refreshEntitlements = async (): Promise<void> => {
|
|
await this.axios.post("/api/v2/licenses/refresh-entitlements");
|
|
};
|
|
|
|
getEntitlements = async (): Promise<TypesGen.Entitlements> => {
|
|
try {
|
|
const response = await this.axios.get<TypesGen.Entitlements>(
|
|
"/api/v2/entitlements",
|
|
);
|
|
|
|
return response.data;
|
|
} catch (ex) {
|
|
if (isAxiosError(ex) && ex.response?.status === 404) {
|
|
return {
|
|
errors: [],
|
|
features: withDefaultFeatures({}),
|
|
has_license: false,
|
|
require_telemetry: false,
|
|
trial: false,
|
|
warnings: [],
|
|
refreshed_at: "",
|
|
};
|
|
}
|
|
throw ex;
|
|
}
|
|
};
|
|
|
|
getExperiments = async (): Promise<TypesGen.Experiment[]> => {
|
|
try {
|
|
const response = await this.axios.get<TypesGen.Experiment[]>(
|
|
"/api/v2/experiments",
|
|
);
|
|
|
|
return response.data;
|
|
} catch (error) {
|
|
if (isAxiosError(error) && error.response?.status === 404) {
|
|
return [];
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
getAvailableExperiments =
|
|
async (): Promise<TypesGen.AvailableExperiments> => {
|
|
try {
|
|
const response = await this.axios.get("/api/v2/experiments/available");
|
|
|
|
return response.data;
|
|
} catch (error) {
|
|
if (isAxiosError(error) && error.response?.status === 404) {
|
|
return { safe: [] };
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
getExternalAuthProvider = async (
|
|
provider: string,
|
|
): Promise<TypesGen.ExternalAuth> => {
|
|
const res = await this.axios.get(`/api/v2/external-auth/${provider}`);
|
|
return res.data;
|
|
};
|
|
|
|
getExternalAuthDevice = async (
|
|
provider: string,
|
|
): Promise<TypesGen.ExternalAuthDevice> => {
|
|
const resp = await this.axios.get(
|
|
`/api/v2/external-auth/${provider}/device`,
|
|
);
|
|
return resp.data;
|
|
};
|
|
|
|
exchangeExternalAuthDevice = async (
|
|
provider: string,
|
|
req: TypesGen.ExternalAuthDeviceExchange,
|
|
): Promise<void> => {
|
|
const resp = await this.axios.post(
|
|
`/api/v2/external-auth/${provider}/device`,
|
|
req,
|
|
);
|
|
|
|
return resp.data;
|
|
};
|
|
|
|
getUserExternalAuthProviders =
|
|
async (): Promise<TypesGen.ListUserExternalAuthResponse> => {
|
|
const resp = await this.axios.get("/api/v2/external-auth");
|
|
return resp.data;
|
|
};
|
|
|
|
unlinkExternalAuthProvider = async (
|
|
provider: string,
|
|
): Promise<DeleteExternalAuthByIDResponse> => {
|
|
const resp = await this.axios.delete(`/api/v2/external-auth/${provider}`);
|
|
return resp.data;
|
|
};
|
|
|
|
getOAuth2GitHubDeviceFlowCallback = async (
|
|
code: string,
|
|
state: string,
|
|
): Promise<TypesGen.OAuth2DeviceFlowCallbackResponse> => {
|
|
const resp = await this.axios.get(
|
|
`/api/v2/users/oauth2/github/callback?code=${code}&state=${state}`,
|
|
);
|
|
// sanity check
|
|
if (
|
|
typeof resp.data !== "object" ||
|
|
typeof resp.data.redirect_url !== "string"
|
|
) {
|
|
console.error("Invalid response from OAuth2 GitHub callback", resp);
|
|
throw new Error("Invalid response from OAuth2 GitHub callback");
|
|
}
|
|
return resp.data;
|
|
};
|
|
|
|
getOAuth2GitHubDevice = async (): Promise<TypesGen.ExternalAuthDevice> => {
|
|
const resp = await this.axios.get("/api/v2/users/oauth2/github/device");
|
|
return resp.data;
|
|
};
|
|
|
|
getOAuth2ProviderApps = async (
|
|
filter?: TypesGen.OAuth2ProviderAppFilter,
|
|
): Promise<TypesGen.OAuth2ProviderApp[]> => {
|
|
const params = filter?.user_id
|
|
? new URLSearchParams({ user_id: filter.user_id }).toString()
|
|
: "";
|
|
|
|
const resp = await this.axios.get(`/api/v2/oauth2-provider/apps?${params}`);
|
|
return resp.data;
|
|
};
|
|
|
|
getOAuth2ProviderApp = async (
|
|
id: string,
|
|
): Promise<TypesGen.OAuth2ProviderApp> => {
|
|
const resp = await this.axios.get(`/api/v2/oauth2-provider/apps/${id}`);
|
|
return resp.data;
|
|
};
|
|
|
|
postOAuth2ProviderApp = async (
|
|
data: TypesGen.PostOAuth2ProviderAppRequest,
|
|
): Promise<TypesGen.OAuth2ProviderApp> => {
|
|
const response = await this.axios.post(
|
|
"/api/v2/oauth2-provider/apps",
|
|
data,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
putOAuth2ProviderApp = async (
|
|
id: string,
|
|
data: TypesGen.PutOAuth2ProviderAppRequest,
|
|
): Promise<TypesGen.OAuth2ProviderApp> => {
|
|
const response = await this.axios.put(
|
|
`/api/v2/oauth2-provider/apps/${id}`,
|
|
data,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
deleteOAuth2ProviderApp = async (id: string): Promise<void> => {
|
|
await this.axios.delete(`/api/v2/oauth2-provider/apps/${id}`);
|
|
};
|
|
|
|
getOAuth2ProviderAppSecrets = async (
|
|
id: string,
|
|
): Promise<TypesGen.OAuth2ProviderAppSecret[]> => {
|
|
const resp = await this.axios.get(
|
|
`/api/v2/oauth2-provider/apps/${id}/secrets`,
|
|
);
|
|
return resp.data;
|
|
};
|
|
|
|
postOAuth2ProviderAppSecret = async (
|
|
id: string,
|
|
): Promise<TypesGen.OAuth2ProviderAppSecretFull> => {
|
|
const resp = await this.axios.post(
|
|
`/api/v2/oauth2-provider/apps/${id}/secrets`,
|
|
);
|
|
return resp.data;
|
|
};
|
|
|
|
deleteOAuth2ProviderAppSecret = async (
|
|
appId: string,
|
|
secretId: string,
|
|
): Promise<void> => {
|
|
await this.axios.delete(
|
|
`/api/v2/oauth2-provider/apps/${appId}/secrets/${secretId}`,
|
|
);
|
|
};
|
|
|
|
revokeOAuth2ProviderApp = async (appId: string): Promise<void> => {
|
|
await this.axios.delete(`/oauth2/tokens?client_id=${appId}`);
|
|
};
|
|
|
|
getAuditLogs = async (
|
|
options: TypesGen.AuditLogsRequest,
|
|
): Promise<TypesGen.AuditLogResponse> => {
|
|
const url = getURLWithSearchParams("/api/v2/audit", options);
|
|
const response = await this.axios.get(url);
|
|
return response.data;
|
|
};
|
|
|
|
getConnectionLogs = async (
|
|
options: TypesGen.ConnectionLogsRequest,
|
|
): Promise<TypesGen.ConnectionLogResponse> => {
|
|
const url = getURLWithSearchParams("/api/v2/connectionlog", options);
|
|
const response = await this.axios.get(url);
|
|
return response.data;
|
|
};
|
|
|
|
getTemplateDAUs = async (
|
|
templateId: string,
|
|
): Promise<TypesGen.DAUsResponse> => {
|
|
const response = await this.axios.get(
|
|
`/api/v2/templates/${templateId}/daus`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getDeploymentDAUs = async (
|
|
// Default to user's local timezone.
|
|
// As /api/v2/insights/daus only accepts whole-number values for tz_offset
|
|
// we truncate the tz offset down to the closest hour.
|
|
offset = Math.trunc(new Date().getTimezoneOffset() / 60),
|
|
): Promise<TypesGen.DAUsResponse> => {
|
|
const response = await this.axios.get(
|
|
`/api/v2/insights/daus?tz_offset=${offset}`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getTemplateACLAvailable = async (
|
|
templateId: string,
|
|
options: TypesGen.UsersRequest,
|
|
): Promise<TypesGen.ACLAvailable> => {
|
|
const url = getURLWithSearchParams(
|
|
`/api/v2/templates/${templateId}/acl/available`,
|
|
options,
|
|
).toString();
|
|
|
|
const response = await this.axios.get(url);
|
|
return response.data;
|
|
};
|
|
|
|
getTemplateACL = async (
|
|
templateId: string,
|
|
): Promise<TypesGen.TemplateACL> => {
|
|
const response = await this.axios.get(
|
|
`/api/v2/templates/${templateId}/acl`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
updateTemplateACL = async (
|
|
templateId: string,
|
|
data: TypesGen.UpdateTemplateACL,
|
|
): Promise<{ message: string }> => {
|
|
const response = await this.axios.patch(
|
|
`/api/v2/templates/${templateId}/acl`,
|
|
data,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
updateWorkspaceACL = async (
|
|
workspaceId: string,
|
|
data: TypesGen.UpdateWorkspaceACL,
|
|
): Promise<void> => {
|
|
await this.axios.patch(`/api/v2/workspaces/${workspaceId}/acl`, data);
|
|
};
|
|
|
|
getApplicationsHost = async (): Promise<TypesGen.AppHostResponse> => {
|
|
const response = await this.axios.get("/api/v2/applications/host");
|
|
return response.data;
|
|
};
|
|
|
|
getGroups = async (
|
|
options: { userId?: string } = {},
|
|
): Promise<TypesGen.Group[]> => {
|
|
const params: Record<string, string> = {};
|
|
if (options.userId !== undefined) {
|
|
params.has_member = options.userId;
|
|
}
|
|
|
|
const response = await this.axios.get("/api/v2/groups", { params });
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
getGroupsByOrganization = async (
|
|
organization: string,
|
|
): Promise<TypesGen.Group[]> => {
|
|
const response = await this.axios.get(
|
|
`/api/v2/organizations/${organization}/groups`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
createGroup = async (
|
|
organization: string,
|
|
data: TypesGen.CreateGroupRequest,
|
|
): Promise<TypesGen.Group> => {
|
|
const response = await this.axios.post(
|
|
`/api/v2/organizations/${organization}/groups`,
|
|
data,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
getGroup = async (
|
|
organization: string,
|
|
groupName: string,
|
|
): Promise<TypesGen.Group> => {
|
|
const response = await this.axios.get(
|
|
`/api/v2/organizations/${organization}/groups/${groupName}`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
patchGroup = async (
|
|
groupId: string,
|
|
data: TypesGen.PatchGroupRequest,
|
|
): Promise<TypesGen.Group> => {
|
|
const response = await this.axios.patch(`/api/v2/groups/${groupId}`, data);
|
|
return response.data;
|
|
};
|
|
|
|
addMember = async (groupId: string, userId: string) => {
|
|
return this.patchGroup(groupId, {
|
|
name: "",
|
|
add_users: [userId],
|
|
remove_users: [],
|
|
display_name: null,
|
|
avatar_url: null,
|
|
quota_allowance: null,
|
|
});
|
|
};
|
|
|
|
removeMember = async (groupId: string, userId: string) => {
|
|
return this.patchGroup(groupId, {
|
|
name: "",
|
|
add_users: [],
|
|
remove_users: [userId],
|
|
display_name: null,
|
|
avatar_url: null,
|
|
quota_allowance: null,
|
|
});
|
|
};
|
|
|
|
deleteGroup = async (groupId: string): Promise<void> => {
|
|
await this.axios.delete(`/api/v2/groups/${groupId}`);
|
|
};
|
|
|
|
getWorkspaceQuota = async (
|
|
organizationName: string,
|
|
username: string,
|
|
): Promise<TypesGen.WorkspaceQuota> => {
|
|
const response = await this.axios.get(
|
|
`/api/v2/organizations/${encodeURIComponent(organizationName)}/members/${encodeURIComponent(username)}/workspace-quota`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getAgentListeningPorts = async (
|
|
agentID: string,
|
|
): Promise<TypesGen.WorkspaceAgentListeningPortsResponse> => {
|
|
const response = await this.axios.get(
|
|
`/api/v2/workspaceagents/${agentID}/listening-ports`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
getWorkspaceAgentSharedPorts = async (
|
|
workspaceID: string,
|
|
): Promise<TypesGen.WorkspaceAgentPortShares> => {
|
|
const response = await this.axios.get(
|
|
`/api/v2/workspaces/${workspaceID}/port-share`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
getWorkspaceAgentCredentials = async (
|
|
workspaceID: string,
|
|
agentName: string,
|
|
): Promise<TypesGen.ExternalAgentCredentials> => {
|
|
const response = await this.axios.get(
|
|
`/api/v2/workspaces/${workspaceID}/external-agent/${agentName}/credentials`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
upsertWorkspaceAgentSharedPort = async (
|
|
workspaceID: string,
|
|
req: TypesGen.UpsertWorkspaceAgentPortShareRequest,
|
|
): Promise<TypesGen.WorkspaceAgentPortShares> => {
|
|
const response = await this.axios.post(
|
|
`/api/v2/workspaces/${workspaceID}/port-share`,
|
|
req,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
deleteWorkspaceAgentSharedPort = async (
|
|
workspaceID: string,
|
|
req: TypesGen.DeleteWorkspaceAgentPortShareRequest,
|
|
): Promise<TypesGen.WorkspaceAgentPortShares> => {
|
|
const response = await this.axios.delete(
|
|
`/api/v2/workspaces/${workspaceID}/port-share`,
|
|
{ data: req },
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
// getDeploymentSSHConfig is used by the VSCode-Extension.
|
|
getDeploymentSSHConfig = async (): Promise<TypesGen.SSHConfigResponse> => {
|
|
const response = await this.axios.get("/api/v2/deployment/ssh");
|
|
return response.data;
|
|
};
|
|
|
|
getDeploymentConfig = async (): Promise<DeploymentConfig> => {
|
|
const response = await this.axios.get("/api/v2/deployment/config");
|
|
return response.data;
|
|
};
|
|
|
|
getDeploymentStats = async (): Promise<TypesGen.DeploymentStats> => {
|
|
const response = await this.axios.get("/api/v2/deployment/stats");
|
|
return response.data;
|
|
};
|
|
|
|
getReplicas = async (): Promise<TypesGen.Replica[]> => {
|
|
const response = await this.axios.get("/api/v2/replicas");
|
|
return response.data;
|
|
};
|
|
|
|
getFile = async (fileId: string): Promise<ArrayBuffer> => {
|
|
const response = await this.axios.get<ArrayBuffer>(
|
|
`/api/v2/files/${fileId}`,
|
|
{ responseType: "arraybuffer" },
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getWorkspaceProxyRegions = async (): Promise<
|
|
TypesGen.RegionsResponse<TypesGen.Region>
|
|
> => {
|
|
const response =
|
|
await this.axios.get<TypesGen.RegionsResponse<TypesGen.Region>>(
|
|
"/api/v2/regions",
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getWorkspaceProxies = async (): Promise<
|
|
TypesGen.RegionsResponse<TypesGen.WorkspaceProxy>
|
|
> => {
|
|
const response = await this.axios.get<
|
|
TypesGen.RegionsResponse<TypesGen.WorkspaceProxy>
|
|
>("/api/v2/workspaceproxies");
|
|
|
|
return response.data;
|
|
};
|
|
|
|
createWorkspaceProxy = async (
|
|
b: TypesGen.CreateWorkspaceProxyRequest,
|
|
): Promise<TypesGen.UpdateWorkspaceProxyResponse> => {
|
|
const response = await this.axios.post("/api/v2/workspaceproxies", b);
|
|
return response.data;
|
|
};
|
|
|
|
getAppearance = async (): Promise<TypesGen.AppearanceConfig> => {
|
|
try {
|
|
const response = await this.axios.get("/api/v2/appearance");
|
|
return response.data || {};
|
|
} catch (ex) {
|
|
if (isAxiosError(ex) && ex.response?.status === 404) {
|
|
return {
|
|
application_name: "",
|
|
docs_url: "",
|
|
logo_url: "",
|
|
announcement_banners: [],
|
|
service_banner: {
|
|
enabled: false,
|
|
},
|
|
};
|
|
}
|
|
|
|
throw ex;
|
|
}
|
|
};
|
|
|
|
updateAppearance = async (
|
|
b: TypesGen.AppearanceConfig,
|
|
): Promise<TypesGen.AppearanceConfig> => {
|
|
const response = await this.axios.put("/api/v2/appearance", b);
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* @param organization Can be the organization's ID or name
|
|
*/
|
|
getTemplateExamples = async (): Promise<TypesGen.TemplateExample[]> => {
|
|
const response = await this.axios.get("/api/v2/templates/examples");
|
|
|
|
return response.data;
|
|
};
|
|
|
|
uploadFile = async (file: File): Promise<TypesGen.UploadResponse> => {
|
|
const response = await this.axios.post("/api/v2/files", file, {
|
|
headers: { "Content-Type": file.type },
|
|
});
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getTemplateVersionLogs = async (
|
|
versionId: string,
|
|
): Promise<TypesGen.ProvisionerJobLog[]> => {
|
|
const response = await this.axios.get<TypesGen.ProvisionerJobLog[]>(
|
|
`/api/v2/templateversions/${versionId}/logs`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
updateWorkspaceVersion = async (
|
|
workspace: TypesGen.Workspace,
|
|
): Promise<TypesGen.WorkspaceBuild> => {
|
|
const template = await this.getTemplate(workspace.template_id);
|
|
return this.startWorkspace(workspace.id, template.active_version_id);
|
|
};
|
|
|
|
getWorkspaceBuildParameters = async (
|
|
workspaceBuildId: TypesGen.WorkspaceBuild["id"],
|
|
): Promise<TypesGen.WorkspaceBuildParameter[]> => {
|
|
const response = await this.axios.get<TypesGen.WorkspaceBuildParameter[]>(
|
|
`/api/v2/workspacebuilds/${workspaceBuildId}/parameters`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getLicenses = async (): Promise<GetLicensesResponse[]> => {
|
|
const response = await this.axios.get("/api/v2/licenses");
|
|
return response.data;
|
|
};
|
|
|
|
createLicense = async (
|
|
data: TypesGen.AddLicenseRequest,
|
|
): Promise<TypesGen.AddLicenseRequest> => {
|
|
const response = await this.axios.post("/api/v2/licenses", data);
|
|
return response.data;
|
|
};
|
|
|
|
removeLicense = async (licenseId: number): Promise<void> => {
|
|
await this.axios.delete(`/api/v2/licenses/${licenseId}`);
|
|
};
|
|
|
|
getDynamicParameters = async (
|
|
templateVersionId: string,
|
|
ownerId: string,
|
|
oldBuildParameters: TypesGen.WorkspaceBuildParameter[],
|
|
) => {
|
|
const request: DynamicParametersRequest = {
|
|
id: 1,
|
|
owner_id: ownerId,
|
|
inputs: Object.fromEntries(
|
|
new Map(oldBuildParameters.map((param) => [param.name, param.value])),
|
|
),
|
|
};
|
|
|
|
const dynamicParametersResponse =
|
|
await this.getTemplateVersionDynamicParameters(
|
|
templateVersionId,
|
|
request,
|
|
);
|
|
|
|
return dynamicParametersResponse.parameters.map((p) => ({
|
|
...p,
|
|
description_plaintext: p.description || "",
|
|
default_value: p.default_value?.valid ? p.default_value.value : "",
|
|
options: p.options
|
|
? p.options.map((opt) => ({
|
|
...opt,
|
|
value: opt.value?.valid ? opt.value.value : "",
|
|
}))
|
|
: [],
|
|
}));
|
|
};
|
|
|
|
/** Steps to change the workspace version
|
|
* - Get the latest template to access the latest active version
|
|
* - Get the current build parameters
|
|
* - Get the template parameters
|
|
* - Update the build parameters and check if there are missed parameters for
|
|
* the new version
|
|
* - If there are missing parameters raise an error
|
|
* - Create a build with the version and updated build parameters
|
|
*/
|
|
changeWorkspaceVersion = async (
|
|
workspace: TypesGen.Workspace,
|
|
templateVersionId: string,
|
|
newBuildParameters: TypesGen.WorkspaceBuildParameter[] = [],
|
|
isDynamicParametersEnabled = false,
|
|
): Promise<TypesGen.WorkspaceBuild> => {
|
|
const currentBuildParameters = await this.getWorkspaceBuildParameters(
|
|
workspace.latest_build.id,
|
|
);
|
|
|
|
let templateParameters: TypesGen.TemplateVersionParameter[] = [];
|
|
if (isDynamicParametersEnabled) {
|
|
templateParameters = await this.getDynamicParameters(
|
|
templateVersionId,
|
|
workspace.owner_id,
|
|
currentBuildParameters,
|
|
);
|
|
} else {
|
|
templateParameters =
|
|
await this.getTemplateVersionRichParameters(templateVersionId);
|
|
}
|
|
|
|
const missingParameters = getMissingParameters(
|
|
currentBuildParameters,
|
|
newBuildParameters,
|
|
templateParameters,
|
|
);
|
|
|
|
if (missingParameters.length > 0) {
|
|
throw new MissingBuildParameters(missingParameters, templateVersionId);
|
|
}
|
|
|
|
return this.postWorkspaceBuild(workspace.id, {
|
|
transition: "start",
|
|
template_version_id: templateVersionId,
|
|
rich_parameter_values: newBuildParameters,
|
|
});
|
|
};
|
|
|
|
/** Steps to update the workspace
|
|
* - Get the latest template to access the latest active version
|
|
* - Get the current build parameters
|
|
* - Get the template parameters
|
|
* - Update the build parameters and check if there are missed parameters for
|
|
* the newest version
|
|
* - If there are missing parameters raise an error
|
|
* - Stop the workspace with the current template version if it is already running
|
|
* - Create a build with the latest version and updated build parameters
|
|
*/
|
|
updateWorkspace = async (
|
|
workspace: TypesGen.Workspace,
|
|
newBuildParameters: TypesGen.WorkspaceBuildParameter[] = [],
|
|
isDynamicParametersEnabled = false,
|
|
): Promise<TypesGen.WorkspaceBuild> => {
|
|
const [template, oldBuildParameters] = await Promise.all([
|
|
this.getTemplate(workspace.template_id),
|
|
this.getWorkspaceBuildParameters(workspace.latest_build.id),
|
|
]);
|
|
|
|
const activeVersionId = template.active_version_id;
|
|
|
|
if (isDynamicParametersEnabled) {
|
|
try {
|
|
return await this.postWorkspaceBuild(workspace.id, {
|
|
transition: "start",
|
|
template_version_id: activeVersionId,
|
|
rich_parameter_values: newBuildParameters,
|
|
});
|
|
} catch (error) {
|
|
// If the build failed because of a parameter validation error, then we
|
|
// throw a special sentinel error that can be caught by the caller.
|
|
if (
|
|
isApiError(error) &&
|
|
error.response.status === 400 &&
|
|
error.response.data.validations &&
|
|
error.response.data.validations.length > 0
|
|
) {
|
|
throw new ParameterValidationError(
|
|
activeVersionId,
|
|
error.response.data.validations,
|
|
);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
const templateParameters =
|
|
await this.getTemplateVersionRichParameters(activeVersionId);
|
|
|
|
const missingParameters = getMissingParameters(
|
|
oldBuildParameters,
|
|
newBuildParameters,
|
|
templateParameters,
|
|
);
|
|
|
|
if (missingParameters.length > 0) {
|
|
throw new MissingBuildParameters(missingParameters, activeVersionId);
|
|
}
|
|
|
|
// Stop the workspace if it is already running.
|
|
if (workspace.latest_build.status === "running") {
|
|
const stopBuild = await this.stopWorkspace(workspace.id);
|
|
const awaitedStopBuild = await this.waitForBuild(stopBuild);
|
|
// If the stop is canceled halfway through, we bail.
|
|
// This is the same behaviour as restartWorkspace.
|
|
if (awaitedStopBuild?.status === "canceled") {
|
|
return Promise.reject(
|
|
new Error("Workspace stop was canceled, not proceeding with update."),
|
|
);
|
|
}
|
|
}
|
|
|
|
return this.postWorkspaceBuild(workspace.id, {
|
|
transition: "start",
|
|
template_version_id: activeVersionId,
|
|
rich_parameter_values: newBuildParameters,
|
|
});
|
|
};
|
|
|
|
getWorkspaceResolveAutostart = async (
|
|
workspaceId: string,
|
|
): Promise<TypesGen.ResolveAutostartResponse> => {
|
|
const response = await this.axios.get(
|
|
`/api/v2/workspaces/${workspaceId}/resolve-autostart`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
issueReconnectingPTYSignedToken = async (
|
|
params: TypesGen.IssueReconnectingPTYSignedTokenRequest,
|
|
): Promise<TypesGen.IssueReconnectingPTYSignedTokenResponse> => {
|
|
const response = await this.axios.post(
|
|
"/api/v2/applications/reconnecting-pty-signed-token",
|
|
params,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getInsightsUserLatency = async (
|
|
filters: InsightsParams,
|
|
): Promise<TypesGen.UserLatencyInsightsResponse> => {
|
|
const params = new URLSearchParams(filters);
|
|
const response = await this.axios.get(
|
|
`/api/v2/insights/user-latency?${params}`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getInsightsUserActivity = async (
|
|
filters: InsightsParams,
|
|
): Promise<TypesGen.UserActivityInsightsResponse> => {
|
|
const params = new URLSearchParams(filters);
|
|
const response = await this.axios.get(
|
|
`/api/v2/insights/user-activity?${params}`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getInsightsUserStatusCounts = async (
|
|
offset = Math.trunc(new Date().getTimezoneOffset() / 60),
|
|
): Promise<TypesGen.GetUserStatusCountsResponse> => {
|
|
const searchParams = new URLSearchParams({
|
|
tz_offset: offset.toString(),
|
|
});
|
|
const response = await this.axios.get(
|
|
`/api/v2/insights/user-status-counts?${searchParams}`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getInsightsTemplate = async (
|
|
params: InsightsTemplateParams,
|
|
): Promise<TypesGen.TemplateInsightsResponse> => {
|
|
const searchParams = new URLSearchParams(params);
|
|
const response = await this.axios.get(
|
|
`/api/v2/insights/templates?${searchParams}`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getHealth = async (force = false) => {
|
|
const params = new URLSearchParams({ force: force.toString() });
|
|
const response = await this.axios.get<TypesGen.HealthcheckReport>(
|
|
`/api/v2/debug/health?${params}`,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
getHealthSettings = async (): Promise<TypesGen.HealthSettings> => {
|
|
const res = await this.axios.get<TypesGen.HealthSettings>(
|
|
"/api/v2/debug/health/settings",
|
|
);
|
|
|
|
return res.data;
|
|
};
|
|
|
|
updateHealthSettings = async (data: TypesGen.UpdateHealthSettings) => {
|
|
const response = await this.axios.put<TypesGen.HealthSettings>(
|
|
"/api/v2/debug/health/settings",
|
|
data,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
putFavoriteWorkspace = async (workspaceID: string) => {
|
|
await this.axios.put(`/api/v2/workspaces/${workspaceID}/favorite`);
|
|
};
|
|
|
|
deleteFavoriteWorkspace = async (workspaceID: string) => {
|
|
await this.axios.delete(`/api/v2/workspaces/${workspaceID}/favorite`);
|
|
};
|
|
|
|
postWorkspaceUsage = async (
|
|
workspaceID: string,
|
|
options: PostWorkspaceUsageRequest,
|
|
) => {
|
|
const response = await this.axios.post(
|
|
`/api/v2/workspaces/${workspaceID}/usage`,
|
|
options,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getUserNotificationPreferences = async (userId: string) => {
|
|
const res = await this.axios.get<TypesGen.NotificationPreference[] | null>(
|
|
`/api/v2/users/${userId}/notifications/preferences`,
|
|
);
|
|
return res.data ?? [];
|
|
};
|
|
|
|
putUserNotificationPreferences = async (
|
|
userId: string,
|
|
req: TypesGen.UpdateUserNotificationPreferences,
|
|
) => {
|
|
const res = await this.axios.put<TypesGen.NotificationPreference[]>(
|
|
`/api/v2/users/${userId}/notifications/preferences`,
|
|
req,
|
|
);
|
|
return res.data;
|
|
};
|
|
|
|
getSystemNotificationTemplates = async () => {
|
|
const res = await this.axios.get<TypesGen.NotificationTemplate[]>(
|
|
"/api/v2/notifications/templates/system",
|
|
);
|
|
return res.data;
|
|
};
|
|
|
|
getCustomNotificationTemplates = async () => {
|
|
const res = await this.axios.get<TypesGen.NotificationTemplate[]>(
|
|
"/api/v2/notifications/templates/custom",
|
|
);
|
|
return res.data;
|
|
};
|
|
|
|
getNotificationDispatchMethods = async () => {
|
|
const res = await this.axios.get<TypesGen.NotificationMethodsResponse>(
|
|
"/api/v2/notifications/dispatch-methods",
|
|
);
|
|
return res.data;
|
|
};
|
|
|
|
updateNotificationTemplateMethod = async (
|
|
templateId: string,
|
|
req: TypesGen.UpdateNotificationTemplateMethod,
|
|
) => {
|
|
const res = await this.axios.put<void>(
|
|
`/api/v2/notifications/templates/${templateId}/method`,
|
|
req,
|
|
);
|
|
return res.data;
|
|
};
|
|
|
|
postTestNotification = async () => {
|
|
await this.axios.post<void>("/api/v2/notifications/test");
|
|
};
|
|
|
|
createWebPushSubscription = async (
|
|
userId: string,
|
|
req: TypesGen.WebpushSubscription,
|
|
) => {
|
|
await this.axios.post<void>(
|
|
`/api/v2/users/${userId}/webpush/subscription`,
|
|
req,
|
|
);
|
|
};
|
|
|
|
deleteWebPushSubscription = async (
|
|
userId: string,
|
|
req: TypesGen.DeleteWebpushSubscription,
|
|
) => {
|
|
await this.axios.delete<void>(
|
|
`/api/v2/users/${userId}/webpush/subscription`,
|
|
{
|
|
data: req,
|
|
},
|
|
);
|
|
};
|
|
|
|
requestOneTimePassword = async (
|
|
req: TypesGen.RequestOneTimePasscodeRequest,
|
|
) => {
|
|
await this.axios.post<void>("/api/v2/users/otp/request", req);
|
|
};
|
|
|
|
changePasswordWithOTP = async (
|
|
req: TypesGen.ChangePasswordWithOneTimePasscodeRequest,
|
|
) => {
|
|
await this.axios.post<void>("/api/v2/users/otp/change-password", req);
|
|
};
|
|
|
|
workspaceBuildTimings = async (workspaceBuildId: string) => {
|
|
const res = await this.axios.get<TypesGen.WorkspaceBuildTimings>(
|
|
`/api/v2/workspacebuilds/${workspaceBuildId}/timings`,
|
|
);
|
|
return res.data;
|
|
};
|
|
|
|
getProvisionerJobs = async (
|
|
orgId: string,
|
|
params: GetProvisionerJobsParams = {},
|
|
) => {
|
|
const res = await this.axios.get<TypesGen.ProvisionerJob[]>(
|
|
`/api/v2/organizations/${orgId}/provisionerjobs`,
|
|
{ params },
|
|
);
|
|
return res.data;
|
|
};
|
|
|
|
cancelProvisionerJob = async (job: TypesGen.ProvisionerJob) => {
|
|
switch (job.type) {
|
|
case "workspace_build":
|
|
if (!job.input.workspace_build_id) {
|
|
throw new Error("Workspace build ID is required to cancel this job");
|
|
}
|
|
return this.cancelWorkspaceBuild(job.input.workspace_build_id);
|
|
|
|
case "template_version_import":
|
|
if (!job.input.template_version_id) {
|
|
throw new Error("Template version ID is required to cancel this job");
|
|
}
|
|
return this.cancelTemplateVersionBuild(job.input.template_version_id);
|
|
|
|
case "template_version_dry_run":
|
|
if (!job.input.template_version_id) {
|
|
throw new Error("Template version ID is required to cancel this job");
|
|
}
|
|
return this.cancelTemplateVersionDryRun(
|
|
job.input.template_version_id,
|
|
job.id,
|
|
);
|
|
}
|
|
};
|
|
|
|
getAgentContainers = async (agentId: string, labels?: string[]) => {
|
|
const params = new URLSearchParams(
|
|
labels?.map((label) => ["label", label]),
|
|
);
|
|
const res =
|
|
await this.axios.get<TypesGen.WorkspaceAgentListContainersResponse>(
|
|
`/api/v2/workspaceagents/${agentId}/containers?${params.toString()}`,
|
|
);
|
|
return res.data;
|
|
};
|
|
|
|
getInboxNotifications = async (startingBeforeId?: string) => {
|
|
const params = new URLSearchParams();
|
|
if (startingBeforeId) {
|
|
params.append("starting_before", startingBeforeId);
|
|
}
|
|
const res = await this.axios.get<TypesGen.ListInboxNotificationsResponse>(
|
|
`/api/v2/notifications/inbox?${params.toString()}`,
|
|
);
|
|
return res.data;
|
|
};
|
|
|
|
updateInboxNotificationReadStatus = async (
|
|
notificationId: string,
|
|
req: TypesGen.UpdateInboxNotificationReadStatusRequest,
|
|
) => {
|
|
const res =
|
|
await this.axios.put<TypesGen.UpdateInboxNotificationReadStatusResponse>(
|
|
`/api/v2/notifications/inbox/${notificationId}/read-status`,
|
|
req,
|
|
);
|
|
return res.data;
|
|
};
|
|
|
|
markAllInboxNotificationsAsRead = async () => {
|
|
await this.axios.put<void>("/api/v2/notifications/inbox/mark-all-as-read");
|
|
};
|
|
|
|
createTask = async (
|
|
user: string,
|
|
req: TypesGen.CreateTaskRequest,
|
|
): Promise<TypesGen.Task> => {
|
|
const response = await this.axios.post<TypesGen.Task>(
|
|
`/api/v2/tasks/${user}`,
|
|
req,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
getTasks = async (
|
|
filter: TypesGen.TasksFilter,
|
|
): Promise<readonly TypesGen.Task[]> => {
|
|
const query: string[] = [];
|
|
if (filter.owner) {
|
|
query.push(`owner:${filter.owner}`);
|
|
}
|
|
if (filter.status) {
|
|
query.push(`status:${filter.status}`);
|
|
}
|
|
|
|
const res = await this.axios.get<TypesGen.TasksListResponse>(
|
|
"/api/v2/tasks",
|
|
{
|
|
params: {
|
|
q: query.join(", "),
|
|
},
|
|
},
|
|
);
|
|
|
|
return res.data.tasks;
|
|
};
|
|
|
|
getTask = async (user: string, id: string): Promise<TypesGen.Task> => {
|
|
const response = await this.axios.get<TypesGen.Task>(
|
|
`/api/v2/tasks/${user}/${id}`,
|
|
);
|
|
|
|
return response.data;
|
|
};
|
|
|
|
deleteTask = async (user: string, id: string): Promise<void> => {
|
|
await this.axios.delete(`/api/v2/tasks/${user}/${id}`);
|
|
};
|
|
|
|
createTaskFeedback = async (
|
|
_taskId: string,
|
|
_req: CreateTaskFeedbackRequest,
|
|
) => {
|
|
return new Promise<void>((res) => {
|
|
setTimeout(() => res(), 500);
|
|
});
|
|
};
|
|
}
|
|
|
|
export type TaskFeedbackRating = "good" | "okay" | "bad";
|
|
|
|
export type CreateTaskFeedbackRequest = {
|
|
rate: TaskFeedbackRating;
|
|
comment?: string;
|
|
};
|
|
|
|
// Experimental API methods call endpoints under the /api/experimental/ prefix.
|
|
// These endpoints are not stable and may change or be removed at any time.
|
|
//
|
|
// All methods must be defined with arrow function syntax. See the docstring
|
|
// above the ApiMethods class for a full explanation.
|
|
class ExperimentalApiMethods {
|
|
constructor(protected readonly axios: AxiosInstance) {}
|
|
|
|
getAIBridgeInterceptions = async (options: SearchParamOptions) => {
|
|
const url = getURLWithSearchParams(
|
|
"/api/experimental/aibridge/interceptions",
|
|
options,
|
|
);
|
|
const response =
|
|
await this.axios.get<TypesGen.AIBridgeListInterceptionsResponse>(url);
|
|
return response.data;
|
|
};
|
|
}
|
|
|
|
// This is a hard coded CSRF token/cookie pair for local development. In prod,
|
|
// the GoLang webserver generates a random cookie with a new token for each
|
|
// document request. For local development, we don't use the Go webserver for
|
|
// static files, so this is the 'hack' to make local development work with
|
|
// remote apis. The CSRF cookie for this token is "JXm9hOUdZctWt0ZZGAy9xiS/gxMKYOThdxjjMnMUyn4="
|
|
const csrfToken =
|
|
"KNKvagCBEHZK7ihe2t7fj6VeJ0UyTDco1yVUJE8N06oNqxLu5Zx1vRxZbgfC0mJJgeGkVjgs08mgPbcWPBkZ1A==";
|
|
|
|
// Always attach CSRF token to all requests. In puppeteer the document is
|
|
// undefined. In those cases, just do nothing.
|
|
const tokenMetadataElement =
|
|
typeof document !== "undefined"
|
|
? document.head.querySelector('meta[property="csrf-token"]')
|
|
: null;
|
|
|
|
function getConfiguredAxiosInstance(): AxiosInstance {
|
|
const instance = globalAxios.create();
|
|
|
|
// Adds 304 for the default axios validateStatus function
|
|
// https://github.com/axios/axios#handling-errors Check status here
|
|
// https://httpstatusdogs.com/
|
|
instance.defaults.validateStatus = (status) => {
|
|
return (status >= 200 && status < 300) || status === 304;
|
|
};
|
|
|
|
const metadataIsAvailable =
|
|
tokenMetadataElement !== null &&
|
|
tokenMetadataElement.getAttribute("content") !== null;
|
|
|
|
if (metadataIsAvailable) {
|
|
if (process.env.NODE_ENV === "development") {
|
|
// Development mode uses a hard-coded CSRF token
|
|
instance.defaults.headers.common["X-CSRF-TOKEN"] = csrfToken;
|
|
instance.defaults.headers.common["X-CSRF-TOKEN"] = csrfToken;
|
|
tokenMetadataElement.setAttribute("content", csrfToken);
|
|
} else {
|
|
instance.defaults.headers.common["X-CSRF-TOKEN"] =
|
|
tokenMetadataElement.getAttribute("content") ?? "";
|
|
}
|
|
} else {
|
|
// Do not write error logs if we are in a FE unit test.
|
|
if (!process.env.JEST_WORKER_ID && !process.env.VITEST) {
|
|
console.error("CSRF token not found");
|
|
}
|
|
}
|
|
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
* Utility function to help create a WebSocket connection with Coder's API.
|
|
*/
|
|
function createWebSocket(
|
|
path: string,
|
|
params: URLSearchParams = new URLSearchParams(),
|
|
) {
|
|
const protocol = location.protocol === "https:" ? "wss:" : "ws:";
|
|
const socket = new WebSocket(
|
|
`${protocol}//${location.host}${path}?${params}`,
|
|
);
|
|
socket.binaryType = "blob";
|
|
return socket;
|
|
}
|
|
|
|
// Other non-API methods defined here to make it a little easier to find them.
|
|
interface ClientApi extends ApiMethods {
|
|
getCsrfToken: () => string;
|
|
setSessionToken: (token: string) => void;
|
|
setHost: (host: string | undefined) => void;
|
|
getAxiosInstance: () => AxiosInstance;
|
|
}
|
|
|
|
class Api extends ApiMethods implements ClientApi {
|
|
constructor() {
|
|
const scopedAxiosInstance = getConfiguredAxiosInstance();
|
|
super(scopedAxiosInstance);
|
|
}
|
|
|
|
// As with ApiMethods, all public methods should be defined with arrow
|
|
// function syntax to ensure they can be passed around the React UI without
|
|
// losing/detaching their `this` context!
|
|
|
|
getCsrfToken = (): string => {
|
|
return csrfToken;
|
|
};
|
|
|
|
setSessionToken = (token: string): void => {
|
|
this.axios.defaults.headers.common["Coder-Session-Token"] = token;
|
|
};
|
|
|
|
setHost = (host: string | undefined): void => {
|
|
this.axios.defaults.baseURL = host;
|
|
};
|
|
|
|
getAxiosInstance = (): AxiosInstance => {
|
|
return this.axios;
|
|
};
|
|
}
|
|
|
|
export const API = new Api();
|