@mytmpvpn/mytmpvpn-client
Version:
MyTmpVpn Client Library
257 lines (256 loc) • 10.8 kB
JavaScript
;
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;