游戏热更新插件

This commit is contained in:
andrewlu
2021-02-02 00:11:00 +08:00
parent ba874af797
commit 3106b54566
40 changed files with 34326 additions and 37 deletions

View File

@@ -0,0 +1,17 @@
{
"versionCfg": {
"versionCode": "3",
"versionName": "1.0.2",
"versionType": "dev",
"versionLog": "adfadsfdsfdsfsdf",
"baseUrl": "https://cdn-awdxc.8zy.com/"
},
"ftpCfg": {
"host": "106.55.246.140",
"port": "2121",
"user": "cos1",
"password": "dkJrWrVy11",
"rootPath": "temp001/",
"keepalive": 100000
}
}

View File

@@ -0,0 +1,155 @@
'use strict';
const fs = require('fs');
const FileUtil = require('./FileUtils');
const Client = require("ftp");
class FileUploader {
ftps = {};
maxClients = 3;
options = {};
queue = [];
setOption(options, maxThread) {
this.options = options;
this.maxClients = maxThread || 1;
}
prepare() {
const currLen = Object.keys(this.ftps).length;
logger.info('准备创建上传线程:', this.maxClients, currLen);
if (currLen < this.maxClients) {
for (let i = this.maxClients - Object.keys(this.ftps).length; i > 0; i--) {
let name = "Thread-" + Math.random();
this.ftps[name] = new UploadThread(this.options);
}
}
}
destroy() {
for (let id in this.ftps) {
this.ftps[id].destroy();
delete this.ftps[id];
}
}
/**
* 上传文件、目录到ftp服务器。
* @param source
* @param dst
*/
upload(source, dst) {
const st = fs.statSync(source);
if (st.isFile()) {
this.queue.push({src: source, dst: dst})
} else if (st.isDirectory()) {
const files = FileUtil.listDirsAndFiles(source);
const startIndex = (source.endsWith('\\') || source.endsWith('/')) ? source.length - 1 : source.length;
const remoteStr = (dst.endsWith('\\') || dst.endsWith('/')) ? dst.substring(0, dst.length - 1) : dst;
for (let i of files.files) {
//bugfix: 这里目标文件路径必须是 linux文件路径.否则ftp会做为普通文件直接上传,而不包含目录.
this.queue.push({src: i, dst: (remoteStr + i.substring(startIndex)).replace(/\\/g, '/')});
}
}
this.checkUpload();
}
async checkUpload() {
// 准备上传线程.
this.prepare();
let once = null;
while ((once = this.queue.shift()) != null) {
const readyFtps = Object.values(this.ftps);
const index = Math.floor(Math.random() * readyFtps.length);
readyFtps[index].addTask(once);
}
}
}
class UploadThread {
ftp = null;
queue = [];
ready = false;
stopped = false;
options = null;
constructor(options) {
this.options = options;
this.ftp = this.newConnection();
this.run();
}
addTask(once) {
this.queue.push(once);
if (this.stopped) {
this.ftp = this.newConnection();
}
}
newConnection() {
this.ready = false;
const ftp = new Client();
ftp.on('ready', () => {
logger.log('ftp ready');
this.ready = true;
});
ftp.on('close', () => {
logger.log('ftp client has close');
this.ready = false;
});
ftp.on('end', () => {
logger.log('ftp client has end');
this.ready = false;
});
ftp.on('error', (err) => {
logger.log('ftp client has an error : ', JSON.stringify(err));
this.ready = false;
});
ftp.connect(this.options);
return ftp;
}
destroy() {
this.stopped = true;
}
async sleep(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
// 定时循环.直到destroy()执行.
async run() {
while (!this.stopped) {
if (!this.ready || this.queue.length <= 0) {
await this.sleep(500);
continue;
}
const once = this.queue[0];
const err = await this.uploadOnce(this.ftp, once);
if (!err) {
this.queue.shift();
logger.info('上传完成:', once.src);
continue;
} else {
logger.warn('上传失败,准备重试:', once.src);
this.ftp.end();
// 上传失败有可能是线程强制结束导致的上传失败.
if (!this.stopped) {
this.ftp = this.newConnection();
}
}
}
this.ftp.end();
}
uploadOnce(ftp, once) {
return new Promise(resolve => {
ftp.put(once.src, once.dst, false, resolve)
});
}
}
module.exports = function () {
return new FileUploader();
}

View File

@@ -0,0 +1,75 @@
const fs = require('fs')
const path = require('path')
// console adapter.
global.logger = global.logger || {};
logger.log = (Editor && Editor.log) || console.log;
logger.info = (Editor && Editor.info) || console.info;
logger.warn = (Editor && Editor.warn) || console.warn;
logger.error = (Editor && Editor.error) || console.error;
logger.success = (Editor && Editor.success) || (Editor && Editor.info) || console.log;
class FileUtil {
/*
* 获取window上的文件目录以及文件列表信息
* @param { String } localDir 本地路径
* @return { files, dirs }
* */
listDirsAndFiles(localDir) {
const dirs = []
const files = []
const dir = fs.readdirSync(localDir)
for (let i = 0; i < dir.length; i++) {
const p = path.join(localDir, dir[i])
const stat = fs.statSync(p)
if (stat.isDirectory()) {
dirs.push(p)
const children = this.listDirsAndFiles(p)
dirs.push(...children.dirs)
files.push(...children.files)
} else {
files.push(p)
}
}
return {
files,
dirs
}
}
/**
* 拷贝文件或目录。
* @param src
* @param dst
*/
copy(src, dst) {
const st = fs.statSync(src);
if (st.isFile()) {
var readable = fs.createReadStream(src);//创建读取流
var writable = fs.createWriteStream(dst);//创建写入流
readable.pipe(writable);
return;
} else if (!st.isDirectory()) {
return;
}
if (!fs.existsSync(dst)) {
fs.mkdirSync(dst, {recursive: true});
}
//读取目录
const paths = fs.readdirSync(src);
for (let path of paths) {
this.copy(src + '/' + path, dst + '/' + path);
}
};
write(filePath, content) {
const pathInfo = path.parse(filePath);
// console.log('写文件:', filePath, pathInfo.dir)
if (!fs.existsSync(pathInfo.dir)) {
fs.mkdirSync(pathInfo.dir, {recursive: true});
}
fs.writeFileSync(filePath, content, 'utf-8');
}
}
module.exports = new FileUtil();

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,90 @@
'use strict';
const fs = require('fs');
const {join} = require('path');
const FileUtil = require('./js/FileUtils');
const FileUpload = require('./js/FileUploader');
// 生成更新文件.
const doMakeUpdatePackage = function (opt, cb) {
if (opt.platform == 'android' || opt.platform == 'ios') {
try {
const configStr = fs.readFileSync(
Editor.url('packages://update-manager/.settings.conf'), 'utf-8');
const configJson = JSON.parse(configStr || "{}");
if (typeof configJson.versionCfg.versionCode === 'undefined') {
logger.warn("未配置更新版本,不生成热更Patch");
cb && cb();
return;
}
if (!configJson.versionCfg.versionType || configJson.versionCfg.versionType.length <= 0) {
configJson.versionCfg.versionType = 'dev';
}
const bundleVers = {};
for (let b of opt.bundles) {
bundleVers[b.name] = b.version;
}
saveNewVersionInfo(opt, bundleVers, configJson);
const remoteDir = join(opt.dest, 'remote');
const assetDir = join(opt.dest, 'assets');
FileUtil.copy(assetDir, remoteDir);
cb && cb();
} catch (e) {
logger.log("未生成更新包", e);
cb && cb();
}
} else {
cb && cb();
}
};
const saveNewVersionInfo = function (opt, bundleVers, configJson) {
const data = {
versionCode: configJson.versionCfg.versionCode,
versionName: configJson.versionCfg.versionName,
server: `${configJson.versionCfg.baseUrl}${configJson.ftpCfg.rootPath}${configJson.versionCfg.versionType}/${configJson.versionCfg.versionCode}`
};
const updateInfo = Object.assign(data, {bundles: bundleVers})
// 将版本更新信息写入文件
let targetUpdatePath = join(opt.dest, `remote/update-${configJson.versionCfg.versionType}.json`)
FileUtil.write(targetUpdatePath, JSON.stringify(updateInfo, null, 2));
logger.log("更新信息存储位置:", targetUpdatePath);
};
module.exports = {
load() {
Editor.Builder.on('build-finished', doMakeUpdatePackage);
try {
fs.readFileSync(
Editor.url('packages://update-manager/.settings.conf'), 'utf-8');
} catch (e) {
fs.writeFileSync(
Editor.url('packages://update-manager/.settings.conf'), JSON.stringify({}, null, 2), 'utf-8');
}
},
unload() {
Editor.Builder.removeListener('build-finished', doMakeUpdatePackage);
},
messages: {
'open'() {
Editor.Panel.open('update-manager');
},
'upload'(event, dir, dst, options) {
logger.info('upload....');
if (!this.ftp) {
this.ftp = FileUpload();
}
this.ftp.setOption(options, options.maxThread);
this.ftp.upload(dir, dst);
},
'uploadStop'() {
if (this.ftp) {
this.ftp.destroy();
this.ftp = null;
}
}
},
};

View File

@@ -0,0 +1,53 @@
{
"name": "update-manager",
"version": "0.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"ftp": {
"version": "0.3.10",
"resolved": "https://registry.npm.taobao.org/ftp/download/ftp-0.3.10.tgz",
"integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=",
"requires": {
"readable-stream": "1.1.x",
"xregexp": "2.0.0"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.4.tgz?cache=0&sync_timestamp=1606706255906&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Finherits%2Fdownload%2Finherits-2.0.4.tgz",
"integrity": "sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w="
},
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npm.taobao.org/isarray/download/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npm.taobao.org/readable-stream/download/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npm.taobao.org/string_decoder/download/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
},
"xregexp": {
"version": "2.0.0",
"resolved": "https://registry.npm.taobao.org/xregexp/download/xregexp-2.0.0.tgz?cache=0&sync_timestamp=1607403606167&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fxregexp%2Fdownload%2Fxregexp-2.0.0.tgz",
"integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM="
}
}
}

