Files
cloudron-box/src/appstore.js

496 lines
23 KiB
JavaScript
Raw Normal View History

'use strict';
exports = module.exports = {
getFeatures: getFeatures,
2019-05-04 11:45:03 -07:00
getApps: getApps,
getApp: getApp,
getAppVersion: getAppVersion,
trackBeginSetup: trackBeginSetup,
trackFinishedSetup: trackFinishedSetup,
2019-05-07 14:16:51 -07:00
registerWithLoginCredentials: registerWithLoginCredentials,
2019-05-05 13:00:45 -07:00
2019-05-06 14:29:56 -07:00
purchaseApp: purchaseApp,
unpurchaseApp: unpurchaseApp,
2017-04-13 00:52:02 -07:00
getUserToken: getUserToken,
2017-06-21 22:17:32 -07:00
getSubscription: getSubscription,
isFreePlan: isFreePlan,
2017-06-21 22:17:32 -07:00
getAppUpdate: getAppUpdate,
getBoxUpdate: getBoxUpdate,
2019-10-24 17:28:59 -07:00
createTicket: createTicket
};
var apps = require('./apps.js'),
assert = require('assert'),
async = require('async'),
2019-10-24 17:28:59 -07:00
BoxError = require('./boxerror.js'),
constants = require('./constants.js'),
2017-04-13 00:52:02 -07:00
debug = require('debug')('box:appstore'),
eventlog = require('./eventlog.js'),
paths = require('./paths.js'),
2018-01-25 09:35:06 -08:00
safe = require('safetydance'),
semver = require('semver'),
settings = require('./settings.js'),
superagent = require('superagent'),
util = require('util');
2020-02-28 15:18:16 +01:00
// These are the default options and will be adjusted once a subscription state is obtained
// Keep in sync with appstore/routes/cloudrons.js
let gFeatures = {
userMaxCount: 5,
domainMaxCount: 1,
externalLdap: false,
privateDockerRegistry: false,
branding: false,
support: false,
2020-07-16 18:14:25 +02:00
directoryConfig: false,
mailboxMaxCount: 5,
emailPremium: false
};
// attempt to load feature cache in case appstore would be down
let tmp = safe.JSON.parse(safe.fs.readFileSync(paths.FEATURES_INFO_FILE, 'utf8'));
2020-02-20 15:45:34 +01:00
if (tmp) gFeatures = tmp;
function getFeatures() {
return gFeatures;
}
2020-02-05 11:58:10 -08:00
function isAppAllowed(appstoreId, listingConfig) {
assert.strictEqual(typeof listingConfig, 'object');
assert.strictEqual(typeof appstoreId, 'string');
if (listingConfig.blacklist && listingConfig.blacklist.includes(appstoreId)) return false;
if (listingConfig.whitelist) return listingConfig.whitelist.includes(appstoreId);
return true;
}
function getCloudronToken(callback) {
2019-04-30 23:35:49 -07:00
assert.strictEqual(typeof callback, 'function');
settings.getCloudronToken(function (error, token) {
2019-10-24 17:28:59 -07:00
if (error) return callback(error);
if (!token) return callback(new BoxError(BoxError.LICENSE_ERROR, 'Missing token'));
2019-04-30 23:35:49 -07:00
callback(null, token);
});
}
function login(email, password, totpToken, callback) {
assert.strictEqual(typeof email, 'string');
assert.strictEqual(typeof password, 'string');
assert.strictEqual(typeof totpToken, 'string');
assert.strictEqual(typeof callback, 'function');
var data = {
email: email,
password: password,
totpToken: totpToken
};
const url = settings.apiServerOrigin() + '/api/v1/login';
superagent.post(url).send(data).timeout(30 * 1000).end(function (error, result) {
2019-10-24 17:28:59 -07:00
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, `login status code: ${result.statusCode}`));
callback(null, result.body); // { userId, accessToken }
});
}
function registerUser(email, password, callback) {
assert.strictEqual(typeof email, 'string');
assert.strictEqual(typeof password, 'string');
assert.strictEqual(typeof callback, 'function');
var data = {
email: email,
password: password,
};
const url = settings.apiServerOrigin() + '/api/v1/register_user';
superagent.post(url).send(data).timeout(30 * 1000).end(function (error, result) {
2019-10-24 17:28:59 -07:00
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
2020-05-02 18:30:44 -07:00
if (result.statusCode === 409) return callback(new BoxError(BoxError.ALREADY_EXISTS, error.message));
2019-10-24 17:28:59 -07:00
if (result.statusCode !== 201) return callback(new BoxError(BoxError.EXTERNAL_ERROR, `register status code: ${result.statusCode}`));
callback(null);
});
}
function getUserToken(callback) {
assert.strictEqual(typeof callback, 'function');
if (settings.isDemo()) return callback(new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'));
getCloudronToken(function (error, token) {
if (error) return callback(error);
const url = `${settings.apiServerOrigin()}/api/v1/user_token`;
superagent.post(url).send({}).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) {
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
if (result.statusCode !== 201) return callback(new BoxError(BoxError.EXTERNAL_ERROR, `getUserToken status code: ${result.status}`));
callback(null, result.body.accessToken);
});
});
}
2017-06-21 22:17:32 -07:00
function getSubscription(callback) {
assert.strictEqual(typeof callback, 'function');
2019-05-03 20:17:40 -07:00
getCloudronToken(function (error, token) {
2017-06-21 22:17:32 -07:00
if (error) return callback(error);
const url = settings.apiServerOrigin() + '/api/v1/subscription';
2019-05-03 20:17:40 -07:00
superagent.get(url).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) {
2019-10-24 17:28:59 -07:00
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR));
if (result.statusCode === 502) return callback(new BoxError(BoxError.EXTERNAL_ERROR, `Stripe error: ${error.message}`));
if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, `Unknown error: ${error.message}`));
2017-06-21 22:17:32 -07:00
2020-02-20 15:45:34 +01:00
// update the features cache
gFeatures = result.body.features;
safe.fs.writeFileSync(paths.FEATURES_INFO_FILE, JSON.stringify(gFeatures), 'utf8');
callback(null, result.body);
2017-06-21 22:17:32 -07:00
});
});
}
function isFreePlan(subscription) {
return !subscription || subscription.plan.id === 'free';
}
// See app.js install it will create a db record first but remove it again if appstore purchase fails
2019-05-06 14:29:56 -07:00
function purchaseApp(data, callback) {
assert.strictEqual(typeof data, 'object'); // { appstoreId, manifestId, appId }
assert(data.appstoreId || data.manifestId);
assert.strictEqual(typeof data.appId, 'string');
2017-04-13 01:07:07 -07:00
assert.strictEqual(typeof callback, 'function');
getCloudronToken(function (error, token) {
2017-04-13 01:07:07 -07:00
if (error) return callback(error);
const url = `${settings.apiServerOrigin()}/api/v1/cloudronapps`;
superagent.post(url).send(data).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) {
2019-10-24 17:28:59 -07:00
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
if (result.statusCode === 404) return callback(new BoxError(BoxError.NOT_FOUND)); // appstoreId does not exist
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
if (result.statusCode === 402) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
// 200 if already purchased, 201 is newly purchased
2019-10-24 17:28:59 -07:00
if (result.statusCode !== 201 && result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('App purchase failed. %s %j', result.status, result.body)));
2017-04-13 01:07:07 -07:00
callback(null);
2017-04-13 01:07:07 -07:00
});
});
}
2019-05-06 14:29:56 -07:00
function unpurchaseApp(appId, data, callback) {
assert.strictEqual(typeof appId, 'string');
2019-04-30 23:35:49 -07:00
assert.strictEqual(typeof data, 'object'); // { appstoreId, manifestId }
assert(data.appstoreId || data.manifestId);
assert.strictEqual(typeof callback, 'function');
getCloudronToken(function (error, token) {
2017-04-13 01:07:07 -07:00
if (error) return callback(error);
const url = `${settings.apiServerOrigin()}/api/v1/cloudronapps/${appId}`;
superagent.get(url).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) {
2019-10-24 17:28:59 -07:00
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
if (result.statusCode === 404) return callback(null); // was never purchased
2019-10-24 17:28:59 -07:00
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
if (result.statusCode !== 201 && result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('App unpurchase failed. %s %j', result.status, result.body)));
superagent.del(url).send(data).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) {
2019-10-24 17:28:59 -07:00
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error));
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
if (result.statusCode !== 204) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('App unpurchase failed. %s %j', result.status, result.body)));
callback(null);
});
});
2017-04-13 01:07:07 -07:00
});
}
2017-04-13 00:52:02 -07:00
function getBoxUpdate(options, callback) {
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
getCloudronToken(function (error, token) {
if (error) return callback(error);
const url = `${settings.apiServerOrigin()}/api/v1/boxupdate`;
const query = {
accessToken: token,
boxVersion: constants.VERSION,
automatic: options.automatic
};
superagent.get(url).query(query).timeout(10 * 1000).end(function (error, result) {
2019-10-24 17:28:59 -07:00
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
if (result.statusCode === 204) return callback(null); // no update
2019-10-24 17:28:59 -07:00
if (result.statusCode !== 200 || !result.body) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text)));
2018-01-25 09:35:06 -08:00
var updateInfo = result.body;
if (!semver.valid(updateInfo.version) || semver.gt(constants.VERSION, updateInfo.version)) {
2019-10-24 17:28:59 -07:00
return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Invalid update version: %s %s', result.statusCode, result.text)));
2018-01-25 09:35:06 -08:00
}
2019-02-20 16:18:47 -08:00
// updateInfo: { version, changelog, sourceTarballUrl, sourceTarballSigUrl, boxVersionsUrl, boxVersionsSigUrl }
2019-10-24 17:28:59 -07:00
if (!updateInfo.version || typeof updateInfo.version !== 'string') return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response (bad version): %s %s', result.statusCode, result.text)));
if (!updateInfo.changelog || !Array.isArray(updateInfo.changelog)) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response (bad version): %s %s', result.statusCode, result.text)));
if (!updateInfo.sourceTarballUrl || typeof updateInfo.sourceTarballUrl !== 'string') return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response (bad sourceTarballUrl): %s %s', result.statusCode, result.text)));
if (!updateInfo.sourceTarballSigUrl || typeof updateInfo.sourceTarballSigUrl !== 'string') return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response (bad sourceTarballSigUrl): %s %s', result.statusCode, result.text)));
if (!updateInfo.boxVersionsUrl || typeof updateInfo.boxVersionsUrl !== 'string') return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response (bad boxVersionsUrl): %s %s', result.statusCode, result.text)));
if (!updateInfo.boxVersionsSigUrl || typeof updateInfo.boxVersionsSigUrl !== 'string') return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response (bad boxVersionsSigUrl): %s %s', result.statusCode, result.text)));
2019-02-20 16:18:47 -08:00
2018-01-25 09:35:06 -08:00
callback(null, updateInfo);
});
});
}
function getAppUpdate(app, options, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
getCloudronToken(function (error, token) {
if (error) return callback(error);
const url = `${settings.apiServerOrigin()}/api/v1/appupdate`;
const query = {
accessToken: token,
boxVersion: constants.VERSION,
appId: app.appStoreId,
appVersion: app.manifest.version,
automatic: options.automatic
};
superagent.get(url).query(query).timeout(10 * 1000).end(function (error, result) {
2019-10-24 17:28:59 -07:00
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error));
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
if (result.statusCode === 204) return callback(null); // no update
2019-10-24 17:28:59 -07:00
if (result.statusCode !== 200 || !result.body) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text)));
2018-01-25 09:35:06 -08:00
const updateInfo = result.body;
// for the appstore, x.y.z is the same as x.y.z-0 but in semver, x.y.z > x.y.z-0
const curAppVersion = semver.prerelease(app.manifest.version) ? app.manifest.version : `${app.manifest.version}-0`;
2018-01-25 09:35:06 -08:00
// do some sanity checks
if (!safe.query(updateInfo, 'manifest.version') || semver.gt(curAppVersion, safe.query(updateInfo, 'manifest.version'))) {
debug('Skipping malformed update of app %s version: %s. got %j', app.id, curAppVersion, updateInfo);
2019-10-24 17:28:59 -07:00
return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Malformed update: %s %s', result.statusCode, result.text)));
2018-01-25 09:35:06 -08:00
}
updateInfo.unstable = !!updateInfo.unstable;
// { id, creationDate, manifest, unstable }
2018-01-25 09:35:06 -08:00
callback(null, updateInfo);
});
});
}
2019-05-07 14:25:27 -07:00
function registerCloudron(data, callback) {
assert.strictEqual(typeof data, 'object');
2019-02-25 17:29:42 -08:00
assert.strictEqual(typeof callback, 'function');
const url = `${settings.apiServerOrigin()}/api/v1/register_cloudron`;
2019-02-25 17:29:42 -08:00
2019-05-07 14:25:27 -07:00
superagent.post(url).send(data).timeout(30 * 1000).end(function (error, result) {
2019-10-24 17:28:59 -07:00
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
if (result.statusCode !== 201) return callback(new BoxError(BoxError.EXTERNAL_ERROR, `Unable to register cloudron: ${result.statusCode} ${error.message}`));
2019-02-25 17:29:42 -08:00
// cloudronId, token, licenseKey
2019-10-24 17:28:59 -07:00
if (!result.body.cloudronId) return callback(new BoxError(BoxError.EXTERNAL_ERROR, 'Invalid response - no cloudron id'));
if (!result.body.cloudronToken) return callback(new BoxError(BoxError.EXTERNAL_ERROR, 'Invalid response - no token'));
if (!result.body.licenseKey) return callback(new BoxError(BoxError.EXTERNAL_ERROR, 'Invalid response - no license'));
async.series([
settings.setCloudronId.bind(null, result.body.cloudronId),
settings.setCloudronToken.bind(null, result.body.cloudronToken),
settings.setLicenseKey.bind(null, result.body.licenseKey),
], function (error) {
2019-10-24 17:28:59 -07:00
if (error) return callback(error);
debug(`registerCloudron: Cloudron registered with id ${result.body.cloudronId}`);
callback();
});
});
}
// This works without a Cloudron token as this Cloudron was not yet registered
2019-12-11 14:02:40 +01:00
let gBeginSetupAlreadyTracked = false;
2020-06-26 09:55:39 -07:00
function trackBeginSetup() {
2019-12-11 14:02:40 +01:00
// avoid browser reload double tracking, not perfect since box might restart, but covers most cases and is simple
if (gBeginSetupAlreadyTracked) return;
gBeginSetupAlreadyTracked = true;
const url = `${settings.apiServerOrigin()}/api/v1/helper/setup_begin`;
2020-06-26 09:55:39 -07:00
superagent.post(url).send({}).timeout(30 * 1000).end(function (error, result) {
if (error && !error.response) return debug(`trackBeginSetup: ${error.message}`);
if (result.statusCode !== 200) return debug(`trackBeginSetup: ${result.statusCode} ${error.message}`);
});
}
// This works without a Cloudron token as this Cloudron was not yet registered
function trackFinishedSetup(domain) {
assert.strictEqual(typeof domain, 'string');
const url = `${settings.apiServerOrigin()}/api/v1/helper/setup_finished`;
superagent.post(url).send({ domain }).timeout(30 * 1000).end(function (error, result) {
if (error && !error.response) return debug(`trackFinishedSetup: ${error.message}`);
if (result.statusCode !== 200) return debug(`trackFinishedSetup: ${result.statusCode} ${error.message}`);
});
}
2019-05-07 14:16:51 -07:00
function registerWithLoginCredentials(options, callback) {
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
function maybeSignup(done) {
if (!options.signup) return done();
registerUser(options.email, options.password, done);
}
2019-02-25 17:29:42 -08:00
2019-05-07 14:15:35 -07:00
getCloudronToken(function (error, token) {
2020-05-02 18:30:44 -07:00
if (token) return callback(new BoxError(BoxError.CONFLICT, 'Cloudron is already registered'));
maybeSignup(function (error) {
if (error) return callback(error);
2019-02-25 17:29:42 -08:00
login(options.email, options.password, options.totpToken || '', function (error, result) {
if (error) return callback(error);
registerCloudron({ domain: settings.adminDomain(), accessToken: result.accessToken, version: constants.VERSION, purpose: options.purpose || '' }, callback);
});
});
2019-02-25 17:29:42 -08:00
});
}
2019-12-16 14:06:55 -08:00
function createTicket(info, auditSource, callback) {
2017-11-14 20:34:25 -08:00
assert.strictEqual(typeof info, 'object');
assert.strictEqual(typeof info.email, 'string');
assert.strictEqual(typeof info.displayName, 'string');
assert.strictEqual(typeof info.type, 'string');
assert.strictEqual(typeof info.subject, 'string');
assert.strictEqual(typeof info.description, 'string');
2019-12-16 14:06:55 -08:00
assert.strictEqual(typeof auditSource, 'object');
2017-11-14 20:34:25 -08:00
assert.strictEqual(typeof callback, 'function');
function collectAppInfoIfNeeded(callback) {
if (!info.appId) return callback();
apps.get(info.appId, callback);
}
getCloudronToken(function (error, token) {
2019-04-30 23:35:49 -07:00
if (error) return callback(error);
2017-11-14 20:34:25 -08:00
collectAppInfoIfNeeded(function (error, result) {
if (error) return callback(error);
if (result) info.app = result;
2017-11-14 20:34:25 -08:00
2020-02-04 13:07:26 -08:00
info.supportEmail = constants.SUPPORT_EMAIL; // destination address for tickets
2017-11-14 20:34:25 -08:00
superagent.post(`${settings.apiServerOrigin()}/api/v1/ticket`).query({ accessToken: token }).send(info).timeout(10 * 1000).end(function (error, result) {
2019-10-24 17:28:59 -07:00
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
if (result.statusCode !== 201) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text)));
2019-12-16 14:06:55 -08:00
eventlog.add(eventlog.ACTION_SUPPORT_TICKET, auditSource, info);
2020-02-05 14:30:56 -08:00
callback(null, { message: `An email for sent to ${constants.SUPPORT_EMAIL}. We will get back shortly!` });
});
2017-11-14 20:34:25 -08:00
});
});
}
2019-05-04 11:45:03 -07:00
function getApps(callback) {
assert.strictEqual(typeof callback, 'function');
getCloudronToken(function (error, token) {
if (error) return callback(error);
settings.getUnstableAppsConfig(function (error, unstable) {
2019-10-24 17:28:59 -07:00
if (error) return callback(error);
const url = `${settings.apiServerOrigin()}/api/v1/apps`;
superagent.get(url).query({ accessToken: token, boxVersion: constants.VERSION, unstable: unstable }).timeout(10 * 1000).end(function (error, result) {
2019-10-24 17:28:59 -07:00
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
if (result.statusCode === 403 || result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('App listing failed. %s %j', result.status, result.body)));
if (!result.body.apps) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text)));
2019-05-04 11:45:03 -07:00
2020-02-05 11:58:10 -08:00
settings.getAppstoreListingConfig(function (error, listingConfig) {
if (error) return callback(error);
const filteredApps = result.body.apps.filter(app => isAppAllowed(app.id, listingConfig));
callback(null, filteredApps);
});
2019-05-04 11:45:03 -07:00
});
});
});
}
function getAppVersion(appId, version, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof version, 'string');
assert.strictEqual(typeof callback, 'function');
2020-02-05 11:58:10 -08:00
settings.getAppstoreListingConfig(function (error, listingConfig) {
2019-05-04 11:45:03 -07:00
if (error) return callback(error);
2020-02-05 11:58:10 -08:00
if (!isAppAllowed(appId, listingConfig)) return callback(new BoxError(BoxError.FEATURE_DISABLED));
2019-05-04 11:45:03 -07:00
2020-02-05 11:58:10 -08:00
getCloudronToken(function (error, token) {
if (error) return callback(error);
let url = `${settings.apiServerOrigin()}/api/v1/apps/${appId}`;
if (version !== 'latest') url += `/versions/${version}`;
2019-05-04 11:45:03 -07:00
2020-02-05 11:58:10 -08:00
superagent.get(url).query({ accessToken: token }).timeout(10 * 1000).end(function (error, result) {
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
if (result.statusCode === 403 || result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
if (result.statusCode === 404) return callback(new BoxError(BoxError.NOT_FOUND));
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('App fetch failed. %s %j', result.status, result.body)));
callback(null, result.body);
});
2019-05-04 11:45:03 -07:00
});
});
}
function getApp(appId, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof callback, 'function');
getAppVersion(appId, 'latest', callback);
}