UNPKG

trm-core

Version:

TRM (Transport Request Manager) Core

571 lines (570 loc) 24.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RegistryV2 = exports.PUBLIC_RESERVED_KEYWORD = void 0; const RegistryType_1 = require("./RegistryType"); const normalize_url_1 = __importDefault(require("@esm2cjs/normalize-url")); const axios_1 = require("axios"); const trm_registry_types_1 = require("trm-registry-types"); const TrmArtifact_1 = require("../trmPackage/TrmArtifact"); const FormData = __importStar(require("form-data")); const trm_commons_1 = require("trm-commons"); const crypto_1 = require("crypto"); const protocol_1 = require("../protocol"); const opener_1 = __importDefault(require("opener")); const commons_1 = require("../commons"); const node_cache_1 = __importDefault(require("node-cache")); const AXIOS_CTX = "RegistryV2"; exports.PUBLIC_RESERVED_KEYWORD = 'public'; class RegistryV2 { constructor(endpoint, name = 'Unknown', _coreVersion) { this.endpoint = endpoint; this.name = name; this._coreVersion = _coreVersion; this._cache = new node_cache_1.default({ stdTTL: 60, useClones: false }); var envEndpoint = process.env.TRM_PUBLIC_REGISTRY_ENDPOINT; trm_commons_1.Logger.log(`TRM_PUBLIC_REGISTRY_ENDPOINT Environment variable: ${envEndpoint}`, true); if (!envEndpoint || envEndpoint.trim().toLowerCase() === exports.PUBLIC_RESERVED_KEYWORD) { envEndpoint = 'https://www.trmregistry.com/registry'; } if (endpoint.trim().toLowerCase() === exports.PUBLIC_RESERVED_KEYWORD) { this._registryType = RegistryType_1.RegistryType.PUBLIC; } else { this._registryType = RegistryType_1.RegistryType.PRIVATE; } if (this._registryType === RegistryType_1.RegistryType.PUBLIC) { this.endpoint = envEndpoint; this.name = exports.PUBLIC_RESERVED_KEYWORD; } else { this.endpoint = endpoint; } trm_commons_1.Logger.log(`Endpoint type: ${this._registryType}`, true); trm_commons_1.Logger.log(`Endpoint before normalize: ${this.endpoint}`, true); this.endpoint = (0, normalize_url_1.default)(this.endpoint, { stripHash: true, removeQueryParameters: true }); trm_commons_1.Logger.log(`Endpoint after normalize: ${this.endpoint}`, true); if (this.endpoint.length > 100) { throw new Error(`Registry address length is too long! Maximum allowed is 100.`); } this._axiosInstance = (0, commons_1.getAxiosInstance)({ baseURL: this.endpoint, headers: this.getDefaultAxiosHeaders() }, AXIOS_CTX); } getDefaultAxiosHeaders() { var axiosHeaders = new axios_1.AxiosHeaders(); if (!this._userAgent) { try { this._userAgent = `trm-core v${this._coreVersion || (0, commons_1.getNodePackage)().version}`; } catch (_a) { this._userAgent = `trm-core with unknown version`; } } axiosHeaders.setUserAgent(this._userAgent); return axiosHeaders; } compare(registry) { if (registry instanceof RegistryV2) { return this.endpoint === registry.endpoint; } else { return false; } } getRegistryType() { return this._registryType; } authenticate() { return __awaiter(this, arguments, void 0, function* (defaultData = {}) { trm_commons_1.Logger.log(`Registry authentication request`, true); const ping = yield this.ping(); trm_commons_1.Logger.log(`Registry authentication type is: ${ping.authentication_type}`, true); if (ping.authentication_type !== trm_registry_types_1.AuthenticationType.NO_AUTH) { if (ping.authentication_type === trm_registry_types_1.AuthenticationType.BASIC) { yield this._basicAuth(defaultData); } if (ping.authentication_type === trm_registry_types_1.AuthenticationType.OAUTH2) { yield this._oauth2(defaultData); } if (ping.authentication_type === trm_registry_types_1.AuthenticationType.TOKEN) { yield this._tokenAuth(defaultData); } } this._cache.flushAll(); return this; }); } _basicAuth() { return __awaiter(this, arguments, void 0, function* (defaultData = {}) { var axiosHeaders = this.getDefaultAxiosHeaders(); var axiosDefaults = { baseURL: this.endpoint, headers: axiosHeaders }; var username = defaultData.username; var password = defaultData.password; const inq1 = yield trm_commons_1.Inquirer.prompt([{ type: "input", name: "username", message: "Registry username", validate: (input) => { return input ? true : false; }, when: !username }, { type: "password", name: "password", message: "Registry password", validate: (input) => { return input ? true : false; }, when: !password }]); username = username || inq1.username; password = password || inq1.password; const basicAuth = `${username}:${password}`; const encodedBasicAuth = Buffer.from(basicAuth).toString('base64'); axiosHeaders.setAuthorization(`Basic ${encodedBasicAuth}`); this._axiosInstance = (0, commons_1.getAxiosInstance)(axiosDefaults, AXIOS_CTX); this._authData = { username, password }; }); } _tokenAuth() { return __awaiter(this, arguments, void 0, function* (defaultData = {}) { var axiosHeaders = this.getDefaultAxiosHeaders(); var axiosDefaults = { baseURL: this.endpoint, headers: axiosHeaders }; var token = defaultData.token; if (!token && this._registryType == RegistryType_1.RegistryType.PUBLIC) { trm_commons_1.Logger.info(`To authenticate, generate a new token.`); trm_commons_1.Logger.info(`Follow the instructions https://docs.trmregistry.com/#/registry/public/authentication.`); } const inq1 = yield trm_commons_1.Inquirer.prompt([{ type: "input", name: "token", message: "Registry token", validate: (input) => { return input ? true : false; }, when: !token }]); token = token || inq1.token; axiosHeaders.setAuthorization(`Bearer ${token}`); this._axiosInstance = (0, commons_1.getAxiosInstance)(axiosDefaults, AXIOS_CTX); this._authData = { token }; }); } _oauth2() { return __awaiter(this, arguments, void 0, function* (defaultData = {}) { const ping = yield this.ping(); var runAuthFlow = false; const accessToken = defaultData.access_token; const refreshToken = defaultData.refresh_token; const tokenExpiry = defaultData.expires_in; const accessTokenTimestamp = defaultData.access_token_timestamp; const currentDate = new Date(); var authData; var oAuth2Request; var oAuth2Response; if (accessToken && accessTokenTimestamp && tokenExpiry) { try { const tokenDate = new Date(accessTokenTimestamp); const elapsedSeconds = (currentDate.getTime() - tokenDate.getTime()) / 1000; if (elapsedSeconds >= parseInt(tokenExpiry)) { if (refreshToken) { oAuth2Request = { grant_type: "refresh_token", refresh_token: refreshToken }; oAuth2Response = (yield ((0, commons_1.getAxiosInstance)({ baseURL: this.endpoint }, AXIOS_CTX)).post('/auth', oAuth2Request)).data; runAuthFlow = false; authData = { access_token: oAuth2Response.access_token, expires_in: oAuth2Response.expires_in, refresh_token: refreshToken, access_token_timestamp: currentDate.getTime() }; } else { runAuthFlow = true; } } else { runAuthFlow = false; authData = { access_token: accessToken, expires_in: tokenExpiry, refresh_token: refreshToken, access_token_timestamp: accessTokenTimestamp }; } } catch (e) { runAuthFlow = true; } } else { runAuthFlow = true; } if (runAuthFlow) { const oAuth2 = ping.authentication_data; const oAuth2ProtocolPath = "//oauth2"; const sRedirectUri = `trm:${oAuth2ProtocolPath}`; const oAuth2Url = new URL(oAuth2.authorization_url); const oAuth2State = (0, crypto_1.randomUUID)(); oAuth2Url.searchParams.append("client_id", oAuth2.client_id); oAuth2Url.searchParams.append("response_type", oAuth2.response_type); oAuth2Url.searchParams.append("redirect_uri", sRedirectUri); oAuth2Url.searchParams.append("state", oAuth2State); var sAuth2Url = oAuth2Url.toString(); if (oAuth2.scope) { sAuth2Url = `${sAuth2Url}&scope=${oAuth2.scope}`; } trm_commons_1.Logger.info(`Open login url at ${sAuth2Url}`); (0, opener_1.default)(sAuth2Url); const oAuth2Callback = yield new protocol_1.Protocol().run(); if (oAuth2Callback.path.startsWith(sRedirectUri)) { if (oAuth2Callback.parameters.state != oAuth2State) { throw new Error("Different state received in callback."); } oAuth2Request = { code: oAuth2Callback.parameters.code, grant_type: "authorization_code", redirect_uri: sRedirectUri }; oAuth2Response = (yield ((0, commons_1.getAxiosInstance)({ baseURL: this.endpoint }, AXIOS_CTX)).post('/auth', oAuth2Request)).data; if (oAuth2Response.token_type !== "Bearer") { throw new Error('Unknown token type.'); } authData = { access_token: oAuth2Response.access_token, expires_in: oAuth2Response.expires_in, refresh_token: oAuth2Response.refresh_token, access_token_timestamp: currentDate.getTime() }; } else { throw new Error("Callback received on a different uri."); } } this._authData = authData; var axiosHeaders = this.getDefaultAxiosHeaders(); var axiosDefaults = { baseURL: this.endpoint, headers: axiosHeaders }; axiosHeaders.setAuthorization(`Bearer ${this._authData.access_token}`); this._axiosInstance = (0, commons_1.getAxiosInstance)(axiosDefaults, AXIOS_CTX); }); } getAuthData() { return this._authData; } ping() { return __awaiter(this, void 0, void 0, function* () { var data = this._cache.get('ping'); if (!data) { try { data = (yield this._axiosInstance.get('/', { headers: {} })).data; } catch (e) { if (e.errors) { e.errors.forEach(err => trm_commons_1.Logger.error(err.message)); } data = new Error(`Registry "${this.name}" cannot be reached.`); } this._cache.set('ping', data); } if (data instanceof Error) { throw data; } else { return data; } }); } whoAmI() { return __awaiter(this, void 0, void 0, function* () { var data = this._cache.get('whoami'); if (!data) { try { data = (yield this._axiosInstance.get('/whoami')).data; } catch (e) { data = e; } this._cache.set('whoami', data); } if (data instanceof Error) { throw data; } else { return data; } }); } getPackage(fullName_1) { return __awaiter(this, arguments, void 0, function* (fullName, version = 'latest') { var data = this._cache.get(`package-${fullName}-${version}`); if (!data) { var ttl; try { data = (yield this._axiosInstance.get(`/package/${fullName}`, { params: { version: encodeURIComponent(version) } })).data; if (data.download_link_expiry) { try { ttl = Math.max(0, Math.floor((data.download_link_expiry - Date.now()) / 1000)); } catch (_a) { } } } catch (e) { data = e; } this._cache.set(`package-${fullName}-${version}`, data, ttl); } if (data instanceof Error) { throw data; } else { return data; } }); } downloadArtifact(fullName_1) { return __awaiter(this, arguments, void 0, function* (fullName, version = 'latest') { var _a; const packageData = yield this.getPackage(fullName, version); const chunks = []; let buffer; const logProgress = trm_commons_1.Logger.progressbar(`↓ ${fullName} ${version} [{bar}] {percentage}% | {value}/{total} bytes`, '>'); try { const response = yield this._axiosInstance.get(packageData.download_link, { headers: { Accept: 'application/octet-stream', }, maxRedirects: 10, responseType: 'stream', validateStatus: s => s >= 200 && s < 400, }); const totalBytes = Number((_a = response.headers['content-length']) !== null && _a !== void 0 ? _a : 0); let downloadedBytes = 0; if (totalBytes > 0) { logProgress.start(totalBytes, 0); } yield new Promise((resolve, reject) => { response.data.on('data', (chunk) => { chunks.push(chunk); downloadedBytes += chunk.length; if (totalBytes > 0) { logProgress.update(downloadedBytes); } }); response.data.on('end', () => resolve()); response.data.on('error', reject); }); if (totalBytes > 0) { logProgress.stop(); } buffer = Buffer.concat(chunks); } catch (e) { try { logProgress.stop(); } catch (_b) { } trm_commons_1.Logger.error(e.toString(), true); trm_commons_1.Logger.error(`Failed to fetch package at ${packageData.download_link}: ${e.message}`); throw e; } return new TrmArtifact_1.TrmArtifact(buffer); }); } validatePublish(fullName_1) { return __awaiter(this, arguments, void 0, function* (fullName, version = 'latest', isPrivate) { const status = (yield this._axiosInstance.get(`/publish/check/${fullName}`, { params: { version: encodeURIComponent(version), private: isPrivate ? 'X' : 'N' } })).status; if (status !== 204) { throw new Error(`Package cannot be published`); } }); } publish(fullName, version, artifact, readme, tags) { return __awaiter(this, void 0, void 0, function* () { const fileName = `${fullName}_v${version}`.replace('.', '_') + '.trm'; const formData = new FormData.default(); formData.append('artifact', artifact.binary, { filename: fileName, contentType: 'application/octet-stream' }); if (readme) { formData.append('readme', Buffer.from(readme), { filename: 'readme.md', contentType: 'text/markdown' }); } var params = { version, tags }; if (!tags) { delete params.tags; } yield this._axiosInstance.post(`/publish/${fullName}`, formData, { params, headers: formData.getHeaders() }); }); } unpublish(fullName, version) { return __awaiter(this, void 0, void 0, function* () { yield this._axiosInstance.post(`/unpublish/${fullName}`, null, { params: { version: encodeURIComponent(version) } }); }); } deprecate(fullName, version, deprecate) { return __awaiter(this, void 0, void 0, function* () { yield this._axiosInstance.post(`/deprecate/${fullName}`, { deprecate_note: deprecate.deprecate_note }, { params: { version: encodeURIComponent(version) } }); }); } addDistTag(fullName, distTag) { return __awaiter(this, void 0, void 0, function* () { const status = (yield this._axiosInstance.put(`/package/tag/${fullName}`, distTag)).status; if (status !== 204) { throw new Error(`Cannot add tag "${distTag.tag.trim().toUpperCase()}"`); } }); } rmDistTag(fullName, distTag) { return __awaiter(this, void 0, void 0, function* () { const status = (yield this._axiosInstance.delete(`/package/tag/${fullName}`, { data: distTag })).status; if (status !== 204) { throw new Error(`Cannot remove tag "${distTag.tag.trim().toLowerCase()}"`); } }); } contents(fullName_1) { return __awaiter(this, arguments, void 0, function* (fullName, version = 'latest') { var _a; const chunks = []; const logProgress = trm_commons_1.Logger.progressbar(`↓ ${fullName} ${version} contents [{bar}] {percentage}% | {value}/{total} bytes`, '>'); try { const response = yield this._axiosInstance.get(`/package/contents/${fullName}`, { params: { version: encodeURIComponent(version), }, responseType: 'stream', }); const totalBytes = Number((_a = response.headers['content-length']) !== null && _a !== void 0 ? _a : 0); let downloadedBytes = 0; if (totalBytes > 0) { logProgress.start(totalBytes, 0); } yield new Promise((resolve, reject) => { response.data.on('data', (chunk) => { chunks.push(chunk); downloadedBytes += chunk.length; if (totalBytes > 0) { logProgress.update(downloadedBytes); } }); response.data.on('end', () => resolve()); response.data.on('error', reject); }); if (totalBytes > 0) { logProgress.stop(); } const buffer = Buffer.concat(chunks); return JSON.parse(buffer.toString('utf-8')); } catch (e) { try { logProgress.stop(); } catch (_b) { } trm_commons_1.Logger.error(e.toString(), true); trm_commons_1.Logger.error(`Failed to fetch contents for ${fullName}: ${e.message}`, true); throw e; } }); } } exports.RegistryV2 = RegistryV2;