diff --git a/src/appstore.js b/src/appstore.js index bb9d55132..5b0b648f4 100644 --- a/src/appstore.js +++ b/src/appstore.js @@ -7,15 +7,60 @@ exports = module.exports = { sendAliveStatus: sendAliveStatus }; -var AppsError = require('./apps.js').AppsError, - assert = require('assert'), - CloudronError = require('./cloudron.js').CloudronError, +var assert = require('assert'), config = require('./config.js'), debug = require('debug')('box:appstore'), + os = require('os'), settings = require('./settings.js'), superagent = require('superagent'), util = require('util'); +function AppstoreError(reason, errorOrMessage) { + assert.strictEqual(typeof reason, 'string'); + assert(errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined'); + + Error.call(this); + Error.captureStackTrace(this, this.constructor); + + this.name = this.constructor.name; + this.reason = reason; + if (typeof errorOrMessage === 'undefined') { + this.message = reason; + } else if (typeof errorOrMessage === 'string') { + this.message = errorOrMessage; + } else { + this.message = 'Internal error'; + this.nestedError = errorOrMessage; + } +} +util.inherits(AppstoreError, Error); +AppstoreError.INTERNAL_ERROR = 'Internal Error'; +AppstoreError.EXTERNAL_ERROR = 'External Error'; +AppstoreError.NOT_FOUND = 'Internal Error'; +AppstoreError.BILLING_REQUIRED = 'Billing Required'; + +var NOOP_CALLBACK = function (error) { if (error) debug(error); }; + +function getAppstoreConfig(callback) { + // Caas Cloudrons do not store appstore credentials in their local database + if (config.provider() === 'caas') { + var url = config.apiServerOrigin() + '/api/v1/exchangeBoxTokenWithUserToken'; + superagent.post(url).query({ token: config.token() }).timeout(30 * 1000).end(function (error, result) { + if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error)); + if (result.statusCode !== 201) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('App unpurchase failed. %s %j', result.status, result.body))); + + callback(null, result.body); + }); + } else { + settings.getAppstoreConfig(function (error, result) { + if (error) return callback(new AppstoreError(AppstoreError.INTERNAL_ERROR, error)); + if (!result.token) return callback(new AppstoreError(AppstoreError.BILLING_REQUIRED)); + + callback(null, result); + }); + } +} + function purchase(appId, appstoreId, callback) { assert.strictEqual(typeof appId, 'string'); assert.strictEqual(typeof appstoreId, 'string'); @@ -23,41 +68,21 @@ function purchase(appId, appstoreId, callback) { if (appstoreId === '') return callback(null); - function purchaseWithAppstoreConfig(appstoreConfig) { - assert.strictEqual(typeof appstoreConfig.userId, 'string'); - assert.strictEqual(typeof appstoreConfig.cloudronId, 'string'); - assert.strictEqual(typeof appstoreConfig.token, 'string'); + getAppstoreConfig(function (error, appstoreConfig) { + if (error) return callback(error); var url = config.apiServerOrigin() + '/api/v1/users/' + appstoreConfig.userId + '/cloudrons/' + appstoreConfig.cloudronId + '/apps/' + appId; var data = { appstoreId: appstoreId }; superagent.post(url).send(data).query({ accessToken: appstoreConfig.token }).timeout(30 * 1000).end(function (error, result) { - if (error && !error.response) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error)); - if (result.statusCode === 404) return callback(new AppsError(AppsError.NOT_FOUND)); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppsError(AppsError.BILLING_REQUIRED)); - if (result.statusCode !== 201 && result.statusCode !== 200) return callback(new AppsError(AppsError.EXTERNAL_ERROR, util.format('App purchase failed. %s %j', result.status, result.body))); + if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error)); + if (result.statusCode === 404) return callback(new AppstoreError(AppstoreError.NOT_FOUND)); + if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.BILLING_REQUIRED)); + if (result.statusCode !== 201 && result.statusCode !== 200) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('App purchase failed. %s %j', result.status, result.body))); callback(null); }); - } - - // Caas Cloudrons do not store appstore credentials in their local database - if (config.provider() === 'caas') { - var url = config.apiServerOrigin() + '/api/v1/exchangeBoxTokenWithUserToken'; - superagent.post(url).query({ token: config.token() }).timeout(30 * 1000).end(function (error, result) { - if (error && !error.response) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error)); - if (result.statusCode !== 201) return callback(new AppsError(AppsError.EXTERNAL_ERROR, util.format('App purchase failed. %s %j', result.status, result.body))); - - purchaseWithAppstoreConfig(result.body); - }); - } else { - settings.getAppstoreConfig(function (error, result) { - if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); - if (!result.token) return callback(new AppsError(AppsError.BILLING_REQUIRED)); - - purchaseWithAppstoreConfig(result); - }); - } + }); } function unpurchase(appId, appstoreId, callback) { @@ -67,85 +92,74 @@ function unpurchase(appId, appstoreId, callback) { if (appstoreId === '') return callback(null); - function unpurchaseWithAppstoreConfig(appstoreConfig) { - assert.strictEqual(typeof appstoreConfig.userId, 'string'); - assert.strictEqual(typeof appstoreConfig.cloudronId, 'string'); - assert.strictEqual(typeof appstoreConfig.token, 'string'); + getAppstoreConfig(function (error, appstoreConfig) { + if (error) return callback(error); var url = config.apiServerOrigin() + '/api/v1/users/' + appstoreConfig.userId + '/cloudrons/' + appstoreConfig.cloudronId + '/apps/' + appId; superagent.get(url).query({ accessToken: appstoreConfig.token }).timeout(30 * 1000).end(function (error, result) { - if (error && !error.response) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error)); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppsError(AppsError.BILLING_REQUIRED)); + if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error)); + if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.BILLING_REQUIRED)); if (result.statusCode === 404) return callback(null); // was never purchased - if (result.statusCode !== 201 && result.statusCode !== 200) return callback(new AppsError(AppsError.EXTERNAL_ERROR, util.format('App purchase failed. %s %j', result.status, result.body))); + if (result.statusCode !== 201 && result.statusCode !== 200) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('App purchase failed. %s %j', result.status, result.body))); superagent.del(url).query({ accessToken: appstoreConfig.token }).timeout(30 * 1000).end(function (error, result) { - if (error && !error.response) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error)); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppsError(AppsError.BILLING_REQUIRED)); - if (result.statusCode !== 204) return callback(new AppsError(AppsError.EXTERNAL_ERROR, util.format('App unpurchase failed. %s %j', result.status, result.body))); + if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error)); + if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.BILLING_REQUIRED)); + if (result.statusCode !== 204) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('App unpurchase failed. %s %j', result.status, result.body))); callback(null); }); }); - } - - // Caas Cloudrons do not store appstore credentials in their local database - if (config.provider() === 'caas') { - var url = config.apiServerOrigin() + '/api/v1/exchangeBoxTokenWithUserToken'; - superagent.post(url).query({ token: config.token() }).timeout(30 * 1000).end(function (error, result) { - if (error && !error.response) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error)); - if (result.statusCode !== 201) return callback(new AppsError(AppsError.EXTERNAL_ERROR, util.format('App unpurchase failed. %s %j', result.status, result.body))); - - unpurchaseWithAppstoreConfig(result.body); - }); - } else { - settings.getAppstoreConfig(function (error, result) { - if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); - if (!result.token) return callback(new AppsError(AppsError.BILLING_REQUIRED)); - - unpurchaseWithAppstoreConfig(result); - }); - } + }); } function sendAliveStatus(data, callback) { + callback = callback || NOOP_CALLBACK; - function sendAliveStatusWithAppstoreConfig(data, appstoreConfig) { - assert.strictEqual(typeof data, 'object'); - assert.strictEqual(typeof appstoreConfig.userId, 'string'); - assert.strictEqual(typeof appstoreConfig.cloudronId, 'string'); - assert.strictEqual(typeof appstoreConfig.token, 'string'); + settings.getAll(function (error, result) { + if (error) return callback(new AppstoreError(AppstoreError.INTERNAL_ERROR, error)); - var url = config.apiServerOrigin() + '/api/v1/users/' + appstoreConfig.userId + '/cloudrons/' + appstoreConfig.cloudronId + '/alive'; - superagent.post(url).send(data).query({ accessToken: appstoreConfig.token }).timeout(30 * 1000).end(function (error, result) { - if (error && !error.response) return callback(new CloudronError(CloudronError.EXTERNAL_ERROR, error)); - if (result.statusCode === 404) return callback(new CloudronError(CloudronError.NOT_FOUND)); - if (result.statusCode !== 201) return callback(new CloudronError(CloudronError.EXTERNAL_ERROR, util.format('Sending alive status failed. %s %j', result.status, result.body))); + var backendSettings = { + dnsConfig: { + provider: result[settings.DNS_CONFIG_KEY].provider, + wildcard: result[settings.DNS_CONFIG_KEY].provider === 'manual' ? result[settings.DNS_CONFIG_KEY].wildcard : undefined + }, + tlsConfig: { + provider: result[settings.TLS_CONFIG_KEY].provider + }, + backupConfig: { + provider: result[settings.BACKUP_CONFIG_KEY].provider + }, + mailConfig: { + enabled: result[settings.MAIL_CONFIG_KEY].enabled + }, + autoupdatePattern: result[settings.AUTOUPDATE_PATTERN_KEY], + timeZone: result[settings.TIME_ZONE_KEY] + }; - callback(null); - }); - } - - // Caas Cloudrons do not store appstore credentials in their local database - if (config.provider() === 'caas') { - var url = config.apiServerOrigin() + '/api/v1/exchangeBoxTokenWithUserToken'; - superagent.post(url).query({ token: config.token() }).timeout(30 * 1000).end(function (error, result) { - if (error && !error.response) return callback(new CloudronError(CloudronError.EXTERNAL_ERROR, error)); - if (result.statusCode !== 201) return callback(new CloudronError(CloudronError.EXTERNAL_ERROR, util.format('Token exchange failed. %s %j', result.status, result.body))); - - sendAliveStatusWithAppstoreConfig(data, result.body); - }); - } else { - settings.getAppstoreConfig(function (error, result) { - if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error)); - - if (!result.token) { - debug('sendAliveStatus: Cloudron not yet registered'); - return callback(null); + var data = { + domain: config.fqdn(), + version: config.version(), + provider: config.provider(), + backendSettings: backendSettings, + machine: { + cpus: os.cpus(), + totalmem: os.totalmem() } + }; - sendAliveStatusWithAppstoreConfig(data, result); + getAppstoreConfig(function (error, appstoreConfig) { + if (error) return callback(error); + + var url = config.apiServerOrigin() + '/api/v1/users/' + appstoreConfig.userId + '/cloudrons/' + appstoreConfig.cloudronId + '/alive'; + superagent.post(url).send(data).query({ accessToken: appstoreConfig.token }).timeout(30 * 1000).end(function (error, result) { + if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error)); + if (result.statusCode === 404) return callback(new AppstoreError(AppstoreError.NOT_FOUND)); + if (result.statusCode !== 201) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('Sending alive status failed. %s %j', result.status, result.body))); + + callback(null); + }); }); - } + }); } diff --git a/src/cloudron.js b/src/cloudron.js index 38d51ae58..cc2f4377e 100644 --- a/src/cloudron.js +++ b/src/cloudron.js @@ -12,7 +12,6 @@ exports = module.exports = { dnsSetup: dnsSetup, sendHeartbeat: sendHeartbeat, - sendAliveStatus: sendAliveStatus, updateToLatest: updateToLatest, reboot: reboot, @@ -32,7 +31,6 @@ exports = module.exports = { }; var apps = require('./apps.js'), - appstore = require('./appstore.js'), assert = require('assert'), async = require('async'), backups = require('./backups.js'), @@ -477,50 +475,6 @@ function sendHeartbeat() { }); } -function sendAliveStatus(callback) { - if (typeof callback !== 'function') { - callback = function (error) { - if (error && error.reason !== CloudronError.INTERNAL_ERROR) debug(error); - else if (error) debug(error); - }; - } - - settings.getAll(function (error, result) { - if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error)); - - var backendSettings = { - dnsConfig: { - provider: result[settings.DNS_CONFIG_KEY].provider, - wildcard: result[settings.DNS_CONFIG_KEY].provider === 'manual' ? result[settings.DNS_CONFIG_KEY].wildcard : undefined - }, - tlsConfig: { - provider: result[settings.TLS_CONFIG_KEY].provider - }, - backupConfig: { - provider: result[settings.BACKUP_CONFIG_KEY].provider - }, - mailConfig: { - enabled: result[settings.MAIL_CONFIG_KEY].enabled - }, - autoupdatePattern: result[settings.AUTOUPDATE_PATTERN_KEY], - timeZone: result[settings.TIME_ZONE_KEY] - }; - - var data = { - domain: config.fqdn(), - version: config.version(), - provider: config.provider(), - backendSettings: backendSettings, - machine: { - cpus: os.cpus(), - totalmem: os.totalmem() - } - }; - - appstore.sendAliveStatus(data, callback); - }); -} - function ensureDkimKey(callback) { var dkimPath = path.join(paths.MAIL_DATA_DIR, 'dkim/' + config.fqdn()); var dkimPrivateKeyFile = path.join(dkimPath, 'private'); diff --git a/src/cron.js b/src/cron.js index 07e1ee8a3..4fe8297fb 100644 --- a/src/cron.js +++ b/src/cron.js @@ -6,6 +6,7 @@ exports = module.exports = { }; var apps = require('./apps.js'), + appstore = require('./appstore.js'), assert = require('assert'), backups = require('./backups.js'), certificates = require('./certificates.js'), @@ -65,7 +66,7 @@ function initialize(callback) { var randomHourMinute = Math.floor(60*Math.random()); gAliveJob = new CronJob({ cronTime: '00 ' + randomHourMinute + ' * * * *', // every hour on a random minute - onTick: cloudron.sendAliveStatus, + onTick: appstore.sendAliveStatus, start: true });