monaca-lib
Version:
Monaca cloud and localkit API bindings for JavaScript
1,684 lines (1,505 loc) • 138 kB
JavaScript
(function() {
'use strict';
var Q = require('q'),
qlimit = require('qlimit'),
request = require('request'),
os = require('os'),
path = require('path'),
fs = require('fs-extra'),
shell = require('shelljs'),
crc32 = require('buffer-crc32'),
nconf = require('nconf'),
extend = require('extend'),
crypto = require('crypto'),
xml2js = require('xml2js'),
lockfile = require('lockfile'),
tmp = require('tmp'),
extract = require('extract-zip'),
glob = require('glob'),
EventEmitter = require('events'),
ora = require('ora'),
compareVersions = require('compare-versions'),
showNpmVersion = false,
npm;
const { spawn } = require('child_process');
const utils = require(path.join(__dirname, 'utils'));
const migration = require('./migration');
const fixPath = require('fix-path');
// support project type
const REACT_NATIVE = 'react-native';
const CAPACITOR = 'capacitor';
const CORDOVA = 'cordova';
// Spinner
var spinner = null;
// local imports
var localProperties = require(path.join(__dirname, 'monaca', 'localProperties'));
var USER_CORDOVA = path.join(
process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'],
'.cordova'
);
var NPM_PACKAGE_FILE = path.join(USER_CORDOVA, 'package.json');
var USER_DATA_FILE = path.join(USER_CORDOVA, 'monaca.json');
var CONFIG_FILE = path.join(USER_CORDOVA, 'monaca_config.json');
// error message
const ERROR_DIRECTORY_PATH_MESSAGE = 'This is directory. Please provide the file path.';
// config
var config = nconf.env()
.file(path.join(__dirname, 'config.json'))
.get('monaca');
/**
* @class Monaca
* @description
* Create Monaca API object.
* @param {string} [apiRoot] - Root of Monaca web API. Defaults to {@link https://ide.monaca.mobi/api}.
* @example
* var monaca = new Monaca();
*
* monaca.login('my@email.org', 'mypassword').then(
* function() {
* // Login successful. Let's do some stuff!
* },
* function(error) {
* // Login failed! :(
* }
* );
*/
var Monaca = function(apiRoot, options) {
// Parameters are optional.
if (typeof apiRoot === 'object') {
options = apiRoot;
apiRoot = undefined;
} else {
options = options || {};
}
var webApiRoot;
if (!apiRoot) {
try {
if (!fs.existsSync(USER_CORDOVA)) fs.mkdirsSync(USER_CORDOVA);
if (!fs.existsSync(CONFIG_FILE)) fs.writeFileSync(CONFIG_FILE, '{}');
} catch (err) {
console.log('Could not write config file ' + err);
}
try {
var configContent = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
var apiEndpoint = configContent['api_endpoint'];
if (apiEndpoint) {
webApiRoot = 'https://' + apiEndpoint + '/en/api';
apiRoot = 'https://ide.' + apiEndpoint + '/api';
}
} catch (e) {
console.log("Cound not find/set the custom API endpoint " + e);
}
}
/**
* @description
* Root of Monaca IDE API.
* @name Monaca#apiRoot
* @type string
* @default https://ide.monaca.mobi/api
*/
Object.defineProperty(this, 'apiRoot', {
value: apiRoot ? apiRoot : config.default_api_root,
writable: true
});
/**
* @description
* Root of Monaca web API.
* @name Monaca#webApiRoot
* @type string
* @default https://monaca.mobi/en/api
*/
Object.defineProperty(this, 'webApiRoot', {
value: webApiRoot ? webApiRoot : config.web_api_root,
writable: true
});
/**
* @description
* Executable path of npm.
* @name Monaca#npmPath
* @type string
*/
Object.defineProperty(this, 'npmPath', {
value: options.npmPath || '',
writable: true
});
/**
* @description
* Executable path of node.
* @name Monaca#nodePath
* @type string
*/
Object.defineProperty(this, 'nodePath', {
value: options.nodePath || '',
writable: true
});
/**
* @description
* Version of Monaca library
* @name Monaca#version
* @type string
*/
Object.defineProperty(this, 'version', {
value: require(path.join(__dirname, '..', 'package.json')).version,
writable: false
});
/**
* @description
* Package name.
* @name Monaca#packageName
* @type string
*/
Object.defineProperty(this, 'packageName', {
value: require(path.join(__dirname, '..', 'package.json')).name,
writable: false
});
/**
* @description
* Client type.
* @name Monaca#clientType
* @type string
*/
Object.defineProperty(this, 'clientType', {
value: options.clientType || 'local',
writable: false
});
/**
* @description
* Client version.
* @name Monaca#clientVersion
* @type string
*/
Object.defineProperty(this, 'clientVersion', {
value: options.clientVersion || '0.0.0',
writable: false
});
/**
* @description
* Debug.
* @name Monaca#debug
* @type boolean
*/
Object.defineProperty(this, 'debug', {
value: (options.hasOwnProperty('debug') && options.debug === true),
writable: false
});
/**
* @description
* userCordova.
* @name Monaca#userCordova
* @type string
*/
Object.defineProperty(this, 'userCordova', {
value: USER_CORDOVA,
writable: false
});
if (this.clientType === 'localkit') {
this._setExecutablePathForNPMAndNode();
} else {
// Check node version
if (compareVersions(process.version, '10.0.0') < 0) {
console.log('We have detected that you are using the old version of Node (' + process.version + '). Please upgrade it to 10.x or higher.');
console.log('In order to work with this version, We need to apply this patch - process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0" \n\r')
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
}
}
this.tokens = {
api: null,
session: null
};
this.loginBody = null;
this._loggedIn = false;
if (this.debug) {
request.debug = true;
}
this.projectSettings = null;
this.emitter = new EventEmitter();
this._monacaData = this._loadAllData();
};
Monaca.prototype.setAPIConfig = function(apiEndpoint) {
var deferred = Q.defer();
try {
this.apiRoot = 'https://ide.' + apiEndpoint + '/api';
this.webApiRoot = 'https://' + apiEndpoint + '/en/api';
return this.setConfig('api_endpoint', apiEndpoint)
.then(
function(result) {
deferred.resolve();
}
)
.catch(
function(e) {
deferred.reject(e);
}
)
} catch (e) {
deferred.reject(e);
}
return deferred.promise;
}
Monaca.prototype.resetAPIConfig = function() {
var deferred = Q.defer();
try {
this.apiRoot = config.default_api_root;
this.webApiRoot = config.web_api_root;
return this.removeConfig('api_endpoint')
.then(
function(result) {
deferred.resolve();
}
)
.catch(
function(e) {
deferred.reject(e);
}
)
} catch (e) {
deferred.reject(e);
}
return deferred.promise;
}
Monaca.prototype._generateUUIDv4 = function(a, b) {
for (
b = a = ''; a++ < 36; b += a * 51 & 52 ?
(
a ^ 15 ?
8 ^ Math.random() *
(a ^ 20 ? 16 : 4) :
4
).toString(16) :
'-'
);
return b;
};
Monaca.prototype.getTrackId = function() {
if (this.getData('trackId')) {
return Q.resolve(this.getData('trackId'));
}
return this.setData({
trackId: this._generateUUIDv4()
}).then(Q.resolve.bind(null, this.getData('trackId')));
};
/**
* @method
* @memberof Monaca
* @description
* Reports any event to Monaca backend. It keeps the previous promise flow.
* @param {object} report - Report object. Must contain 'event'.
* Optional values are 'arg1' and 'otherArgs'.
* @param {any} resolvedValue - Optional. This parameter will be returned by the promise.
* @return {Promise}
* @example
* monaca.reportAnalytics({event: 'create'})
* .then(nextMethod)
*/
Monaca.prototype.reportAnalytics = function(report, resolvedValue) {
return this.getTrackId().then(
function(trackId) {
var form = extend({}, report, { event: 'monaca_lib_' + report.event }, {
trackId: trackId,
clientType: this.clientType,
version: this.version,
clientId: this.getData('clientId')
});
return this._post(this.apiRoot + '/user/track', form)
.then(Q.resolve.bind(null, resolvedValue), Q.resolve.bind(null, resolvedValue));
}.bind(this),
Q.resolve.bind(null, resolvedValue)
);
};
/**
* @method
* @memberof Monaca
* @description
* Reports a fail event to Monaca backend. This must be used with a rejected promise.
* It keeps the rejected promise flow.
* @param {object} report - Report object. Must contain 'event'.
* Optional values are 'arg1' and 'otherArgs'.
* @param {any} resolvedValue - Optional. This parameter will be returned by the promise.
* @return {Rejected promise}
* @example
* monaca.reportFail({event: 'create'})
* .catch(handleErrors)
*/
Monaca.prototype.reportFail = function(report, error) {
report.errorDetail = error === 'object' ? error.message : error;
return this.reportAnalytics(extend({}, report, { event: report.event + '_fail' }))
.then(Q.reject.bind(null, error));
};
/**
* @method
* @memberof Monaca
* @description
* Reports a finish event to Monaca backend. It keeps the previous promise flow.
* @param {object} report - Report object. Must contain 'event'.
* Optional values are 'arg1' and 'otherArgs'.
* @param {any} resolvedValue - Optional. This parameter will be returned by the promise.
* @return {Promise}
* @example
* monaca.reportAnalytics({event: 'create'})
* .then(nextMethod)
*/
Monaca.prototype.reportFinish = function(report, resolvedValue) {
return this.reportAnalytics(extend({}, report, { event: report.event + '_finish' }), resolvedValue);
};
Monaca.prototype._safeParse = function(jsonString) {
try {
return JSON.parse(jsonString);
} catch(e) {
throw new Error('Not a JSON response.');
}
};
Monaca.prototype._loadAllData = function() {
var data;
try {
data = require(USER_DATA_FILE);
} catch(e) {
if (e.code === 'MODULE_NOT_FOUND') {
return {};
}
throw e;
}
return data;
};
Monaca.prototype._saveAllData = function() {
var deferred = Q.defer(),
jsonData;
try {
jsonData = JSON.stringify(this._monacaData);
}
catch (error) {
return deferred.reject(error);
}
fs.exists(path.dirname(USER_DATA_FILE), function(exists) {
if (!exists) {
shell.mkdir('-p', path.dirname(USER_DATA_FILE));
}
fs.writeFile(USER_DATA_FILE, jsonData, function(error) {
if (error) {
deferred.reject(error);
}
else {
deferred.resolve();
}
});
});
return deferred.promise;
};
Monaca.prototype.setData = function(data) {
extend(this._monacaData, data);
return this._saveAllData();
};
Monaca.prototype.getData = function(key) {
return this._monacaData[key];
};
Monaca.prototype._filterFiles = function(dst, src) {
for (var key in dst) {
utils.spinnerLoading(spinner, 'Comparing File: ' + key);
if (dst.hasOwnProperty(key)) {
var d = dst[key];
if (d.type == 'dir') {
delete dst[key];
}
else if (dst.hasOwnProperty(key) && src.hasOwnProperty(key)) {
var s = src[key];
if (d.hash === s.hash) {
delete dst[key];
}
}
}
}
};
Monaca.prototype._filterMonacaIgnore = function(projectDir) {
return this._getMonacaIgnore(projectDir)
.split("\r\n") // Split by \r\n.
.map(function(ele) { return ele.split("\n")}) // Split each array element from previous step by \n again.
.reduce(function(a,b) { return a.concat(b)}) // Now concat them into one array.
.filter(function(n) {
return n.trim() !== "" && n.indexOf("#") !== 0;
});
};
Monaca.prototype._getMonacaIgnore = function(projectDir) {
var monacaIgnore = path.resolve(projectDir, '.monacaignore');
if (fs.existsSync(monacaIgnore)) {
return fs.readFileSync(monacaIgnore, 'utf8');
} else {
// generate .monacaignore for all frameworks
var result = this._generateMonacaIgnore(projectDir);
if (result instanceof Error) {
throw result;
} else {
return this._getMonacaIgnore(projectDir);
}
}
};
Monaca.prototype._generateMonacaIgnore = function(projectDir) {
let defaultConfig = '';
let cordovaVersion = 0;
// Get cordova version
try {
cordovaVersion = parseInt(this.getCordovaVersion(projectDir));
} catch (e) {
cordovaVersion = 0;
}
if (cordovaVersion > 5) {
defaultConfig = path.resolve(__dirname, 'default-config', '.monacaignore');
} else {
// remove /platform from .monacaignore for lower cordovaVersion <= 5
defaultConfig = path.resolve(__dirname, 'default-config', 'cordova5', '.monacaignore');
}
if (fs.existsSync(defaultConfig)) {
console.log('Generating default .monacaignore file.');
console.log('This file is used to filter files for download and upload operation. Please modify the content as your requirements.');
return fs.copySync(defaultConfig, path.resolve(projectDir, '.monacaignore'));
} else {
return (new Error('No default .monacaignore file found.'));
}
};
Monaca.prototype._createRequestClient = function(data) {
var deferred = Q.defer(), qs = {};
var apiToken = this.getData('x-monaca-param-api-token'),
session = this.getData('x-monaca-param-session');
if (apiToken) {
qs.api_token = apiToken;
}
if (data) {
extend(qs, data);
}
this.getConfig('http_proxy').then(
function(httpProxy) {
var requestClient = request.defaults({
qs: qs,
// rejectUnauthorized: false, // DELETE
encoding: null,
proxy: httpProxy,
headers: {
Cookie: session || null
},
timeout: 300 * 1000
});
deferred.resolve(requestClient);
}.bind(this),
function(error) {
deferred.reject(error);
}
);
return deferred.promise;
};
Monaca.prototype._request = function(method, resource, data, requestClient, isFile ) {
method = method.toUpperCase();
resource = resource.match(/^https?\:\/\//) ? resource : (this.apiRoot + resource);
var deferred = Q.defer();
var createRequestClient = function() {
return (requestClient ? Q.resolve(requestClient) : this._createRequestClient(method === 'GET' ? data : undefined));
}.bind(this);
createRequestClient().then(
function(requestClient) {
var hashData = null;
if (! isFile) {
hashData = {
method: method,
url: resource,
form: method === 'POST' ? data : undefined
};
} else {
hashData = {
method: method,
url: resource,
formData: method === 'POST' ? data : undefined
};
}
requestClient(
hashData ,
function(error, response, body) {
if (error) {
deferred.reject(error.code);
} else {
const unauthorizedErrorMessage = 'Error in user authentication. Please login first.';
if (response.statusCode === 200) {
deferred.resolve({body: body, response: response});
} else if (response.statusCode === 401 && resource.startsWith(this.apiRoot) && !this.retry) {
this.retry = true;
this.relogin().then(function() {
deferred.resolve(this._request(method, resource, data, null ));
}.bind(this), function(error) {
deferred.reject(new Error(unauthorizedErrorMessage));
});
} else if (response.statusCode === 401) {
let errorMessage = unauthorizedErrorMessage;
const bodyJson = JSON.parse(body);
if (body && bodyJson && bodyJson.message) {
errorMessage = bodyJson.message;
}
deferred.reject(new Error(errorMessage));
} else {
try {
deferred.reject(JSON.parse(body));
}
catch (e) {
deferred.reject(response.statusCode);
}
}
}
}.bind(this)
);
}.bind(this),
function(error) {
deferred.reject(error);
}
)
return deferred.promise;
};
Monaca.prototype._get = function(resource, data) {
return this._request('GET', resource, data);
};
Monaca.prototype._post = function(resource, data) {
return this._request('POST', resource, data);
};
Monaca.prototype._post_file = function(resource, data) {
return this._request('POST', resource, data, null, true );
};
/**
* @method
* @memberof Monaca
* @description
* Returns information about server availability
* @return {Promise}
*/
Monaca.prototype.getConnectionStatus = function() {
var url = this.apiRoot.match(/https(.*)\//)[0] + 'server_check';
return this._post(url, {})
.then(
function(res) {
if ((/^2/).test(res.response.statusCode)) {
return Q.resolve('available');
} else {
return Q.resolve('not available');
}
},
function() {
return Q.resolve('not available');
}
);
};
/**
* @method
* @memberof Monaca
* @description
* Download project file and save to disk. Must be loggeed in to
* use.
* @param {string} projectId - Monaca project id.
* @param {string} remotePath - Source file in cloud.
* @param {string} localPath - Local file destination.
* @param {number} retryCount - Retry count to download a file.
* @return {Promise}
* @example
* monaca.downloadFile('SOME_PROJECT_ID', '/remote/file', '/local/file').then(
* function() {
* // File download successful!
* },
* function(error) {
* // File download failed.
* }
* );
*/
Monaca.prototype.downloadFile = function(projectId, remotePath, localPath, retryCount = 0) {
const RETRY_COUNT_LIMIT = 3;
const RETRY_SLEEP_MS = 3000;
var deferred = Q.defer();
this._post('/project/' + projectId + '/file/read/' + encodeURIComponent(remotePath), { path: remotePath }).then(
function(data) {
var parentDir = path.dirname(localPath);
fs.exists(parentDir, function(exists) {
if (!exists) {
shell.mkdir('-p', parentDir);
}
fs.writeFile(localPath, data.body, function(error) {
if (error) {
deferred.reject(error);
}
else {
deferred.resolve(localPath);
}
});
});
},
async function(error) {
if (retryCount < RETRY_COUNT_LIMIT) {
console.log(`Retrying ${retryCount + 1} times for ${remotePath}...`);
await utils.sleep(RETRY_SLEEP_MS);
this.downloadFile(projectId, remotePath, localPath, retryCount + 1)
.then(deferred.resolve)
.catch(deferred.reject);
} else {
deferred.reject(error);
}
}.bind(this)
);
return deferred.promise;
};
/**
* @method
* @memberof Monaca
* @description
* Upload a file from disk to the cloud. Must be logged in to use.
* @param {string} projectId - Monaca project ID.
* @param {string} localPath - Local source file.
* @param {string} remotePath - Remote file in cloud.
* @return {Promise}
* @example
* monaca.uploadFile('SOME_PROJECT_ID', '/local/file', '/remote/file').then(
* function() {
* // File upload successful!
* },
* function(error) {
* // File upload failed.
* }
* );
*/
Monaca.prototype.uploadFile = function(projectId, localPath, remotePath) {
var deferred = Q.defer();
fs.exists(localPath, function(exists) {
if (!exists) {
deferred.reject(new Error('File does not exist.'));
}
else {
fs.readFile(localPath, function(error, data) {
if (error) {
deferred.reject(error);
}
else {
this._post_file('/project/' + projectId + '/file/save/' + encodeURIComponent(remotePath), {
path: remotePath,
// contentBase64: data.toString('base64')
file: fs.createReadStream(localPath)
}).then(
function() {
deferred.resolve(remotePath);
},
function(error) {
deferred.reject(error);
}
);
}
}.bind(this));
}
}.bind(this));
return deferred.promise;
};
Monaca.prototype._deleteFileFromCloud = function(projectId, remotePath) {
var deferred = Q.defer();
this._post('/project/' + projectId + '/file/remove', {
paths: remotePath
}).then(
function() {
deferred.resolve(remotePath);
},
function(error) {
deferred.reject(error);
}
);
return deferred.promise;
};
Monaca.prototype._login = function() {
var deferred = Q.defer(),
options;
if (arguments.length === 3) {
options = arguments[2];
}
else if (typeof arguments[1] === 'object') {
options = arguments[1];
}
else {
options = {};
}
var form = {
language: options.language || 'en',
clientType: options.clientType || this.clientType || 'local',
version: options.version || this.packageName + ' ' + this.version,
os: os.platform()
};
// Edition option is for Visual Studio only.
if (options.edition) {
form.edition = options.edition;
}
if (arguments.length === 1 || typeof arguments[1] === 'object') {
form.token = arguments[0];
}
else {
form.email = arguments[0];
form.password = arguments[1];
}
return this._post(this.apiRoot + '/user/login', form)
.then(
function(data) {
var body = this._safeParse(data.body),
response = data.response;
if (body.status === 'ok') {
var headers = response.caseless.dict;
return this.setData({
'reloginToken': body.result.token,
'clientId': body.result.clientId,
'x-monaca-param-api-token': headers['x-monaca-param-api-token'],
'x-monaca-param-session': headers['x-monaca-param-session'],
'subscriberUrl': body.result.subscriberUrl,
})
.then(
function() {
this.tokens = {
api: headers['x-monaca-param-api-token'],
session: headers['x-monaca-param-session']
};
this.loginBody = body.result;
this._loggedIn = true;
return Q.resolve(this.loginBody);
}.bind(this),
Q.reject
);
}
return Q.reject(body);
}.bind(this),
Q.reject
);
};
/**
* @method
* @memberof Monaca
* @description
* Prepares the current session with local data before trying any request.
* If local data is not found it calls relogin.
* @param {object} [options] - Login parameters.
* @param {string} [options.version] - App name and version to send to the Monaca API. Defaults to "monaca-lib x.y.z".
* @param {string} [options.language] - Can be either "en" or "ja". Defaults to "en".
* @return {Promise}
* @example
* monaca.prepareSession().then(
* function() {
* // Login successful!
* },
* function(error) {
* // Login failed!
* }
* );
*/
Monaca.prototype.prepareSession = function(options) {
var apiToken = this.getData('x-monaca-param-api-token'),
session = this.getData('x-monaca-param-session');
if (!apiToken || !session) {
return this.relogin(options);
}
this.tokens = {
api: apiToken,
session: session
};
this._loggedIn = true;
return Q.resolve();
};
/**
* @method
* @memberof Monaca
* @description
* Login to Monaca cloud using a saved relogin token. Use {@link Monaca#login} to
* login the first time.
* @param {object} [options] - Login parameters.
* @param {string} [options.version] - App name and version to send to the Monaca API. Defaults to "monaca-lib x.y.z".
* @param {string} [options.language] - Can be either "en" or "ja". Defaults to "en".
* @return {Promise}
* @example
* monaca.relogin().then(
* function() {
* // Login successful!
* },
* function(error) {
* // Login failed!
* }
* );
*/
Monaca.prototype.relogin = function(options) {
options = options || {};
var reloginToken = this.getData('reloginToken');
if (typeof reloginToken !== 'string' || reloginToken === '') {
return Q.reject(new Error('Not a valid relogin token.'));
}
return this._login(reloginToken, options);
};
/**
* @method
* @memberof Monaca
* @description
* Sign in to Monaca cloud using email and password. Will save relogin token to disk
* if successful. After the relogin token has been saved, {@link Monaca#relogin} can
* be used to login.
* @param {string} email - A Monaca account email.
* @param {string} password - Password associated with the account.
* @param {object} [options] - Additional options.
* @param {string} [options.version] - App name and version to send to the Monaca API. Defaults to "monaca-lib x.y.z".
* @param {string} [options.language] - Can be either "en" or "ja". Defaults to "en".
* @return {Promise}
* @example
* monaca.login('my@email.com', 'password').then(
* function() {
* // Login successful!
* },
* function(error) {
* // Login failed!
* }
* );
*/
Monaca.prototype.login = function(email, password, options) {
if (options) {
return this._login(email, password, options);
}
else {
return this._login(email, password);
}
};
/**
* @method
* @memberof Monaca
* @description
* Sign out from Monaca cloud. Will remove relogin token from disk and session tokens
* from memory.
* @return {Promise}
* @example
* monaca.login('my@email.com', 'password').then(
* function() {
* monaca.logout();
* }
* );
*/
Monaca.prototype.logout = function() {
return this._get(this.apiRoot + '/user/logout')
.finally(
function() {
this.setData({
'reloginToken': '',
'clientId': '',
'x-monaca-param-api-token': '',
'x-monaca-param-session': '',
'trackId': ''
})
.then(
function() {
this.tokens = {
api: null,
session: null
};
this._loggedIn = false;
}.bind(this)
);
}.bind(this)
)
};
/**
* @method
* @memberof Monaca
* @description
* Creates a new account in Monaca cloud using email and password. Returns a token used to check.
* if the account has been activated.
* @param {string} email - A Monaca account email.
* @param {string} password - Password associated with the account.
* @param {string} password_confirm - Password confirmation.
* @return {Promise}
* @example
* monaca.signup('my@email.com', 'password', 'password').then(
* function() {
* // Signup successful!
* },
* function(error) {
* // Signup failed!
* }
* );
*/
Monaca.prototype.signup = function(email, password, passwordConfirm, options) {
options = options || {};
var deferred = Q.defer();
var form = {
language: options.language || 'en',
clientType: options.clientType || this.clientType || 'local',
version: options.version || this.packageName + ' ' + this.version,
os: os.platform(),
register: {
email: email,
password: password,
password_confirm: passwordConfirm
}
};
return this._post(this.webApiRoot + '/register', form)
.then(
function(data) {
var body = this._safeParse(data.body);
if (body.status === 'ok') {
return Q.resolve(body.result.submitOK.token);
}
var errorMessage = body.title;
Object.keys(body.result.formError).forEach(function(key) {
errorMessage += '\n' + key + ': ' + body.result.formError[key];
});
return Q.reject(new Error(errorMessage));
}.bind(this),
Q.reject
);
};
/**
* @method
* @memberof Monaca
* @description
* Checks if the account related to the specified token is already activated.
* @param {string} token - token related to a user account.
* @return {Promise}
* @example
* monaca.isActivatedUser('token').then(
* function() {
* // Account is activated!
* },
* function(error) {
* // Account is not activated yet!
* }
* );
*/
Monaca.prototype.isActivatedUser = function(token, options) {
options = options || {};
var deferred = Q.defer();
return this._post(this.webApiRoot + '/check_activate', {
language: options.language || 'en',
clientType: options.clientType || this.clientType || 'local',
version: options.version || this.packageName + ' ' + this.version,
os: os.platform(),
param: token
})
.then(
function(data) {
var body = this._safeParse(data.body);
if (body.status === 'ok') {
return body.result === 1 ? Q.resolve() : Q.reject();
}
return Q.reject(body.title);
}.bind(this),
Q.reject
);
};
/**
* Checks if the project can be built.
*
* @memberof Monaca
*
* @param {Object} projectInfo Contains project information such as projectId
* @param {Object} buildParams Contains build parameters. Example: {platform: 'android', purpose: 'release', checkForMinimumRequirements: false}
*
* @example
* monaca.checkBuildAvailability({
* projectId: 'myProjectID'
* }, {
* platform: 'android',
* purpose: 'debug'
* }).then(
* () => { //Build the project },
* (err) => { //Cannot build the project }
* );
*
* @return {Promise}
*/
Monaca.prototype.checkBuildAvailability = function(projectInfo, buildParams) {
if (!projectInfo || !projectInfo.projectId) {
return Q.reject(new Error("Missing project info data."));
} else if(!buildParams || !buildParams.platform || !buildParams.purpose) {
return Q.reject(new Error("Missing build parameters."));
}
var projectId = projectInfo.projectId,
platform = buildParams.platform,
buildType = buildParams.purpose;
let checkForMinimumRequirements = !!buildParams.checkForMinimumRequirements;
return this._get('/project/' + projectId + '/can_build_app')
.then(
function(data) {
var body = this._safeParse(data.body);
if (body.status === 'ok') {
var checkError = function() {
var platformContent = body.result[platform];
if (!platformContent) {
return 'Specified platform is not supported or doesn\'t exist.';
}
if (!platformContent.has_remaining_slot) {
return 'Your plan does not allow further builds at the moment. Please upgrade your account to start build, or try again later.';
}
if (!platformContent.is_start_file_exist) {
return 'Your project is missing the startup file (usually index.html).';
}
// electron platform
if (platform.startsWith('electron')) {
if (platformContent.has_valid_cordova_version === false) return 'This cordova version is not supported.';
if (platformContent.has_valid_icon === false) return 'The icon is invalid. Icon should be at least 512x512 pixels.'
}
if (typeof platformContent.can_build_for[buildType] === 'undefined') {
return platform + ' ' + buildType + ' build is not supported or doesn\'t exist.';
}
// android platform
if (platform === 'android') {
if (!platformContent.is_versionname_valid) {
return 'Version name is invalid.';
}
if (buildType === 'release' && (!checkForMinimumRequirements && !platformContent.has_keysetting || !checkForMinimumRequirements && !platformContent.has_keystore)) {
return 'Missing KeyStore configuration. Configure remote build by executing `monaca remote build --browser`.';
}
}
// ios platform
if (platform === 'ios') {
if (!platformContent.has_splash_and_icons) {
return 'Your project is missing splash screens and/or icons. Please add the missing files from remote build settings by executing `monaca remote build --browser`.';
}
if (buildType === 'debug') {
if (!platformContent.has_dev_provisioning) {
return 'Missing dev provisioning file. Please upload it from remote build settings by executing `monaca remote build --browser`.';
}
if (platformContent.dev_provisioning_error) {
return 'Error in dev provisioning file. Please upload again from remote build settings by executing `monaca remote build --browser`.';
}
} else if (buildType === 'debugger') {
if (!platformContent.has_debug_provisioning) {
return 'Missing debug provisioning file. Please upload it from remote build settings by executing `monaca remote build --browser`.';
}
if (platformContent.debug_provisioning_error) {
return 'Error in debug provisioning file. Please upload again from remote build settings by executing `monaca remote build --browser`.';
}
} else if (buildType === 'simulator') {
return ''; // no need to check provisioning file.
} else {
if (!platformContent['has_' + buildType + '_provisioning']) {
return 'Missing ' + buildType + ' provisioning file. Please upload it from remote build settings by executing `monaca remote build --browser`.';
}
if (platformContent[buildType + '_provisioning_error']) {
return 'Error in' + buildType + ' provisioning file. Please upload again from remote build settings by executing `monaca remote build --browser`.';
}
}
}
return '';
};
var errorMessage = checkError();
if (errorMessage) {
return Q.reject(new Error(errorMessage));
}
return Q.resolve(body);
} else {
return Q.reject(new Error(body.status + " - " + body.message));
}
}.bind(this),
function(err) {
var errMessage = err.message ? err.message : '';
if (err.code === 404) {
return Q.reject(new Error("Error 404. Cannot reach the server, contact Monaca Support."));
} else {
return Q.reject(new Error("Internal server error, contact Monaca Support. " + errMessage));
}
}
);
};
/**
* @method
* @memberof Monaca
* @description
* Generate a one time token for an URL.
* @param {string} url
* @return {Promise}
*/
Monaca.prototype.getSessionUrl = function(url) {
var deferred = Q.defer();
this._get('/user/getSessionUrl', { url: url }).then(
function(data) {
deferred.resolve(this._safeParse(data.body).result.url);
}.bind(this),
function(error) {
deferred.reject(error);
}
);
return deferred.promise;
};
/**
* @method
* @memberof Monaca
* @description
* Downloads a file from Monaca request.
*
* If the download is successful the promise will resolve with the
* downloaded filename.
* @param {string} url - URL to download from
* @param {object} data - Request parameters
* @param {string} filename - Filename the data will be saved to. Can be a callback (promise) function.
* @return {Promise}
*/
Monaca.prototype.download = function(url, data, filename) {
var deferred = Q.defer();
var getFilename = (filename, response) => {
var d = Q.defer();
if (typeof filename === 'string') {
d.resolve(filename);
} else if (typeof filename === 'function') {
// Callback so that the caller can decide the filename from the response
const dest = filename(response);
if (typeof dest === 'string') {
// if the function return string, return it right away
d.resolve(dest);
} else if (dest != null && dest.then != null && typeof dest.then === 'function') {
// if the function return promise
dest.then(data => {
if (!data) {
d.reject(new Error('Could not get data from dialog'));
} else if (data.canceled) {
d.reject(new Error('User cancel the save dialog'));
} else if (!data.filePath) {
d.reject(new Error('Not a valid file name.'));
} else if (data.filePath) {
d.resolve(data.filePath);
}
});
}
} else {
d.reject(new Error('Not a valid file name.'));
}
return d.promise;
};
this._createRequestClient(data).then(function(requestClient) {
requestClient.get(url)
.on('response', function(response) {
getFilename(filename, response).then(
function (dest) {
if (typeof dest === 'string') {
var file = fs.createWriteStream(dest);
response.pipe(file);
file.on('finish', function() {
deferred.resolve(dest);
});
file.on('error', function(error) {
deferred.reject(error);
});
} else {
deferred.reject(new Error('Not a valid file name.'));
}
},
function (e) {
deferred.reject(e);
}
);
})
}.bind(this),
function(error) {
return deferred.reject(error);
});
return deferred.promise;
}
/**
* @method
* @memberof Monaca
* @name getLatestNews
* @description
* Fetches latest news and status on known issues from Monaca Cloud.
* @param {Object} [options] Parameters
* @param {Boolean} [options.disableStatusUpdate]
* @return {Promise}
*/
Monaca.prototype.getLatestNews = function(options) {
var deferred = Q.defer();
options = options || {};
options.disableStatusUpdate = options.disableStatusUpdate ? 1 : 0;
this._get('/user/info/news', options ? options : {} ).then(
function(data) {
deferred.resolve(this._safeParse(data.body));
}.bind(this),
function(error) {
deferred.reject(error);
}
);
return deferred.promise;
};
/**
* @method
* @memberof Monaca
* @description
* Fetch a list of all available projects.
* @return {Promise}
* @example
* monaca.getProjects().then(
* function(projects) {
* console.log('You have ' + projects.length + ' projects!');
* },
* function(error) {
* // Unable to fetch list.
* }
* );
*/
Monaca.prototype.getProjects = function() {
var deferred = Q.defer();
this._get('/user/projects').then(
function(data) {
deferred.resolve(this._safeParse(data.body).result.items);
}.bind(this),
function(error) {
deferred.reject(error);
}
);
return deferred.promise;
};
/**
* @method
* @memberof Monaca
* @description
* Get project ID.
* @return {Promise}
*/
Monaca.prototype.getProjectId = function(projectDir) {
return localProperties.get(projectDir, 'project_id');
};
/**
* @method
* @memberof Monaca
* @description
* Delete project ID.
* @return {Promise}
*/
Monaca.prototype.deleteProjectId = function(projectDir) {
return localProperties.del(projectDir, 'project_id');
};
/**
* @method
* @memberof Monaca
* @description
* Get local project ID.
* @return {Promise}
*/
Monaca.prototype.getLocalProjectId = function(projectDir) {
var deferred = Q.defer(),
absolutePath = path.resolve(projectDir);
try {
var projectId = crypto.createHash('sha256').update(absolutePath).digest('hex');
deferred.resolve(projectId);
}
catch (error) {
deferred.reject(error);
}
return deferred.promise;
};
/**
* @method
* @memberof Monaca
* @description
* Set project ID.
* @return {Promise}
*/
Monaca.prototype.setProjectId = function(projectDir, projectId) {
return localProperties.set(projectDir, 'project_id', projectId);
};
/**
* @method
* @memberof Monaca
* @description
* Fetch a list of files and directories for a project.
* Must be logged in to use.
* @param {string} projectId
* @param {Array} ignoreList
* @return {Promise}
* @example
* monaca.getRemoteProjectFiles('SOME_PROJECT_ID', ['node_modules', '.git']).then(
* function(files) {
* // Fetched file list!
* },
* function(error) {
* // Failed fetching file list!
* }
* );
*/
Monaca.prototype.getRemoteProjectFiles = function(projectId, ignoreList) {
var deferred = Q.defer();
utils.info('Reading Remote Files...', deferred, spinner);
let readDirRecursive = (path) => {
let readDirQueue = [],
fileList = {};
let readDir = (path) => {
let getItemList = (path) => {
return new Promise((resolve,reject) => {
utils.spinnerLoading(spinner, 'Reading Remote File: ' + path);
this.getRemoteProjectFilesByPath(projectId, path)
.then(files => {
return resolve(files);
})
.catch(err => {
return reject(err);
});
});
}
let processItemList = (files) => {
if (files && !utils.isEmptyObject(files)) {
files = utils.filterIgnoreFiles(files, ignoreList, true);
for (let fileKey in files) {
let file = files[fileKey];
fileList[fileKey] = file;
if (file.type === 'dir') {
readDirQueue.push(fileKey);
continue;
}
}
}
if (readDirQueue.length > 0) {
return readDir(readDirQueue.shift());
}
return fileList;
}
return getItemList(path)
.then(processItemList);
}
return readDir(path);
}
readDirRecursive('/')
.then(itemList => {
utils.info('Reading Remote Files [FINISHED]', deferred, spinner);
deferred.resolve(itemList);
})
.catch(error => {
utils.info('Reading Remote Files [ERROR]', deferred, spinner);
if (error && (error.code === 404 || error.message === 'Not found')) utils.info(`\nThis project (${projectId}) is not existed in cloud.`, deferred);
if (error && (error === 500 || error.code === 500)) utils.info(`\nIt seems that there is problem with this project (${projectId}).\nPlease verify 'project_id' in '.monaca/local_properties.json' and this project in the cloud.`, deferred);
deferred.reject(error);
});
return deferred.promise;
};
/**
* @method
* @memberof Monaca
* @description
* Fetch a list of files and directories for a project.
* Must be logged in to use.
* @param {string} projectId
* @param {string} path
* @return {Promise}
* @example
* monaca.getRemoteProjectFilesByPath('SOME_PROJECT_ID', '/').then(
* function(files) {
* // Fetched file list!
* },
* function(error) {
* // Failed fetching file list!
* }
* );
*/
Monaca.prototype.getRemoteProjectFilesByPath = function(projectId, path = '/') {
return new Promise((resolve, reject) => {
this._post('/project/' + projectId + '/file/tree/byPath/' + encodeURIComponent(path), {
path: path
})
.then( data => {
let files = null;;
if (data && data.body) files = JSON.parse(data.body).result.items;
resolve(files);
})
.catch( err => {
reject(err);
});
});
}
/**
* @method
* @memberof Monaca
* @description
* Fetch a list of files and directories for a project.
* Must be logged in to use.
* @param {string} projectId
* @return {Promise}
* @example
* monaca.getProjectFiles('SOME_PROJECT_ID').then(
* function(files) {
* // Fetched file list!
* },
* function(error) {
* // Failed fetching file list!
* }
* );
*/
Monaca.prototype.getProjectFiles = function(projectId) {
var deferred = Q.defer();
utils.info('Reading Remote Files...', deferred);
this._post('/project/' + projectId + '/file/tree', {ignoredNodeModules: true}).then(
function(data) {
utils.info('Reading Remote Files [FINISHED]', deferred);
deferred.resolve(JSON.parse(data.body).result.items);
},
function(error) {
utils.info('Reading Remote Files [ERROR]', deferred);
if (error && (error.code === 404 || error.message === 'Not found')) utils.info(`\nThis project (${projectId}) is not existed in cloud.`, deferred);
if (error && (error === 500 || error.code === 500)) utils.info(`\nIt seems that there is problem with this project (${projectId}).\nPlease verify 'project_id' in '.monaca/local_properties.json' and this project in the cloud.`, deferred);
deferred.reject(error);
}
);
return deferred.promise;
};
/**
* @method
* @memberof Monaca
* @description
* Fetch a list of files and directories for a local project.
* @param {string} projectDir - Path to project.
* @param {Object} [options] Parameters like filter to filter from list of files.
* @return {Promise}
* @example
* monaca.getLocalProjectFiles = function('/some/directory').then(
* function(files) {
* // Successfully fetched file list!
* },
* function(error) {
* // Failed fetching file list!
* }
* );
*/
Monaca.prototype.getLocalProjectFiles = function(projectDir, options) {
let deferred = Q.defer();
let qLimit = qlimit(100);
let getFileChecksum = qLimit(function(file, isSymbolicLink) {
let deferred = Q.defer();
utils.spinnerLoading(spinner, 'Reading Local File: ' + file);
if (isSymbolicLink) {
fs.readlink(file, function(error, data) {
if (error) {
deferred.reject(error);
}
else {
deferred.resolve(crc32(data).toString('hex'));
}
});
} else {
fs.readFile(file, function(error, data) {
if (error) {
deferred.reject(error);
}
else {
deferred.resolve(crc32(data).toString('hex'));
}
});
}
return deferred.promise;
});
utils.info('Reading Local Files...', deferred, spinner);
fs.exists(projectDir, function(exists) {
if (exists) {
let files = {},
promises = [],
filteredList = [];
try {
// create .monacaignore if there isn't
let ignoreList = this._filterMonacaIgnore(projectDir);
// Read all files from project directory
filteredList = glob.sync("**/*",
{
cwd: path.resolve(projectDir),
dot: true,
}
);
// filter list from .monacaignore
filteredList = utils.filter(filteredList, ignoreList);
// user-defined filter functions
if (options && options.filter && typeof options.filter === 'function') {
filteredList = filteredList.filter(options.filter);
}
} catch(error) {
return deferred.reject(error);
}
utils.info('Reading Local Files [Calculating File Checksum]', deferred, spinner);
filteredList.forEach(function(file) {
let obj = {},
key = path.join('/', file);
// Converting Windows path delimit