UNPKG

jamsocket

Version:

A CLI for the Jamsocket platform

262 lines (261 loc) 11 kB
"use strict"; 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;