diff --git a/.gitignore b/.gitignore index c4fa1f63..c8f8d743 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,8 @@ pids # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git node_modules -coverage \ No newline at end of file +coverage + +# vs code +.history +.idea \ No newline at end of file diff --git a/README.md b/README.md index 5d72439a..1611e8db 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,6 @@ CodePush Server is a CodePush progam server! microsoft CodePush cloud is slow in - oss *storage bundle file in [aliyun](https://www.aliyun.com/product/oss)* - tencentcloud *storage bundle file in [tencentcloud](https://cloud.tencent.com/product/cos)* -## qq交流群 - -- QQ群: 628921445 -- QQ群: 535491067 - ## 正确使用code-push热更新 - 苹果App允许使用热更新[Apple's developer agreement](https://developer.apple.com/programs/ios/information/iOS_Program_Information_4_3_15.pdf), 为了不影响用户体验,规定必须使用静默更新。 Google Play不能使用静默更新,必须弹框告知用户App有更新。中国的android市场必须采用静默更新(如果弹框提示,App会被“请上传最新版本的二进制应用包”原因驳回)。 diff --git a/app.js b/app.js index 7c7d20cf..ee93e3cc 100644 --- a/app.js +++ b/app.js @@ -9,6 +9,7 @@ var _ = require('lodash'); var fs = require('fs'); var routes = require('./routes/index'); +var indexV1 = require('./routes/indexV1'); var auth = require('./routes/auth'); var accessKeys = require('./routes/accessKeys'); var account = require('./routes/account'); @@ -72,6 +73,7 @@ if (_.get(config, 'common.storageType') === 'local') { } app.use('/', routes); +app.use('/v0.1/public/codepush', indexV1); app.use('/auth', auth); app.use('/accessKeys', accessKeys); app.use('/account', account); diff --git a/config/config.js b/config/config.js index 07c2deec..0789b5ee 100644 --- a/config/config.js +++ b/config/config.js @@ -20,6 +20,14 @@ config.development = { bucketName: "", downloadUrl: "" // Binary files download host address. }, + // Config for upyun (https://www.upyun.com/) storage when storageType value is "upyun" + upyun: { + storageDir: process.env.UPYUN_STORAGE_DIR, + serviceName: process.env.UPYUN_SERVICE_NAME, + operatorName: process.env.UPYUN_OPERATOR_NAME, + operatorPass: process.env.UPYUN_OPERATOR_PASS, + downloadUrl: process.env.DOWNLOAD_URL, + }, // Config for Amazon s3 (https://aws.amazon.com/cn/s3/) storage when storageType value is "s3". s3: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, diff --git a/core/services/client-manager.js b/core/services/client-manager.js index 54880869..87a16625 100644 --- a/core/services/client-manager.js +++ b/core/services/client-manager.js @@ -125,8 +125,10 @@ proto.updateCheck = function(deploymentKey, appVersion, label, packageHash, clie downloadUrl: "", description: "", isAvailable: false, + isDisabled: true, isMandatory: false, appVersion: appVersion, + targetBinaryRange: "", packageHash: "", label: "", packageSize: 0, @@ -178,9 +180,11 @@ proto.updateCheck = function(deploymentKey, appVersion, label, packageHash, clie && _.eq(packages.deployment_id, deploymentsVersions.deployment_id) && !_.eq(packages.package_hash, packageHash)) { rs.packageId = packageId; + rs.targetBinaryRange = deploymentsVersions.app_version; rs.downloadUrl = rs.downloadURL = common.getBlobDownloadUrl(_.get(packages, 'blob_url')); rs.description = _.get(packages, 'description', ''); rs.isAvailable = _.eq(packages.is_disabled, 1) ? false : true; + rs.isDisabled = _.eq(packages.is_disabled, 1) ? true : false; rs.isMandatory = _.eq(packages.is_mandatory, 1) ? true : false; rs.appVersion = appVersion; rs.packageHash = _.get(packages, 'package_hash', ''); @@ -278,7 +282,7 @@ proto.reportStatusDeploy = function (deploymentKey, label, clientUniqueId, other if (_.isEmpty(metrics)) { return; } - if (constConfig.DEPLOYMENT_SUCCEEDED) { + if (_.eq(status, constConfig.DEPLOYMENT_SUCCEEDED)) { return metrics.increment(['installed', 'active'],{by: 1}); } else { return metrics.increment(['installed', 'failed'],{by: 1}); diff --git a/core/utils/common.js b/core/utils/common.js index e7f7db08..6d221535 100644 --- a/core/utils/common.js +++ b/core/utils/common.js @@ -7,6 +7,7 @@ var config = require('../config'); var _ = require('lodash'); var validator = require('validator'); var qiniu = require("qiniu"); +var upyun = require('upyun'); var common = {}; var AppError = require('../app-error'); var jschardet = require("jschardet"); @@ -225,6 +226,8 @@ common.uploadFileToStorage = function (key, filePath) { return common.uploadFileToOSS(key, filePath); } else if (storageType === 'qiniu') { return common.uploadFileToQiniu(key, filePath); + } else if (storageType === 'upyun') { + return common.uploadFileToUpyun(key, filePath); } else if (storageType === 'tencentcloud') { return common.uploadFileToTencentCloud(key, filePath); } @@ -350,6 +353,43 @@ common.uploadFileToQiniu = function (key, filePath) { }); }; +common.uploadFileToUpyun = function (key, filePath) { + var serviceName = _.get(config, "upyun.serviceName"); + var operatorName = _.get(config, "upyun.operatorName"); + var operatorPass = _.get(config, "upyun.operatorPass", ""); + var storageDir = _.get(config, "upyun.storageDir", ""); + var service = new upyun.Service(serviceName, operatorName, operatorPass); + var client = new upyun.Client(service); + return ( + new Promise((resolve, reject) => { + client.makeDir(storageDir).then(result => { + if(!storageDir) { + reject(new AppError.AppError('Please config the upyun remoteDir!')); + return; + } + let remotePath = storageDir + '/' + key; + log.debug('uploadFileToUpyun remotePath:', remotePath); + log.debug('uploadFileToUpyun mkDir result:', result); + client.putFile(remotePath, fs.createReadStream(filePath)).then(data => { + log.debug('uploadFileToUpyun putFile response:', data); + if(data) { + resolve(key) + } else { + log.debug('uploadFileToUpyun putFile failed!', data); + reject(new AppError.AppError('Upload file to upyun failed!')); + } + }).catch(e1 => { + log.debug('uploadFileToUpyun putFile exception e1:', e1); + reject(new AppError.AppError(JSON.stringify(e1))); + }) + }).catch(e => { + log.debug('uploadFileToUpyun putFile exception e:', e); + reject(new AppError.AppError(JSON.stringify(e))); + }); + }) + ); +}; + common.uploadFileToS3 = function (key, filePath) { var AWS = require('aws-sdk'); return ( diff --git a/core/utils/security.js b/core/utils/security.js index b6bab402..a4054acd 100644 --- a/core/utils/security.js +++ b/core/utils/security.js @@ -69,7 +69,7 @@ security.packageHashSync = function (jsonData) { log.debug('packageHashSync manifestData:', manifestData); var manifestString = JSON.stringify(manifestData.sort()); manifestString = _.replace(manifestString, /\\\//g, '/'); - log.debug('packageHashSync manifestString:', manifestData); + log.debug('packageHashSync manifestString:', manifestString); return security.stringSha256Sync(manifestString); } @@ -202,6 +202,10 @@ security.calcAllFileSha256 = function (directoryPath) { var data = {}; _.forIn(results, (value, key) => { var relativePath = path.relative(directoryPath, key); + var matchresult = relativePath.match(/(\/|\\).*/); + if (matchresult) { + relativePath = path.join('CodePush', matchresult[0]); + } relativePath = slash(relativePath); data[relativePath] = value; }); diff --git a/docs/README.md b/docs/README.md index dc455f8c..9419e94d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -236,15 +236,3 @@ edit config.xml. add code below. ``` - -## Use [CodePush Web](https://github.com/lisong/code-push-web) manage apps - -> add codePushWebUrl config in config.js - -eg. - -```javascript -"common": { - "codePushWebUrl": "Your CodePush Web address", -} -``` diff --git a/docs/process.json b/docs/process.json index de88022b..8ef0e3e1 100644 --- a/docs/process.json +++ b/docs/process.json @@ -10,6 +10,14 @@ "NODE_ENV" : "production", "PORT" : 3000, "CONFIG_FILE" : "/path/to/production/config.js" + + // Must set add config when STORAGE_TYPE is upyun + // "STORAGE_TYPE" : "upyun", + // "DOWNLOAD_URL" : "", + // "UPYUN_STORAGE_DIR" : "", + // "UPYUN_SERVICE_NAME" : "", + // "UPYUN_OPERATOR_NAME" : "", + // "UPYUN_OPERATOR_PASS" : "" } } ] diff --git a/package-lock.json b/package-lock.json index ef06fecf..79668d14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -255,6 +255,22 @@ "resolved": "http://registry.npm.taobao.org/aws4/download/aws4-1.6.0.tgz", "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" }, + "axios": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz", + "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==", + "requires": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" + } + } + }, "babel-runtime": { "version": "6.26.0", "resolved": "http://registry.npm.taobao.org/babel-runtime/download/babel-runtime-6.26.0.tgz", @@ -285,6 +301,11 @@ "resolved": "http://registry.npm.taobao.org/balanced-match/download/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=" + }, "base64-js": { "version": "1.2.3", "resolved": "http://registry.npm.taobao.org/base64-js/download/base64-js-1.2.3.tgz", @@ -452,6 +473,11 @@ "is-regex": "^1.0.3" } }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" + }, "circular-json": { "version": "0.5.5", "resolved": "http://registry.npm.taobao.org/circular-json/download/circular-json-0.5.5.tgz", @@ -668,6 +694,11 @@ "which": "^1.2.9" } }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" + }, "cryptiles": { "version": "3.1.2", "resolved": "http://registry.npm.taobao.org/cryptiles/download/cryptiles-3.1.2.tgz", @@ -1081,6 +1112,14 @@ "locate-path": "^3.0.0" } }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + } + }, "forever-agent": { "version": "0.6.1", "resolved": "http://registry.npm.taobao.org/forever-agent/download/forever-agent-0.6.1.tgz", @@ -1314,6 +1353,11 @@ "resolved": "http://registry.npm.taobao.org/hide-powered-by/download/hide-powered-by-1.0.0.tgz", "integrity": "sha1-SoWtZYgfYoV/xwr3F0oRhNzM4ys=" }, + "hmacsha1": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hmacsha1/-/hmacsha1-1.0.0.tgz", + "integrity": "sha1-wbeuA6TqEWNICQrxT4FIwSk4qRc=" + }, "hoek": { "version": "4.2.1", "resolved": "http://registry.npm.taobao.org/hoek/download/hoek-4.2.1.tgz", @@ -1962,6 +2006,16 @@ "xregexp": "^2.0.0" } }, + "md5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", + "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", + "requires": { + "charenc": "~0.0.1", + "crypt": "~0.0.1", + "is-buffer": "~1.1.1" + } + }, "mdurl": { "version": "1.0.1", "resolved": "http://registry.npm.taobao.org/mdurl/download/mdurl-1.0.1.tgz", @@ -3450,6 +3504,20 @@ "resolved": "http://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "upyun": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/upyun/-/upyun-3.3.9.tgz", + "integrity": "sha512-UqvSKvvFgbQwLI+yjTO0WUnmS2i2KIvX4N2Je6ZTRN4cja17DHX7ARd13DO6h65/JOQJPs/Nua2zlnixNFIdWA==", + "requires": { + "axios": "^0.18.0", + "base-64": "^0.1.0", + "form-data": "^2.1.4", + "hmacsha1": "^1.0.0", + "is-promise": "^2.1.0", + "md5": "^2.2.1", + "mime-types": "^2.1.15" + } + }, "url": { "version": "0.10.3", "resolved": "http://registry.npm.taobao.org/url/download/url-0.10.3.tgz", diff --git a/package.json b/package.json index 6e60a383..1e3fc19a 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "nodemailer": "^4.0.1", "pug": "^2.0.1", "qiniu": "^7.1.3", + "upyun": "^3.3.9", "rand-token": "^0.4.0", "recursive-readdir": "^2.1.1", "redis": "^2.6.2", diff --git a/routes/apps.js b/routes/apps.js index 8edc39a9..8fd7d832 100644 --- a/routes/apps.js +++ b/routes/apps.js @@ -171,7 +171,7 @@ router.get('/:appName/deployments/:deploymentName/history', return deployments.getDeploymentHistory(deploymentInfo.id); }) .then((rs) => { - res.send({history: rs}); + res.send({history: _.pullAll(rs, [null, false])}); }) .catch((e) => { if (e instanceof AppError.AppError) { diff --git a/routes/indexV1.js b/routes/indexV1.js new file mode 100644 index 00000000..1720f1a8 --- /dev/null +++ b/routes/indexV1.js @@ -0,0 +1,89 @@ +var express = require('express'); +var router = express.Router(); +var Promise = require('bluebird'); +var AppError = require('../core/app-error'); +var middleware = require('../core/middleware'); +var ClientManager = require('../core/services/client-manager'); +var _ = require('lodash'); +var log4js = require('log4js'); +var log = log4js.getLogger("cps:indexV1"); + +router.get('/update_check', (req, res, next) => { + var deploymentKey = _.get(req, "query.deployment_key"); + var appVersion = _.get(req, "query.app_version"); + var label = _.get(req, "query.label"); + var packageHash = _.get(req, "query.package_hash") + var isCompanion = _.get(req, "query.is_companion") + var clientUniqueId = _.get(req, "query.client_unique_id") + var clientManager = new ClientManager(); + log.debug('req.query', req.query); + clientManager.updateCheckFromCache(deploymentKey, appVersion, label, packageHash, clientUniqueId) + .then((rs) => { + //灰度检测 + return clientManager.chosenMan(rs.packageId, rs.rollout, clientUniqueId) + .then((data)=>{ + if (!data) { + rs.isAvailable = false; + return rs; + } + return rs; + }); + }) + .then((rs) => { + delete rs.packageId; + delete rs.rollout; + var update_info = { + download_url : rs.downloadUrl, + description : rs.description, + is_available : rs.isAvailable, + is_disabled : rs.isDisabled, + target_binary_range: rs.targetBinaryRange, + label: rs.label, + package_hash: rs.packageHash, + package_size: rs.packageSize, + should_run_binary_version: rs.shouldRunBinaryVersion, + update_app_version: rs.updateAppVersion, + is_mandatory: rs.isMandatory, + }; + res.send({"update_info": update_info}); + }) + .catch((e) => { + if (e instanceof AppError.AppError) { + res.status(404).send(e.message); + } else { + next(e); + } + }); +}); + +router.post('/report_status/download', (req, res) => { + log.debug('req.body', req.body); + var clientUniqueId = _.get(req, "body.client_unique_id"); + var label = _.get(req, "body.label"); + var deploymentKey = _.get(req, "body.deployment_key"); + var clientManager = new ClientManager(); + clientManager.reportStatusDownload(deploymentKey, label, clientUniqueId) + .catch((err) => { + if (!err instanceof AppError.AppError) { + console.error(err.stack) + } + }); + res.send('OK'); +}); + +router.post('/report_status/deploy', (req, res) => { + log.debug('req.body', req.body); + var clientUniqueId = _.get(req, "body.client_unique_id"); + var label = _.get(req, "body.label"); + var deploymentKey = _.get(req, "body.deployment_key"); + var clientManager = new ClientManager(); + clientManager.reportStatusDeploy(deploymentKey, label, clientUniqueId, req.body) + .catch((err) => { + if (!err instanceof AppError.AppError) { + console.error(err.stack) + } + }); + res.send('OK'); +}); + +module.exports = router;