View File

@@ -0,0 +1,22 @@
{
"name": "update-manager",
"version": "0.0.1",
"description": "用于配置热更新信息.",
"author": "Andrewlu",
"main": "main.js",
"main-menu": {
"i18n:MAIN_MENU.package.title/热更新配置": {
"message": "update-manager:open"
}
},
"panel": {
"main": "panel/index.html",
"type": "simple",
"title": "热更新配置",
"width": 500,
"height": 600
},
"dependencies": {
"ftp": "^0.3.10"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,266 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- import CSS -->
<link rel="stylesheet" href="./element-ui/index.css">
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #3646ea;
background-color: #fafcfc;
padding: 35px;
height: 100%;
}
</style>
</head>
<body style="height: 1000px;">
<div id="app">
<el-tabs v-model="tabName">
<el-tab-pane label="版本配置" name="versionConfig" style="height: 400px;">
<el-form ref="version" :model="versionCfg" label-width="100px">
<el-tooltip effect="dark" :content="tip.versionCode" open-delay=500 placement="top">
<el-form-item label="新版本号:">
<el-input type="number" v-model="versionCfg.versionCode" placeholder="1"></el-input>
</el-form-item>
</el-tooltip>
<el-tooltip effect="dark" :content="tip.versionName" open-delay=500 placement="top">
<el-form-item label="版本名称:">
<el-input v-model="versionCfg.versionName" placeholder="1.0.0"></el-input>
</el-form-item>
</el-tooltip>
<el-tooltip effect="dark" :content="tip.versionType" open-delay=500 placement="top">
<el-form-item label="版本类型:">
<el-input v-model="versionCfg.versionType" placeholder="dev"></el-input>
</el-form-item>
</el-tooltip>
<el-tooltip effect="dark" :content="tip.baseUrl" open-delay=500 placement="top">
<el-form-item label="热更地址:">
<el-input v-model="versionCfg.baseUrl"
placeholder="https://cdn-xxx.com"></el-input>
</el-form-item>
</el-tooltip>
<el-tooltip effect="dark" :content="tip.versionLog" open-delay=500 placement="top">
<el-form-item label="版本描述:">
<el-input type="textarea" v-model="versionCfg.versionLog"
placeholder="版本更新内容..."></el-input>
</el-form-item>
</el-tooltip>
</el-form>
</el-tab-pane>
<el-tab-pane label="上传配置" name="ftpConfig" style="height: 400px;">
<el-form ref="ftpCfg" :model="ftpCfg" label-width="100px">
<el-tooltip effect="dark" :content="tip.ip" placement="top" open-delay=500>
<el-form-item label="IP:">
<el-col :span="18">
<el-input type="text" v-model="ftpCfg.host" placeholder="127.0.0.1"></el-input>
</el-col>
<el-col class="line" :span="2">-</el-col>
<el-col :span="4">
<el-input type="number" v-model="ftpCfg.port" placeholder="端口号"></el-input>
</el-col>
</el-form-item>
</el-tooltip>
<el-tooltip effect="dark" :content="tip.user" open-delay=500 placement="top">
<el-form-item label="登录帐号:">
<el-col :span="11">
<el-input type="text" v-model="ftpCfg.user" placeholder="登录帐号"></el-input>
</el-col>
<el-col class="line" :span="2">-</el-col>
<el-col :span="11">
<el-input type="password" v-model="ftpCfg.password" placeholder="密码"></el-input>
</el-col>
</el-form-item>
</el-tooltip>
<el-tooltip effect="dark" content="FTP长连接持续心跳时间" open-delay=500 placement="top">
<el-form-item label="KeepAlive:">
<el-input type="number" v-model="ftpCfg.keepalive" placeholder="10000"></el-input>
</el-form-item>
</el-tooltip>
<el-tooltip effect="dark" content="FTP同时上传文件数,数量越大,上传效率越高<br/>受限于带宽性能,并非越大越好,推荐5~10" open-delay=500
placement="top">
<el-form-item label="并发数量:">
<el-input type="number" v-model="ftpCfg.maxThread" placeholder="5"></el-input>
</el-form-item>
</el-tooltip>
<el-tooltip effect="dark" :content="tip.rootPath" open-delay=500 placement="top">
<el-form-item label="FTP目录:">
<el-input v-model="ftpCfg.rootPath"
placeholder="TempDir/"></el-input>
</el-form-item>
</el-tooltip>
<el-tooltip effect="dark" content="勾选后仅会上传热更描述文件,<br/>需要手动上传remote目录" open-delay=500 placement="top">
<el-form-item label="手动上传:">
<el-switch v-model="ftpCfg.onlyJson"></el-switch>
</el-form-item>
</el-tooltip>
</el-form>
</el-tab-pane>
<el-tab-pane label="使用帮助" name="help" style="height: 400px;">
<div style="height: 300px;">
<el-alert :closable="false"
title="如不需要发布新版本,可无需做任何操作,以正常步骤打包发布即可"
type="info">
</el-alert>
<el-divider content-position="left"></el-divider>
<el-steps direction="vertical" :active="1" space="100px">
<el-step title="版本配置"
description="使用前需要先配置版本信息,并点击保存配置."></el-step>
<el-step title="构建版本"
description="开始构建版本,构建完成后会在构建目录中生成remote文件夹以及update-dev.json更新描述文件"></el-step>
<el-step title="上传更新包"
description="构建完成时,如需发布新版本,则需要点击[上传更新包],更新包会向游戏代码中注入热更逻辑."></el-step>
</el-steps>
</div>
</el-tab-pane>
</el-tabs>
<el-row>
<el-col :span="8">
<el-button type="success" @click="onSave">保存配置信息</el-button>
</el-col>
<el-col :span="8">
<el-button type="danger" @click="onUpload">上传更新包</el-button>
</el-col>
<el-col :span="8">
<el-button type="warning" @click="onStopUpload">终止上传</el-button>
</el-col>
</el-row>
</div>
</body>
<!-- import Vue before Element -->
<script src="../js/vue.2.5.16.js"></script>
<!-- import JavaScript -->
<script src="./element-ui/index.js"></script>
<script>
const fs = require('fs');
const path = require('path');
new Vue({
el: '#app',
data() {
return {
tabName: "versionConfig",
projectPath: "",
versionCfg: {
versionCode: 1,
versionName: "1.0.0",
versionType: "dev",
versionLog: "",
baseUrl: ""
},
ftpCfg: {
host: "127.0.0.1",
port: 21,
user: "",
password: "",
rootPath: "",
keepalive: 100000,
onlyJson: false,
maxThread: 5
},
tip: {
versionCode: "递增版本号,如果与旧版本号相同,则会替换线上版本包.",
versionName: "版本号名称,示例:1.0.0, 用于游戏内显示",
versionType: "版本类型,用于标记当前版本所处的线上环境类型,<br/>如灰度包,内测包,发布包等,不同类型的版本物理隔离,不会相互影响",
versionLog: "版本发布日志,用于游戏内显示更新内容.",
baseUrl: "FTP上传服务器对应的CDN访问URL,仅需要填写根域名即可.",
ip: "FTP上传IP地址,示例:127.0.0.1",
port: "FTP端口号,默认21",
user: "FTP登录验证帐号及密码",
rootPath: "FTP文件上传根目录"
}
};
},
created: function () {
let data = fs.readFileSync(
Editor.url('packages://update-manager/.settings.conf'), 'utf-8');
if (data) {
Object.assign(this, JSON.parse(data));
}
this.projectPath = Editor.url('packages://update-manager');
this.projectPath = this.projectPath.substring(0, this.projectPath.length - 'packages/update-manager'.length)
Editor.log('当前项目路径:', this.projectPath);
},
methods: {
onSave() {
if (!this.versionCfg.baseUrl.endsWith('/') && this.versionCfg.baseUrl.length > 0) {
this.versionCfg.baseUrl += "/";
}
if (!this.ftpCfg.rootPath.endsWith('/') && this.ftpCfg.rootPath.length > 0) {
this.ftpCfg.rootPath += "/";
}
const data = Object.assign({}, {versionCfg: this.versionCfg, ftpCfg: this.ftpCfg});
fs && fs.writeFileSync && fs.writeFileSync(
Editor.url('packages://update-manager/.settings.conf'), JSON.stringify(data, null, 2), 'utf-8');
this.$message({message: "配置保存成功!", type: "success"});
},
onUpload() {
Editor.info("准备上传更新包");
this.$message("准备上传更新包");
if (!this.versionCfg.baseUrl.endsWith('/') && this.versionCfg.baseUrl.length > 0) {
this.versionCfg.baseUrl += "/";
}
if (!this.ftpCfg.rootPath.endsWith('/') && this.ftpCfg.rootPath.length > 0) {
this.ftpCfg.rootPath += "/";
}
let filePath = `update-${this.versionCfg.versionType}.json`;
const uploadDstPath = `${this.ftpCfg.rootPath}${this.versionCfg.versionType}/${filePath}`;
let updateUrl = `${this.versionCfg.baseUrl}${uploadDstPath}`;
const serverUrl = `${this.versionCfg.baseUrl}${this.ftpCfg.rootPath}${this.versionCfg.versionType}/${this.versionCfg.versionCode}`;
if (!this.notifyMainJs(updateUrl, serverUrl)) {
return;
}
if (this.ftpCfg.onlyJson) {
Editor.warn('当前仅上传JSON文件,请手动上传remote目录');
return;
}
// 上传更新描述文件.
const updateJsonPath = path.join(this.projectPath, `build/jsb-link/remote/${filePath}`);
Editor.Ipc.sendToMain('update-manager:upload', updateJsonPath, uploadDstPath, this.ftpCfg);
// 上传远程更新包.
let uploadZipPath = path.join(this.projectPath, `build/jsb-link/remote`);
let uploadZipDstPath = `${this.ftpCfg.rootPath}${this.versionCfg.versionType}/${this.versionCfg.versionCode}/remote`;
Editor.Ipc.sendToMain('update-manager:upload', uploadZipPath, uploadZipDstPath, this.ftpCfg);
},
onStopUpload() {
Editor.Ipc.sendToMain('update-manager:uploadStop');
},
notifyMainJs(updateUrl, serverUrl) {
this.$message.info("notifyMainJs");
const filePath = path.join(this.projectPath, 'build/jsb-link/main.js');
const exists = fs.existsSync(filePath);
if (!exists) {
this.$message.error('main.js 不存在,请重新构建');
return false;
}
let mainjs = fs.readFileSync(filePath, 'utf-8');
let varJs = `window.updateUrl="${updateUrl}";\r\n`;
let remoteJs = `window.remoteUrl="${serverUrl}";\r\n`;
// preMainJS read.
const preMain = fs && fs.readFileSync && fs.readFileSync(
Editor.url('packages://update-manager/templates/pre_main.js'), 'utf-8');
if (!mainjs.startsWith(varJs)) {
mainjs = mainjs.replace('window.boot();', 'window.beforeBoot();');
mainjs = varJs + remoteJs + preMain + mainjs;
fs && fs.writeFileSync && fs.writeFileSync(filePath, mainjs, 'utf-8');
}
this.$message.success("Main.js 热更代码注入成功");
return true;
}
}
})
</script>
</html>

View File

@@ -0,0 +1,90 @@
window.beforeBoot = function () {
cc.log("游戏正在启动中.")
if (window.remoteUrl) {
const settings = window._CCSettings;
settings.server = window.remoteUrl;
cc.log("远程资源地址:", settings.server);
}
let url = window.updateUrl || false;
if (!url) {
cc.log("未配置版本更新地址,跳过更新.")
window.boot();
return;
}
url += `?_t=${Math.random()}`;
cc.log("请求更新地址:", url);
get(url, function (err, asset) {
cc.log("请求更新信息:", url, err && err.toLocaleString(), JSON.stringify(asset));
if (err || !asset) {
window.boot();
return;
}
window.mergeVersion(asset);
window.boot();
cc.log("游戏已启动.");
});
};
window.mergeVersion = function (updateInfo) {
const currentVer = cc.sys.localStorage.getItem("currentVer");
let isFirstRun = false;
let newVerFlag = false;
if (!currentVer) {
isFirstRun = true;
cc.log("当前为首次运行");
} else {
const oldVerInfo = JSON.parse(currentVer);
if (oldVerInfo && oldVerInfo.versionCode != updateInfo.versionCode) {
newVerFlag = true;
cc.log("发现新版本信息:", updateInfo.versionCode, oldVerInfo.versionCode);
}
}
const settings = window._CCSettings;
if (updateInfo.server) {
settings.server = updateInfo.server;
cc.log("更新远程资源地址:", updateInfo.server);
}
const bundleVers = updateInfo.bundles
if (bundleVers) {
let changed = false;
for (let b in bundleVers) {
if (bundleVers[b] != settings.bundleVers[b]) {
// 配置中的bundleVer版本不一致,则添加到remote列表中去,以供远程加载.
if (settings.remoteBundles.indexOf(b) < 0) {
settings.remoteBundles.push(b);
}
changed = true;
cc.log("发现更新Bundle:", b);
}
}
settings.bundleVers = bundleVers;
// 如果首次运行,但检测版本有差异,则标记有更新.
if (isFirstRun && changed) {
newVerFlag = true;
cc.log("标记为新版本.");
}
}
cc.sys.localStorage.setItem('newVerFlag', newVerFlag ? 1 : 0);
cc.sys.localStorage.setItem('firstRunFlag', isFirstRun ? 1 : 0);
cc.sys.localStorage.setItem('currentVer', JSON.stringify(updateInfo));
}
// ajax 请求.
function get(url, cb) {
const ajax = new XMLHttpRequest();
ajax.open("get", url, true);
ajax.setRequestHeader("Content-Type", "application/json;charset=utf-8");
ajax.onreadystatechange = function () {
if (ajax.readyState == 4) {
if (ajax.status == 200) {
var response = JSON.parse(ajax.responseText)
cb && cb(null, response);
return;
} else {
cb && cb("request error!");
}
}
}
ajax.send(null);
}