jamsocket
Version:
A CLI for the Jamsocket platform
262 lines (261 loc) • 11 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.JamsocketApi = exports.AuthenticationError = exports.AUTH_ERROR_HTTP_CODES = exports.HTTPError = void 0;
const request_1 = require("./lib/request");
var HttpMethod;
(function (HttpMethod) {
HttpMethod["Get"] = "GET";
HttpMethod["Post"] = "POST";
HttpMethod["Delete"] = "DELETE";
})(HttpMethod || (HttpMethod = {}));
class HTTPError extends Error {
constructor(status, code, message) {
super(message);
this.status = status;
this.code = code;
this.name = 'HTTPError';
}
}
exports.HTTPError = HTTPError;
exports.AUTH_ERROR_HTTP_CODES = new Set([401, 403, 407]);
class AuthenticationError extends HTTPError {
constructor(status, code, message) {
super(status, code, message);
this.status = status;
this.code = code;
this.name = 'AuthenticationError';
}
}
exports.AuthenticationError = AuthenticationError;
class JamsocketApi {
constructor(apiBase, options = {}) {
this.apiBase = apiBase;
this.options = options;
}
static fromEnvironment() {
const override = process.env.JAMSOCKET_SERVER_API;
let apiBase;
if (override === undefined) {
apiBase = 'https://api.jamsocket.com';
}
else {
console.warn(`Using Jamsocket server override: ${override}`);
apiBase = override;
}
const allowInsecure = process.env.ALLOW_INSECURE === 'true';
let rejectUnauthorized;
if (allowInsecure) {
if (override === undefined) {
console.warn('Insecure connections are only allowed when overriding the Jamsocket server. (Ignoring env var ALLOW_INSECURE)');
rejectUnauthorized = true;
}
else {
console.warn('Allowing insecure connections. (Found env var ALLOW_INSECURE)');
rejectUnauthorized = false;
}
}
return new JamsocketApi(apiBase, { rejectUnauthorized });
}
getAppBaseUrl() {
const hostname = new URL(this.apiBase).hostname;
const parts = hostname.split('.');
if (parts[0] === 'api') {
parts.shift();
}
const rootDomain = parts.join('.');
return `https://app.${rootDomain}`;
}
getLoginUrl(loginToken) {
const baseUrl = this.getAppBaseUrl();
return `${baseUrl}/cli-login/${loginToken}`;
}
async makeRequest(endpoint, method, body, headers, config) {
const url = `${this.apiBase}${endpoint}`;
const user = config?.getUserEmail() ?? null;
const account = config?.getAccount() ?? null;
if (user) {
headers = { ...headers, 'X-Jamsocket-User': user };
}
if (account) {
headers = { ...headers, 'X-Jamsocket-Account': account };
}
const response = await (0, request_1.request)(url, body || null, { ...this.options, method, headers });
const isJSONContentType = response.headers['content-type'] === 'application/json';
let responseBody;
try {
responseBody = JSON.parse(response.body);
}
catch { }
const isValidJSON = isJSONContentType && responseBody !== undefined;
if (response.statusCode && response.statusCode >= 400) {
if (isJSONContentType && isValidJSON) {
const { message, status, code, id } = responseBody.error;
throw new HTTPError(response.statusCode, code, `jamsocket: ${status} - ${code}: ${message} (id: ${id})`);
}
throw new HTTPError(response.statusCode, null, `jamsocket: ${response.statusCode}: ${response.body}`);
}
if (!isJSONContentType) {
throw new Error(`Unexpected content-type: ${response.headers['content-type']}. Url was: ${url}.`);
}
if (!isValidJSON) {
throw new Error(`jamsocket: error parsing JSON response: "${response.body}". Url was: ${url}. Status was: ${response.statusCode}`);
}
return responseBody;
}
async makeAuthenticatedRequest(endpoint, method, configOrAuthToken, body) {
let config;
let authHeaders = {};
if (typeof configOrAuthToken === 'string') {
config = undefined;
authHeaders = { Authorization: `Bearer ${configOrAuthToken}` };
}
else {
config = configOrAuthToken;
authHeaders = config.getAuthHeaders();
}
try {
// NOTE: this await here is required so that all the Promise "callback" logic is wrapped in this try/catch
return await this.makeRequest(endpoint, method, body, authHeaders, config);
}
catch (error) {
if (error instanceof HTTPError && exports.AUTH_ERROR_HTTP_CODES.has(error.status))
throw new AuthenticationError(error.status, error.code, error.message);
throw error;
}
}
makeStreamRequest(endpoint, headers, callback, config) {
const url = `${this.apiBase}${endpoint}`;
const user = config?.getUserEmail() ?? null;
const account = config?.getAccount() ?? null;
if (user) {
headers = { ...headers, 'X-Jamsocket-User': user };
}
if (account) {
headers = { ...headers, 'X-Jamsocket-Account': account };
}
return (0, request_1.eventStream)(url, {
...this.options,
method: HttpMethod.Get,
headers: { ...headers },
}, callback);
}
makeAuthenticatedStreamRequest(endpoint, config, callback) {
const authHeaders = config.getAuthHeaders();
return this.makeStreamRequest(endpoint, authHeaders, callback, config);
}
checkAuthToken(authToken) {
const url = '/auth';
return this.makeAuthenticatedRequest(url, HttpMethod.Get, authToken);
}
checkAuthConfig(config) {
const url = '/auth';
return this.makeAuthenticatedRequest(url, HttpMethod.Get, config);
}
serviceImage(accountName, serviceName, config) {
const url = `/service/${accountName}/${serviceName}/image-name`;
return this.makeAuthenticatedRequest(url, HttpMethod.Get, config);
}
serviceCreate(accountName, name, config) {
const url = `/v2/account/${accountName}/service`;
return this.makeAuthenticatedRequest(url, HttpMethod.Post, config, {
name,
});
}
serviceDelete(accountName, serviceName, config) {
const url = `/v2/service/${accountName}/${serviceName}/delete`;
return this.makeAuthenticatedRequest(url, HttpMethod.Post, config);
}
serviceInfo(accountName, serviceName, config) {
const url = `/v2/service/${accountName}/${serviceName}`;
return this.makeAuthenticatedRequest(url, HttpMethod.Get, config);
}
serviceList(accountName, config) {
const url = `/v2/account/${accountName}/services`;
return this.makeAuthenticatedRequest(url, HttpMethod.Get, config);
}
updateEnvironment(accountName, service, environment, config, body) {
let url = `/v2/service/${accountName}/${service}`;
if (environment) {
url += `/${environment}`;
}
url += '/update';
return this.makeAuthenticatedRequest(url, HttpMethod.Post, config, body);
}
spawn(accountName, serviceName, config, body) {
const url = `/v1/user/${accountName}/service/${serviceName}/spawn`;
return this.makeAuthenticatedRequest(url, HttpMethod.Post, config, body);
}
connect(accountName, serviceName, serviceEnvironment, config, body) {
const service = serviceEnvironment ? `${serviceName}/${serviceEnvironment}` : serviceName;
const url = `/v2/service/${accountName}/${service}/connect`;
return this.makeAuthenticatedRequest(url, HttpMethod.Post, config, body);
}
listRunningBackends(accountName, config) {
const url = `/v2/account/${accountName}/backends`;
return this.makeAuthenticatedRequest(url, HttpMethod.Get, config);
}
imagesList(accountName, serviceName, config) {
const url = `/v2/service/${accountName}/${serviceName}/images`;
return this.makeAuthenticatedRequest(url, HttpMethod.Get, config);
}
streamLogs(backend, config, callback) {
const url = `/v2/backend/${backend}/logs/stream`;
return this.makeAuthenticatedStreamRequest(url, config, callback);
}
streamMetrics(backend, config, callback) {
const url = `/v2/backend/${backend}/metrics/stream`;
return this.makeAuthenticatedStreamRequest(url, config, callback);
}
streamStatus(backend, callback, config) {
const url = `/v2/backend/${backend}/status/stream`;
const wrappedCallback = (line) => {
const val = JSON.parse(line);
callback(val);
};
return this.makeStreamRequest(url, null, wrappedCallback, config);
}
async status(backend, config) {
const url = `/v2/backend/${backend}/status`;
return this.makeRequest(url, HttpMethod.Get, undefined, undefined, config);
}
async terminate(backend, hard, config) {
const url = `/v2/backend/${backend}/terminate`;
return this.makeAuthenticatedRequest(url, HttpMethod.Post, config, { hard });
}
async terminateAllBackends(accountName, serviceName, body, config) {
const url = `/service/${accountName}/${serviceName}/terminate-backends`;
return this.makeAuthenticatedRequest(url, HttpMethod.Post, config, body);
}
async backendInfo(backend, config) {
const url = `/v2/backend/${backend}`;
return this.makeAuthenticatedRequest(url, HttpMethod.Get, config);
}
async startLoginAttempt() {
const url = '/cli-login';
return this.makeRequest(url, HttpMethod.Post, {});
}
async completeLoginAttempt(token, code) {
const url = `/cli-login/${token}/complete`;
return this.makeRequest(url, HttpMethod.Post, { code });
}
async revokeUserSession(userSessionId, config) {
const url = `/user-session/${userSessionId}/delete`;
return this.makeAuthenticatedRequest(url, HttpMethod.Post, config);
}
streamLoginStatus(loginToken) {
const endpoint = `/cli-login/${loginToken}/status/stream`;
const url = `${this.apiBase}${endpoint}`;
// right now, this stream only returns a single message and then closes
return new Promise((resolve) => {
const stream = (0, request_1.eventStream)(url, {
...this.options,
method: HttpMethod.Get,
}, (line) => {
const val = JSON.parse(line);
resolve(val.status === 'ok');
stream.close();
});
});
}
}
exports.JamsocketApi = JamsocketApi;
;