UNPKG

@mytmpvpn/mytmpvpn-client

Version:

MyTmpVpn Client Library

257 lines (256 loc) 10.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MyTmpVpnClientImpl = exports.MyTmpVpnClient = void 0; const axios_1 = require("axios"); const log = require("loglevel"); const utils = require("@mytmpvpn/mytmpvpn-common/utils"); const error = require("@mytmpvpn/mytmpvpn-common/errors"); const vpn_1 = require("@mytmpvpn/mytmpvpn-common/models/vpn"); const USER_REST_PATH = '/v1/user'; const VPN_REST_PATH = '/v1/vpns'; const REGIONS_REST_PATH = '/v1/regions'; const REGIONSDETAILED_REST_PATH = '/v1/regionsDetailed'; const PEANUTS_REST_PATH = '/v1/peanuts'; const PEANUTS_PACKS_REST_PATH = `${PEANUTS_REST_PATH}/packs`; function checkVpnId(vpnId) { if (!vpnId) { throw new error.MyTmpVpnError(`Undefined vpnId: ${vpnId}`); } } // Function to decode JWT token function decodeJWT(token) { try { const base64Url = token.split('.')[1]; const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); const jsonPayload = decodeURIComponent(atob(base64) .split('') .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)) .join('')); return JSON.parse(jsonPayload); } catch (error) { log.error('Error decoding JWT token:', error); return null; } } class MyTmpVpnClient { getUser() { return this.user; } getSession() { return this.session; } setUserSession(user, session) { this.user = user; this.session = session; } async listVpns(pageSize = 1000) { let response; let vpns = []; const pagingParams = { pageSize, nextPageToken: undefined }; do { response = await this.listVpnsPaginated(pagingParams); log.debug(`List response: ${JSON.stringify(response)}`); vpns = vpns.concat(response.vpns); pagingParams.nextPageToken = response.nextPageToken; } while (response.nextPageToken); return vpns; } async createVpnSync(region, config, timeout_s = 300) { const vpn = await this.createVpn(region, config); return await this.waitUntilVpnStateIs(vpn.vpnId, vpn_1.VpnState.Running, timeout_s); } /* vpn\state Failed | Creating | Created | Provisioning | Running | Paused | Deprovisioning | Deleted Failed return return return return return return return return Creating wait return wait wait wait wait wait wait Created wait return return wait wait wait wait wait Provisioning wait return return return wait wait wait wait Paused wait return return return wait return wait wait Running wait return return return return wait wait wait Deprovisioning wait return return return return return return wait Deleted wait return return return return return return return */ async waitUntilVpnStateIs(vpnId, state, timeoutInSeconds = 300) { checkVpnId(vpnId); const exitAt = new Date(); exitAt.setSeconds(exitAt.getSeconds() + timeoutInSeconds); while (true) { const now = new Date(); if (now.getTime() >= exitAt.getTime()) throw new error.MyTmpVpnError(`Timeout ${timeoutInSeconds} expires!`); const { vpn } = await this.getVpn(vpnId); if (vpn.state === null || vpn.state === undefined) throw new error.MyTmpVpnError(`No state found in vpn: ${JSON.stringify(vpn)}`); if (vpn.state === vpn_1.VpnState.Failed) return vpn; if (state === vpn_1.VpnState.Created) return vpn; if (state === vpn.state) return vpn; if (vpn.state === vpn_1.VpnState.Paused && ((0, vpn_1.toRank)(state) < (0, vpn_1.toRank)(vpn_1.VpnState.Running))) return vpn; if ((0, vpn_1.toRank)(vpn.state) > (0, vpn_1.toRank)(state)) { log.debug(`rank(${JSON.stringify(vpn)}) == ${(0, vpn_1.toRank)(vpn.state)} > ${(0, vpn_1.toRank)(state)} == rank(${state})`); return vpn; } await utils.sleep(10000); } } async waitAndDeleteVpn(vpnId) { const response = await this.getVpn(vpnId); if (response.vpn.state == vpn_1.VpnState.Deleted) { log.debug(`${vpnId}: is already in Deleted state, returning`); return; } if ((0, vpn_1.toRank)(response.vpn.state) <= (0, vpn_1.toRank)(vpn_1.VpnState.Running)) { log.debug(`${vpnId}: waiting to reach Running state before deleting`); await this.waitUntilVpnStateIs(vpnId, vpn_1.VpnState.Running); } return await this.deleteVpn(vpnId); } async waitAndDeleteVpnSync(vpnId, timeout_s = 300) { await this.waitAndDeleteVpn(vpnId); return await this.waitUntilVpnStateIs(vpnId, vpn_1.VpnState.Deleted); } async waitAndDeleteAll(vpnIds, timeout_s = vpnIds.length * 300) { const promises = vpnIds.map(async (vpnId) => { return this.waitAndDeleteVpn(vpnId); }); return Promise.all(promises); } async waitAndDeleteAllSync(vpnIds, timeout_s = vpnIds.length * 300) { const promises = vpnIds.map(async (vpnId) => { return await this.waitAndDeleteVpnSync(vpnId); }); return Promise.all(promises); } } exports.MyTmpVpnClient = MyTmpVpnClient; class MyTmpVpnClientImpl extends MyTmpVpnClient { constructor(apiUrl) { super(); this.ax = axios_1.default.create({ baseURL: apiUrl }); } getDefaultAuthorizationHeaders() { const session = this.getSession(); return { Accept: 'application/json', Authorization: session != null ? `${session.getIdToken().getJwtToken()}` : 'Unauthenticated' }; } getDefaultNonAuthorizationHeaders() { return { Accept: 'application/json' }; } async getVpnConfigLimits() { // Get the VpnConfigLimits from the JWT Token itself const session = this.getSession(); if (!session) { throw new error.MyTmpVpnError('No session found'); } const idToken = session.getIdToken().getJwtToken(); // Decode the token to access the claims const decodedToken = decodeJWT(idToken); if (!decodedToken) { throw new error.MyTmpVpnError('Failed to decode token'); } log.debug('Decoded token:', decodedToken); // Parse the custom:config claim const customConfigStr = decodedToken['custom:config']; if (customConfigStr) { log.debug('Found custom:config in token:', customConfigStr); try { const customConfig = JSON.parse(customConfigStr); return customConfig.vpnConfigLimits; } catch (e) { throw new error.MyTmpVpnError(`Failed to parse decodedToken: ${JSON.stringify(decodedToken)}; error: ${e}`); } } // Compiler is stupid: using logAndThrow() does not work, the compiler does not see // it will throw right away, so we are "forced" to use this instead throw new error.MyTmpVpnError(`No custom:config found in token claims: ${decodedToken}`); } async listVpnsPaginated(pagingParams) { const { data } = await this.ax.get(`${VPN_REST_PATH}`, { headers: this.getDefaultAuthorizationHeaders(), params: pagingParams, }); // log.debug(JSON.stringify(data, null, 4)); // 👇️ "response status is: 200" // log.error('response status is: ', data); return data; } async deleteVpn(vpnId) { const { data, status } = await this.ax.delete(`${VPN_REST_PATH}/${vpnId}`, { headers: this.getDefaultAuthorizationHeaders() }); if (status !== 202 && status !== 204) { throw new error.MyTmpVpnError(`Error during deletion of vpn: ${vpnId}: ${JSON.stringify(data)}`); } return await this.getVpn(vpnId); } async createVpn(region, config) { const { data, status } = await this.ax.post(`${VPN_REST_PATH}/`, { region, config }, { headers: this.getDefaultAuthorizationHeaders() }); if (status !== 202) { throw new error.MyTmpVpnError(`Error during creation of vpn in: ${region}: ${JSON.stringify(data)}`); } const res = await this.getVpn(data.vpn.id); // There is no need to return VpnMetrics here as creation takes time return res.vpn; } async getVpn(vpnId) { checkVpnId(vpnId); const { data } = await this.ax.get(`${VPN_REST_PATH}/${vpnId}`, { headers: this.getDefaultAuthorizationHeaders() }); return data; } async getVpnConfig(vpnId) { checkVpnId(vpnId); const { data, status } = await this.ax.get(`${VPN_REST_PATH}/${vpnId}/config`, { headers: this.getDefaultAuthorizationHeaders() }); log.debug(`Returning ${JSON.stringify(data)}`); return data; } async getVpnQrConfig(vpnId) { checkVpnId(vpnId); const { data, status } = await this.ax.get(`${VPN_REST_PATH}/${vpnId}/qrconfig`, { headers: this.getDefaultAuthorizationHeaders() }); log.debug(`Returning ${JSON.stringify(data)}`); return data; } async listRegions() { const { data } = await this.ax.get(`${REGIONS_REST_PATH}/`, { headers: this.getDefaultNonAuthorizationHeaders(), }); return data; } async listRegionsDetailed() { const { data } = await this.ax.get(`${REGIONSDETAILED_REST_PATH}/`, { headers: this.getDefaultNonAuthorizationHeaders(), }); return data; } async listPeanutsPacks() { const { data } = await this.ax.get(`${PEANUTS_PACKS_REST_PATH}/`, { headers: this.getDefaultNonAuthorizationHeaders() }); return data; } async getPeanutsBalance() { const { data } = await this.ax.get(`${PEANUTS_REST_PATH}`, { headers: this.getDefaultAuthorizationHeaders() }); return data; } } exports.MyTmpVpnClientImpl = MyTmpVpnClientImpl;