'use strict'; /* global $:false */ /* global angular:false */ /* global EventSource:false */ /* global asyncForEach:false */ angular.module('Application').service('Client', ['$http', '$interval', 'md5', 'Notification', function ($http, $interval, md5, Notification) { var client = null; // variable available only here to avoid this._property pattern var token = null; // Keep this in sync with docs and constants.js, docker.js var DEFAULT_MEMORY_LIMIT = 1024 * 1024 * 256; function ClientError(statusCode, messageOrObject) { Error.call(this); this.name = this.constructor.name; this.statusCode = statusCode; if (messageOrObject === null || typeof messageOrObject === 'undefined') { this.message = 'Empty message or object'; } else if (typeof messageOrObject === 'string') { this.message = messageOrObject; } else if (messageOrObject.message) { this.message = messageOrObject.message; } else { this.message = JSON.stringify(messageOrObject); } } function defaultErrorHandler(callback) { return function (data, status) { // handle request killed by browser (eg. cors issue) if (data === null && status === -1) { client.offline = true; return callback(new ClientError('Request cancelled by browser')); } // 401 is when the token is invalid. 403 can happen when the token is valid but missing scopes (like when user became admin) // re-login will make the code get a new token if (status === 401 || status === 403) return client.login(); if (status >= 500) { if (!client.offline) client.error(data); client.offline = true; return callback(new ClientError(status, data)); } var obj = data; try { obj = JSON.parse(data); } catch (e) {} callback(new ClientError(status, obj)); }; } function defaultSuccessHandler(callback) { return function (data, status) { client.offline = false; return callback(null, data, status); }; } // XHR wrapper to set the auth header function get(url, config, callback) { if (arguments.length !== 3) { console.error('GET', arguments); throw('Wrong number of arguments'); } config = config || {}; config.headers = config.headers || {}; config.headers.Authorization = 'Bearer ' + token; return $http.get(client.apiOrigin + url, config) .success(defaultSuccessHandler(callback)) .error(defaultErrorHandler(callback)); } function head(url, config, callback) { if (arguments.length !== 3) { console.error('HEAD', arguments); throw('Wrong number of arguments'); } config = config || {}; config.headers = config.headers || {}; config.headers.Authorization = 'Bearer ' + token; return $http.head(client.apiOrigin + url, config) .success(defaultSuccessHandler(callback)) .error(defaultErrorHandler(callback)); } function post(url, data, config, callback) { if (arguments.length !== 4) { console.error('POST', arguments); throw('Wrong number of arguments'); } data = data || {}; config = config || {}; config.headers = config.headers || {}; config.headers.Authorization = 'Bearer ' + token; return $http.post(client.apiOrigin + url, data, config) .success(defaultSuccessHandler(callback)) .error(defaultErrorHandler(callback)); } function put(url, data, config, callback) { if (arguments.length !== 4) { console.error('PUT', arguments); throw('Wrong number of arguments'); } data = data || {}; config = config || {}; config.headers = config.headers || {}; config.headers.Authorization = 'Bearer ' + token; return $http.put(client.apiOrigin + url, data, config) .success(defaultSuccessHandler(callback)) .error(defaultErrorHandler(callback)); } function del(url, config, callback) { if (arguments.length !== 3) { console.error('DEL', arguments); throw('Wrong number of arguments'); } config = config || {}; config.headers = config.headers || {}; config.headers.Authorization = 'Bearer ' + token; return $http.delete(client.apiOrigin + url, config) .success(defaultSuccessHandler(callback)) .error(defaultErrorHandler(callback)); } function Client() { this.offline = false; this._ready = false; this._configListener = []; this._readyListener = []; this._userInfo = { id: null, username: null, email: null, twoFactorAuthenticationEnabled: false }; this._config = { apiServerOrigin: null, webServerOrigin: null, fqdn: null, ip: null, revision: null, update: { box: null, apps: null }, progress: {}, region: null, size: null, memory: 0 }; this._installedApps = []; this._installedAppsById = {}; this._appTags = []; this._clientId = '<%= oauth.clientId %>'; this._clientSecret = '<%= oauth.clientSecret %>'; // window.location fallback for websocket connections which do not have relative uris this.apiOrigin = '<%= oauth.apiOrigin %>' || window.location.origin; this.avatar = ''; this._appstoreAppCache = []; this.resetAvatar(); this.setToken(localStorage.token); } Client.prototype.error = function (error) { var message = ''; if (typeof error === 'object') { message = error.message || error; } else { message = error; } Notification.error({ title: 'Cloudron Error', message: message }); }; Client.prototype.clearNotifications = function () { Notification.clearAll(); }; /* If `action` is a non-empty string, it will be treated as a url, if it is a function, that function will be exectued on click */ Client.prototype.notify = function (title, message, persistent, type, action) { var options = { title: title, message: message}; if (persistent) options.delay = 'never'; // any non Number means never timeout if (action) { options.onClick = function (/* params */) { // if action is a string, we assume it is a link if (typeof action === 'string' && action !== '') window.location = action; else if (typeof action === 'function') action(); else console.warn('Notification action is not supported.', action); }; } if (type === 'error') Notification.error(options); else if (type === 'success') Notification.success(options); else if (type === 'info') Notification.info(options); else if (type === 'warning') Notification.warning(options); else throw('Invalid notification type "' + type + '"'); }; Client.prototype.setReady = function () { if (this._ready) return; this._ready = true; this._readyListener.forEach(function (callback) { callback(); }); // clear the listeners, we only callback once! this._readyListener = []; }; Client.prototype.onReady = function (callback) { if (this._ready) callback(); else this._readyListener.push(callback); }; Client.prototype.onConfig = function (callback) { this._configListener.push(callback); if (this._config && this._config.apiServerOrigin) callback(this._config); }; Client.prototype.resetAvatar = function () { this.avatar = this.apiOrigin + '/api/v1/cloudron/avatar?' + String(Math.random()).slice(2); var favicon = $('#favicon'); if (favicon) favicon.attr('href', this.avatar); }; Client.prototype.setUserInfo = function (userInfo) { // In order to keep the angular bindings alive, set each property individually this._userInfo.id = userInfo.id; this._userInfo.username = userInfo.username; this._userInfo.email = userInfo.email; this._userInfo.fallbackEmail = userInfo.fallbackEmail; this._userInfo.displayName = userInfo.displayName; this._userInfo.twoFactorAuthenticationEnabled = userInfo.twoFactorAuthenticationEnabled; this._userInfo.admin = userInfo.admin; this._userInfo.gravatar = 'https://www.gravatar.com/avatar/' + md5.createHash(userInfo.email) + '.jpg?s=24&d=mm'; this._userInfo.gravatarHuge = 'https://www.gravatar.com/avatar/' + md5.createHash(userInfo.email) + '.jpg?s=128&d=mm'; }; Client.prototype.setConfig = function (config) { var that = this; angular.copy(config, this._config); this._configListener.forEach(function (callback) { callback(that._config); }); }; Client.prototype.getInstalledApps = function () { return this._installedApps; }; Client.prototype.getAppTags = function () { return this._appTags; }; Client.prototype.getUserInfo = function () { return this._userInfo; }; Client.prototype.getConfig = function () { return this._config; }; Client.prototype.setToken = function (accessToken) { if (!accessToken) localStorage.removeItem('token'); else localStorage.token = accessToken; // set the token closure token = accessToken; }; Client.prototype.getToken = function () { return token; }; Client.prototype.makeURL = function (url) { if (url.indexOf('?') === -1) { return this.apiOrigin + url + '?access_token=' + token; } else { return this.apiOrigin + url + '&access_token=' + token; } }; /* * Rest API wrappers */ Client.prototype.config = function (callback) { get('/api/v1/config', null, function (error, data, status) { if (error) return callback(error); if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.userInfo = function (callback) { get('/api/v1/profile', null, function (error, data, status) { if (error) return callback(error); if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.changeCloudronAvatar = function (avatarFile, callback) { var fd = new FormData(); fd.append('avatar', avatarFile); var config = { headers: { 'Content-Type': undefined }, transformRequest: angular.identity }; post('/api/v1/settings/cloudron_avatar', fd, config, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.changeCloudronName = function (name, callback) { var data = { name: name }; post('/api/v1/settings/cloudron_name', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.installApp = function (id, manifest, title, config, callback) { var that = this; var data = { appStoreId: id + '@' + manifest.version, location: config.location, domain: config.domain, portBindings: config.portBindings, accessRestriction: config.accessRestriction, cert: config.cert, key: config.key, sso: config.sso }; post('/api/v1/apps/install', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); // put new app with amended title in cache data.manifest = { title: title }; data.iconUrl = data.iconUrl ? (that.apiOrigin + data.iconUrl + '?access_token=' + token) : null; data.progress = 0; callback(null, data.id); }); }; Client.prototype.cloneApp = function (appId, config, callback) { var data = { location: config.location, domain: config.domain, portBindings: config.portBindings, backupId: config.backupId }; post('/api/v1/apps/' + appId + '/clone', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.restoreApp = function (appId, backupId, password, callback) { var data = { password: password, backupId: backupId }; post('/api/v1/apps/' + appId + '/restore', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.backupApp = function (appId, callback) { var data = {}; post('/api/v1/apps/' + appId + '/backup', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.uninstallApp = function (appId, password, callback) { var data = { password: password }; post('/api/v1/apps/' + appId + '/uninstall', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.configureApp = function (id, data, callback) { post('/api/v1/apps/' + id + '/configure', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.updateApp = function (id, manifest, callback) { var data = { appStoreId: manifest.id + '@' + manifest.version }; post('/api/v1/apps/' + id + '/update', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.startApp = function (id, callback) { post('/api/v1/apps/' + id + '/start', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.stopApp = function (id, callback) { post('/api/v1/apps/' + id + '/stop', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.debugApp = function (id, state, callback) { var data = { appId: id, debugMode: state ? { readonlyRootfs: true, cmd: [ '/bin/bash', '-c', 'echo "Repair mode. Use the webterminal or cloudron exec to repair. Sleeping" && sleep infinity' ] } : null }; post('/api/v1/apps/' + id + '/configure', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.version = function (callback) { get('/api/v1/cloudron/status', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }).error(defaultErrorHandler(callback)); }; Client.prototype.getStatus = function (callback) { get('/api/v1/cloudron/status', null, function (error, data, status) { if (error) return callback(error); if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data)); callback(null, data); }).error(defaultErrorHandler(callback)); }; Client.prototype.setBackupConfig = function (backupConfig, callback) { post('/api/v1/settings/backup_config', backupConfig, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getBackupConfig = function (callback) { get('/api/v1/settings/backup_config', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setDynamicDnsConfig = function (enabled, callback) { post('/api/v1/settings/dynamic_dns', { enabled: enabled }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getDynamicDnsConfig = function (callback) { get('/api/v1/settings/dynamic_dns', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.enabled); }); }; Client.prototype.setUnstableAppsConfig = function (enabled, callback) { post('/api/v1/settings/unstable_apps', { enabled: enabled }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getUnstableAppsConfig = function (callback) { get('/api/v1/settings/unstable_apps', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.enabled); }); }; Client.prototype.getUpdateInfo = function (callback) { get('/api/v1/cloudron/update', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.checkForUpdates = function (callback) { post('/api/v1/cloudron/check_for_updates', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); client.refreshConfig(callback); }); }; Client.prototype.setAppAutoupdatePattern = function (pattern, callback) { post('/api/v1/settings/app_autoupdate_pattern', { pattern: pattern }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getAppAutoupdatePattern = function (callback) { get('/api/v1/settings/app_autoupdate_pattern', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setBoxAutoupdatePattern = function (pattern, callback) { post('/api/v1/settings/box_autoupdate_pattern', { pattern: pattern }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getBoxAutoupdatePattern = function (callback) { get('/api/v1/settings/box_autoupdate_pattern', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getRemoteSupport = function (callback) { get('/api/v1/support/remote_support', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.enabled); }); }; Client.prototype.enableRemoteSupport = function (enable, callback) { post('/api/v1/support/remote_support', { enable: enable }, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getBackups = function (callback) { get('/api/v1/backups', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.backups); }); }; Client.prototype.getLatestTaskByType = function (type, callback) { get('/api/v1/tasks?page=1&per_page=1&type=' + type, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.tasks.length ? data.tasks[0] : null); }); }; Client.prototype.getTask = function (taskId, callback) { get('/api/v1/tasks/' + taskId, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getTaskLogs = function (taskId, follow, lines, callback) { if (follow) { var eventSource = new EventSource(client.apiOrigin + '/api/v1/tasks/' + taskId + '/logstream?lines=' + lines + '&access_token=' + token); callback(null, eventSource); } else { get('/api/v1/services/' + taskId + '/logs?lines=' + lines, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); } }; Client.prototype.startBackup = function (callback) { post('/api/v1/backups/create', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data.taskId); }); }; Client.prototype.cleanupBackups = function (callback) { post('/api/v1/backups/cleanup', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data.taskId); }); }; Client.prototype.stopTask = function (taskId, callback) { post('/api/v1/tasks/' + taskId + '/stop', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.restore = function (backupConfig, backupId, version, callback) { var data = { backupConfig: backupConfig, backupId: backupId, version: version }; post('/api/v1/cloudron/restore', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status)); callback(null); }); }; Client.prototype.getNotifications = function (acknowledged, page, perPage, callback) { var config = { params: { page: page, per_page: perPage } }; if (typeof acknowledged === 'boolean') config.params.acknowledged = acknowledged; get('/api/v1/notifications', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.notifications); }); }; Client.prototype.ackNotification = function (notificationId, callback) { post('/api/v1/notifications/' + notificationId, {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status)); callback(null); }); }; Client.prototype.getEvent = function (eventId, callback) { get('/api/v1/cloudron/eventlog/' + eventId, {}, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.event); }); }; Client.prototype.getEventLogs = function (actions, search, page, perPage, callback) { var config = { params: { actions: actions, search: search, page: page, per_page: perPage } }; get('/api/v1/cloudron/eventlog', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.eventlogs); }); }; Client.prototype.getPlatformLogs = function (unit, follow, lines, callback) { if (follow) { var eventSource = new EventSource(client.apiOrigin + '/api/v1/cloudron/logstream/' + unit + '?lines=' + lines + '&access_token=' + token); callback(null, eventSource); } else { get('/api/v1/cloudron/logs/' + unit + '?lines=' + lines, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); } }; Client.prototype.getServiceLogs = function (serviceName, follow, lines, callback) { if (follow) { var eventSource = new EventSource(client.apiOrigin + '/api/v1/services/' + serviceName + '/logstream?lines=' + lines + '&access_token=' + token); callback(null, eventSource); } else { get('/api/v1/services/' + serviceName + '/logs?lines=' + lines, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); } }; Client.prototype.getApps = function (callback) { get('/api/v1/apps', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.apps); }); }; Client.prototype.getAppLogs = function (appId, follow, lines, callback) { if (follow) { var eventSource = new EventSource(client.apiOrigin + '/api/v1/apps/' + appId + '/logstream?lines=' + lines + '&access_token=' + token); callback(null, eventSource); } else { get('/api/v1/apps/' + appId + '/logs', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); } }; Client.prototype.getAppBackups = function (appId, callback) { get('/api/v1/apps/' + appId + '/backups', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.backups); }); }; Client.prototype.getServices = function (callback) { get('/api/v1/services', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.services); }); }; Client.prototype.getService = function (serviceName, callback) { get('/api/v1/services/' + serviceName, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.service); }); }; Client.prototype.configureService = function (serviceName, memoryLimit, callback) { post('/api/v1/services/' + serviceName, { memory: memoryLimit }, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.restartService = function (serviceName, callback) { post('/api/v1/services/' + serviceName + '/restart', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getUsers = function (search, page, perPage, callback) { if (typeof search === 'function') { callback = search; search = ''; page = 1; perPage = 1000; } var config = { params: { page: page, per_page: perPage } }; if (search) config.params.search = search; get('/api/v1/users', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.users); }); }; Client.prototype.getUser = function (userId, callback) { get('/api/v1/users/' + userId, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getGroups = function (callback) { get('/api/v1/groups', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.groups); }); }; Client.prototype.setGroups = function (userId, groupIds, callback) { put('/api/v1/users/' + userId + '/groups', { groupIds: groupIds }, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getGroup = function (groupId, callback) { get('/api/v1/groups/' + groupId, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.createGroup = function (name, callback) { var data = { name: name }; post('/api/v1/groups', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.updateGroup = function (id, name, callback) { var data = { name: name }; post('/api/v1/groups/' + id, data, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setGroupMembers = function (id, userIds, callback) { var data = { userIds: userIds }; put('/api/v1/groups/' + id + '/members', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.removeGroup = function (groupId, password, callback) { var config = { data: { password: password }, headers: { 'Content-Type': 'application/json' } }; del('/api/v1/groups/' + groupId, config, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getApp = function (appId, callback) { get('/api/v1/apps/' + appId, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); var tmp = data.manifest.description.match(/\(.*)\<\/upstream\>/i); data.upstreamVersion = (tmp && tmp[1]) ? tmp[1] : ''; callback(null, data); }); }; Client.prototype.getCachedApp = function (appId, callback) { var appFound = null; this._installedApps.some(function (app) { if (app.id === appId) { appFound = app; return true; } else { return false; } }); if (appFound) return callback(null, appFound); else return callback(new Error('App not found')); }; Client.prototype.getCachedAppSync = function (appId) { var appFound = null; this._installedApps.some(function (app) { if (app.id === appId) { appFound = app; return true; } else { return false; } }); return appFound; }; Client.prototype.createInvite = function (userId, callback) { post('/api/v1/users/' + userId + '/create_invite', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.resetToken); }); }; Client.prototype.sendInvite = function (userId, callback) { post('/api/v1/users/' + userId + '/send_invite', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.setup = function (domain, zoneName, provider, config, tlsConfig, callback) { var data = { dnsConfig: { domain: domain, zoneName: zoneName, provider: provider, config: config, tlsConfig: tlsConfig } }; post('/api/v1/cloudron/setup', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.createAdmin = function (username, password, email, displayName, setupToken, callback) { var that = this; var data = { username: username, password: password, email: email, displayName: displayName }; var query = ''; if (setupToken) query = '?setupToken=' + setupToken; post('/api/v1/cloudron/activate' + query, data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); that.setToken(data.token); that.setUserInfo({ username: username, email: email, admin: true, twoFactorAuthenticationEnabled: false }); callback(null, data.activated); }); }; Client.prototype.getOAuthClients = function (callback) { get('/api/v1/clients', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.clients); }); }; Client.prototype.createOAuthClient = function (appId, scope, redirectURI, callback) { var data = { appId: appId, scope: scope, redirectURI: redirectURI }; post('/api/v1/clients', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null, data.clients); }); }; Client.prototype.delOAuthClient = function (id, callback) { del('/api/v1/clients/' + id, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.createTokenByClientId = function (id, scope, expiresAt, name, callback) { var data = { scope: scope, expiresAt: expiresAt, name: name || '' }; post('/api/v1/clients/' + id + '/tokens', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null, data.token); }); }; Client.prototype.getTokensByClientId = function (id, callback) { get('/api/v1/clients/' + id + '/tokens', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.tokens); }); }; Client.prototype.delTokensByClientId = function (id, callback) { del('/api/v1/clients/' + id + '/tokens', null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.delToken = function (clientId, tokenId, callback) { del('/api/v1/clients/' + clientId + '/tokens/' + tokenId, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.update = function (callback) { var data = { }; post('/api/v1/cloudron/update', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data.taskId); }); }; Client.prototype.isRebootRequired = function (callback) { get('/api/v1/cloudron/reboot', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.rebootRequired); }); }; Client.prototype.reboot = function (callback) { post('/api/v1/cloudron/reboot', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setAdmin = function (domain, password, callback) { post('/api/v1/domains/' + domain + '/set_admin', { password: password }, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setCertificate = function (certificateFile, keyFile, callback) { var data = { cert: certificateFile, key: keyFile }; post('/api/v1/settings/certificate', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.disks = function (callback) { get('/api/v1/cloudron/disks', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.graphs = function (targets, from, callback) { var config = { params: { target: targets, format: 'json', from: from } }; get('/api/v1/cloudron/graphs', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.createTicket = function (type, subject, description, appId /* optional */, callback) { var data = { type: type, subject: subject, description: description, appId: appId || undefined }; post('/api/v1/support/ticket', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.createUser = function (user, callback) { var data = { email: user.email, displayName: user.displayName, admin: user.admin }; if (user.username !== null) data.username = user.username; post('/api/v1/users', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.updateUser = function (user, callback) { var data = { email: user.email, displayName: user.displayName, fallbackEmail: user.fallbackEmail, admin: user.admin }; post('/api/v1/users/' + user.id, data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.transferOwnership = function (oldOwnerId, newOwnerId, callback) { var data = { ownerId: newOwnerId }; post('/api/v1/users/' + oldOwnerId + '/transfer', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.removeUser = function (userId, password, callback) { var config = { data: { password: password }, headers: { 'Content-Type': 'application/json' } }; del('/api/v1/users/' + userId, config, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.updateProfile = function (data, callback) { post('/api/v1/profile', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.changePassword = function (currentPassword, newPassword, callback) { var data = { password: currentPassword, newPassword: newPassword }; post('/api/v1/profile/password', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setTwoFactorAuthenticationSecret = function (callback) { var data = {}; post('/api/v1/profile/twofactorauthentication', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.enableTwoFactorAuthentication = function (totpToken, callback) { var data = { totpToken: totpToken }; post('/api/v1/profile/twofactorauthentication/enable', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.disableTwoFactorAuthentication = function (password, callback) { var data = { password: password }; post('/api/v1/profile/twofactorauthentication/disable', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.refreshUserInfo = function (callback) { var that = this; callback = typeof callback === 'function' ? callback : function () {}; this.userInfo(function (error, result) { if (error) return callback(error); that.setUserInfo(result); callback(null); }); }; Client.prototype.refreshConfig = function (callback) { var that = this; callback = typeof callback === 'function' ? callback : function () {}; this.config(function (error, result) { if (error) return callback(error); that.getUpdateInfo(function (error, info) { // note: non-admin users may get access denied for this if (!error) result.update = info.update; // attach update information to config object that.setConfig(result); callback(null); }); }); }; Client.prototype._appPostProcess = function (app) { // calculate the icon paths app.iconUrl = app.iconUrl ? (this.apiOrigin + app.iconUrl + '?access_token=' + token) : null; // FIXME have a real message structure, not some string to randomly parse // extract progress percentage var installationProgress = app.installationProgress || ''; var progress = parseInt(installationProgress.split(',')[0], 10); // Unfortunatelly some errors are not actual progress messages, but still have a number in fron like a http status code if (isNaN(progress) || progress > 100) { app.progress = 0; app.message = installationProgress; } else { app.progress = progress; app.message = installationProgress.replace(/.*, /,''); } // amend the post install confirm state app.pendingPostInstallConfirmation = !!localStorage['confirmPostInstall_' + app.id]; return app; }; function binarySearch(array, pred) { var lo = -1, hi = array.length; while (1 + lo !== hi) { var mi = lo + ((hi - lo) >> 1); if (pred(array[mi])) { hi = mi; } else { lo = mi; } } return hi; } Client.prototype._updateAppCache = function (app) { var found = -1; // find by id and not by loc because the app's fqdn might have changed between invokations for (var i = 0; i < this._installedApps.length; ++i) { if (this._installedApps[i].id === app.id) { found = i; break; } } var tmp = {}; angular.copy(app, tmp); this._appPostProcess(tmp); // only replace if the app is already known if (found !== -1 && this._installedApps[found].fqdn === tmp.fqdn) { // app location has not changed angular.copy(tmp, this._installedApps[found]); } else { if (found !== -1) this._installedApps.splice(found, 1); // remove it var loc = binarySearch(this._installedApps, function (item) { return item.fqdn.localeCompare(app.fqdn) <= 0; }); this._installedApps.splice(loc, 0, tmp); // insert into sorted fqdn array } this._installedAppsById[app.id] = this._installedApps[found]; // TODO this not very elegant // update app tags tmp = this._installedApps .map(function (app) { return app.tags || []; }) // return array of arrays .reduce(function (a, i) { return a.concat(i); }, []) // merge all arrays into one .filter(function (v, i, self) { return self.indexOf(v) === i; }); // filter duplicates // keep tag array references angular.copy(tmp, this._appTags); }; // this requires app:manage permissions Client.prototype.refreshAppCache = function (id, callback) { var that = this; callback = typeof callback === 'function' ? callback : function () {}; this.getApp(id, function (error, app) { if (error) return callback(error); that._updateAppCache(app); callback(null, app); }); }; Client.prototype.refreshInstalledApps = function (callback) { var that = this; callback = typeof callback === 'function' ? callback : function () {}; this.getApps(function (error, apps) { if (error) return callback(error); asyncForEach(apps, function (app, iteratorCallback) { var canManageApp = that._userInfo.admin || that._userInfo.id === app.ownerId; if (!canManageApp) { that._updateAppCache(app); return iteratorCallback(); } if (that._installedAppsById[app.id] && that._installedAppsById[app.id].ts === app.ts) return iteratorCallback(); // app has not changed that.refreshAppCache(app.id, iteratorCallback); }, function iteratorDone(error) { if (error) return callback(error); // filter out old apps, going backwards to allow splicing for (var i = that._installedApps.length - 1; i >= 0; --i) { if (!apps.some(function (elem) { return (elem.id === that._installedApps[i].id); })) { var removed = that._installedApps.splice(i, 1); delete that._installedAppsById[removed[0].id]; } } callback(null); }); }); }; Client.prototype.login = function () { this.setToken(null); this._userInfo = {}; var callbackURL = window.location.protocol + '//' + window.location.host + '/login_callback.html'; var scope = 'root,profile,apps'; // generate a state id to protect agains csrf var state = Math.floor((1 + Math.random()) * 0x1000000000000).toString(16).substring(1); window.localStorage.oauth2State = state; // stash for further use in login_callback window.localStorage.returnTo = '/' + window.location.hash; window.location.href = this.apiOrigin + '/api/v1/oauth/dialog/authorize?response_type=token&client_id=' + this._clientId + '&redirect_uri=' + callbackURL + '&scope=' + scope + '&state=' + state; }; Client.prototype.logout = function () { this.setToken(null); this._userInfo = {}; // logout from OAuth session var origin = window.location.protocol + '//' + window.location.host; window.location.href = this.apiOrigin + '/api/v1/session/logout?redirect=' + origin; }; // this is ununsed because webadmin uses implicit grant flow Client.prototype.exchangeCodeForToken = function (code, callback) { var data = { grant_type: 'authorization_code', code: code, redirect_uri: window.location.protocol + '//' + window.location.host, client_id: this._clientId, client_secret: this._clientSecret }; post('/api/v1/oauth/token?response_type=token&client_id=' + this._clientId, data, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.access_token); }); }; Client.prototype.enoughResourcesAvailable = function (app) { var needed = app.manifest.memoryLimit || DEFAULT_MEMORY_LIMIT; // RAM+Swap var used = this.getInstalledApps().reduce(function (prev, cur) { return prev + (cur.memoryLimit || cur.manifest.memoryLimit || DEFAULT_MEMORY_LIMIT); }, 0); var roundedMemory = Math.round(this.getConfig().memory / (1024 * 1024 * 1024)) * 1024 * 1024 * 1024; // round to nearest GB var totalMemory = roundedMemory * 1.5; // cloudron-system-setup.sh creates equal amount of swap. 1.5 factor is arbitrary var available = (totalMemory || 0) - used; return (available - needed) >= 0; }; Client.prototype.uploadFile = function (appId, file, progressCallback, callback) { var fd = new FormData(); fd.append('file', file); var config = { headers: { 'Content-Type': undefined }, transformRequest: angular.identity, uploadEventHandlers: { progress: progressCallback } }; post('/api/v1/apps/' + appId + '/upload?file=' + encodeURIComponent('/tmp/' + file.name), fd, config, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.checkDownloadableFile = function (appId, filePath, callback) { var config = { headers: { 'Content-Type': undefined } }; head('/api/v1/apps/' + appId + '/download?file=' + encodeURIComponent(filePath), config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.sendTestMail = function (domain, to, callback) { var data = { to: to }; post('/api/v1/mail/' + domain + '/send_test_mail', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; // Domains Client.prototype.getDomains = function (callback) { get('/api/v1/domains', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.domains); }); }; Client.prototype.getDomain = function (domain, callback) { get('/api/v1/domains/' + domain, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.addMailDomain = function (domain, callback) { post('/api/v1/mail', { domain: domain }, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.addDomain = function (domain, zoneName, provider, config, fallbackCertificate, tlsConfig, callback) { var data = { domain: domain, provider: provider, config: config, tlsConfig: tlsConfig }; if (zoneName) data.zoneName = zoneName; var that = this; if (fallbackCertificate) data.fallbackCertificate = fallbackCertificate; // hack until we fix the domains.js post('/api/v1/domains', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); that.addMailDomain(domain, callback); }); }; Client.prototype.updateDomain = function (domain, zoneName, provider, config, fallbackCertificate, tlsConfig, callback) { var data = { provider: provider, config: config, tlsConfig: tlsConfig }; if (zoneName) data.zoneName = zoneName; var that = this; if (fallbackCertificate) data.fallbackCertificate = fallbackCertificate; put('/api/v1/domains/' + domain, data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); that.setDnsRecords(domain, callback); // this is done so that an out-of-sync dkim key can be synced }); }; Client.prototype.removeMailDomain = function (domain, password, callback) { var config = { data: { password: password }, headers: { 'Content-Type': 'application/json' } }; // FIXME del('/api/v1/mail/' + domain, config, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.renewCerts = function (domain, callback) { post('/api/v1/cloudron/renew_certs', { domain: domain || null }, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data.taskId); }); }; Client.prototype.removeDomain = function (domain, password, callback) { var config = { data: { password: password }, headers: { 'Content-Type': 'application/json' } }; this.removeMailDomain(domain, password, function () { // hack: ignore errors until we fix the domains.js del('/api/v1/domains/' + domain, config, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }); }; Client.prototype.prepareDashboardDomain = function (domain, callback) { var data = { domain: domain }; post('/api/v1/cloudron/prepare_dashboard_domain', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data.taskId); }); }; Client.prototype.setDashboardDomain = function (domain, callback) { var data = { domain: domain }; post('/api/v1/cloudron/set_dashboard_domain', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; // Email Client.prototype.getMailConfigForDomain = function (domain, callback) { get('/api/v1/mail/' + domain, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.enableMailForDomain = function (domain, enabled, callback) { post('/api/v1/mail/' + domain + '/enable', { enabled: enabled }, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.setDnsRecords = function (domain, callback) { post('/api/v1/mail/' + domain + '/dns', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getMailStatusForDomain = function (domain, callback) { get('/api/v1/mail/' + domain + '/status', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setMailRelay = function (domain, data, callback) { post('/api/v1/mail/' + domain + '/relay', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.setCatchallAddresses = function (domain, addresses, callback) { post('/api/v1/mail/' + domain + '/catch_all', { addresses: addresses }, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.setMailFromValidation = function (domain, enabled, callback) { post('/api/v1/mail/' + domain + '/mail_from_validation', { enabled: enabled }, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; // Mailboxes Client.prototype.getMailboxes = function (domain, callback) { get('/api/v1/mail/' + domain + '/mailboxes', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.mailboxes); }); }; Client.prototype.getMailbox = function (domain, name, callback) { get('/api/v1/mail/' + domain + '/mailboxes/' + name, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.mailbox); }); }; Client.prototype.addMailbox = function (domain, name, userId, callback) { var data = { name: name, userId: userId }; post('/api/v1/mail/' + domain + '/mailboxes', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.updateMailbox = function (domain, name, userId, callback) { var data = { userId: userId }; post('/api/v1/mail/' + domain + '/mailboxes/' + name, data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.removeMailbox = function (domain, name, callback) { del('/api/v1/mail/' + domain + '/mailboxes/' + name, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.listAliases = function (domain, callback) { get('/api/v1/mail/' + domain + '/aliases', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.aliases); }); }; Client.prototype.getAliases = function (domain, name, callback) { get('/api/v1/mail/' + domain + '/aliases/' + name, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.aliases); }); }; Client.prototype.setAliases = function (domain, name, aliases, callback) { var data = { aliases: aliases }; put('/api/v1/mail/' + domain + '/aliases/' + name, data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.listMailingLists = function (domain, callback) { get('/api/v1/mail/' + domain + '/lists', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.lists); }); }; Client.prototype.getMailingList = function (domain, name, callback) { get('/api/v1/mail/' + domain + '/lists/' + name, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.list); }); }; Client.prototype.addMailingList = function (domain, name, members, callback) { var data = { name: name, members: members }; post('/api/v1/mail/' + domain + '/lists', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.updateMailingList = function (domain, name, members, callback) { var data = { members: members }; post('/api/v1/mail/' + domain + '/lists/' + name, data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.removeMailingList = function (domain, name, callback) { del('/api/v1/mail/' + domain + '/lists/' + name, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getAppstoreAppByIdAndVersion = function (appId, version, callback) { var url = '/api/v1/appstore/apps/' + appId; if (version && version !== 'latest') url += '/versions/' + version; get(url, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getAppstoreApps = function (callback) { var that = this; get('/api/v1/appstore/apps', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); angular.copy(data.apps, that._appstoreAppCache); return callback(null, that._appstoreAppCache); }); }; Client.prototype.getAppstoreAppsFast = function (callback) { if (this._appstoreAppCache.length !== 0) return callback(null, this._appstoreAppCache); this.getAppstoreApps(callback); }; Client.prototype.getSubscription = function (callback) { get('/api/v1/appstore/subscription', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); // just some helper property, since angular bindings cannot dot his easily data.emailEncoded = encodeURIComponent(data.email); callback(null, data); // { email, plan: { id, name }, cancel_at, status } }); }; Client.prototype.registerCloudron = function (email, password, totpToken, signup, callback) { var data = { email: email, password: password, signup: signup }; if (totpToken) data.totpToken = totpToken; post('/api/v1/appstore/register_cloudron', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null); }); }; client = new Client(); return client; }]);