electron-asar-updater-pro-new 2.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/package.json +34 -0
- package/readme.md +129 -0
- package/src/index.js +396 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 xianyunleo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "electron-asar-updater-pro-new",
|
|
3
|
+
"version": "2.5.4",
|
|
4
|
+
"description": "Handles Electron app.asar updates. Electron asar 热更新",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/xianyunleo/electron-asar-updater-pro.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"electron",
|
|
15
|
+
"asar",
|
|
16
|
+
"update",
|
|
17
|
+
"updater"
|
|
18
|
+
],
|
|
19
|
+
"author": "mojixiang1102@gmail.com",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/xianyunleo/electron-asar-updater-pro/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/xianyunleo/electron-asar-updater-pro#readme",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"adm-zip": "^0.5.10",
|
|
27
|
+
"electron-fetch": "^1.9.1",
|
|
28
|
+
"electron-log": "^4.0.0",
|
|
29
|
+
"semver-diff": "^3.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"electron": ">=13.0.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# electron-asar-updater-pro
|
|
2
|
+
|
|
3
|
+
专业现代化的 electron asar文件更新。支持Windows、Mac 、Linux
|
|
4
|
+
|
|
5
|
+
优点:Windows 无需额外的 exe,支持C盘Program Files目录下的更新。
|
|
6
|
+
|
|
7
|
+
建议:因为开发模式下,路径不准确,仅测试代码跑通。完整流程,请将项目编译打包运行测试。
|
|
8
|
+
|
|
9
|
+
#### 安装
|
|
10
|
+
```
|
|
11
|
+
npm i electron-asar-updater-pro
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
#### 安装要求
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
Electron >= 13
|
|
18
|
+
Node >= 14
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
#### 示例
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
//Main Process
|
|
25
|
+
const Updater = require('electron-asar-updater-pro');
|
|
26
|
+
const options = {
|
|
27
|
+
api: {url: 'http://www.test.com/api'},
|
|
28
|
+
debug: true
|
|
29
|
+
}
|
|
30
|
+
const updater = new Updater(options);
|
|
31
|
+
|
|
32
|
+
ipcMain.handle('updater-check', async (event, data) => {
|
|
33
|
+
return await updater.check();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
ipcMain.handle('updater-update', async (event, data) => {
|
|
37
|
+
updater.on('downloadProgress', progress => {
|
|
38
|
+
event.sender.send('updater-download-progress', progress)
|
|
39
|
+
});
|
|
40
|
+
await updater.update();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
//Renderer Process
|
|
44
|
+
async function check() {
|
|
45
|
+
try {
|
|
46
|
+
const result = await ipcRenderer.invoke('updater-check');
|
|
47
|
+
if(result){
|
|
48
|
+
await update();
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.log('检查更新失败');
|
|
52
|
+
console.log(error);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
async function update() {
|
|
57
|
+
try {
|
|
58
|
+
ipcRenderer.on('updater-download-progress', (event, message) => {
|
|
59
|
+
console.log(message)
|
|
60
|
+
})
|
|
61
|
+
await ipcRenderer.invoke('updater-update');
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.log('更新失败');
|
|
64
|
+
console.log(error);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### 服务端api json
|
|
71
|
+
```
|
|
72
|
+
远程asar文件名可以随意,sha256是指asar文件或者zip文件的hash
|
|
73
|
+
|
|
74
|
+
{
|
|
75
|
+
"version": "1.1.0",
|
|
76
|
+
"asar": "http://www.test.com/update.asar",
|
|
77
|
+
"sha256": "xxx"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
如果asar是zip文件,那么结构如下
|
|
81
|
+
── update.zip
|
|
82
|
+
└── update.asar
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### 构造方法
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
options = {
|
|
90
|
+
api: {
|
|
91
|
+
url: '', //
|
|
92
|
+
body: {}, //string或object,服务端可根据这个参数,返回不同的response json
|
|
93
|
+
method: 'POST|GET', //default POST
|
|
94
|
+
headers: {}
|
|
95
|
+
},
|
|
96
|
+
autoRestart: true,
|
|
97
|
+
debug: false,
|
|
98
|
+
};
|
|
99
|
+
const updater = new Updater(options);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### 方法
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
await check(); //检查是否有更新,本地版本号和远程版本号比较
|
|
106
|
+
await update(); //更新并重启软件,必须先执行check方法
|
|
107
|
+
stopDownload(); //停止下载,仅限node v15及以上版本。
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
#### 事件
|
|
111
|
+
```js
|
|
112
|
+
updater.on('downloadProgress', progress => {
|
|
113
|
+
//下载进度
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
updater.on('status', status => {
|
|
117
|
+
//Updater.EnumStatus,更新的状态,用作参考。
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
#### 静态属性
|
|
121
|
+
```js
|
|
122
|
+
Updater.EnumStatus; //更新的状态
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
#### 其它:
|
|
126
|
+
|
|
127
|
+
如果你使用了`electron-vite` 作为脚手架,那么你可能需要配置`build.rollupOptions.external: ["original-fs"],`,请参考 https://cn.electron-vite.org/config/#%E5%86%85%E7%BD%AE%E9%85%8D%E7%BD%AE
|
|
128
|
+
|
|
129
|
+
如果你使用了`vue-cli-plugin-electron-builder` 作为脚手架,那么你可能需要配置`externals: ["electron-asar-updater-pro"],`,请参考 https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/guide.html#native-modules
|
package/src/index.js
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
const {app,net} = require('electron');
|
|
2
|
+
const fs = require("original-fs");
|
|
3
|
+
const fsPromises = fs.promises;
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const child_process = require("child_process");
|
|
6
|
+
const {EventEmitter} = require("events");
|
|
7
|
+
const electronLog = require("electron-log");
|
|
8
|
+
const semverDiff = require("semver-diff");
|
|
9
|
+
const Fetch = require('electron-fetch')
|
|
10
|
+
const AdmZip = require("adm-zip");
|
|
11
|
+
|
|
12
|
+
const exists = async (p) => {
|
|
13
|
+
return fsPromises.access(p).then(() => true).catch(() => false)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const Updater = class Updater extends EventEmitter {
|
|
17
|
+
_options = {
|
|
18
|
+
api: {
|
|
19
|
+
url: null,
|
|
20
|
+
body: '',
|
|
21
|
+
method: 'POST',
|
|
22
|
+
headers:{}
|
|
23
|
+
},
|
|
24
|
+
autoRestart: true,
|
|
25
|
+
debug: false,
|
|
26
|
+
};
|
|
27
|
+
_updateFileName = 'update.asar';
|
|
28
|
+
_downloadUrl = '';
|
|
29
|
+
_sha256 = '';
|
|
30
|
+
_downloadDir = '';
|
|
31
|
+
_downloadFilePath = '';
|
|
32
|
+
_status = 0;
|
|
33
|
+
_dlAbortController;
|
|
34
|
+
_isOldNode = false;
|
|
35
|
+
static EnumStatus = {
|
|
36
|
+
Ready: 0,
|
|
37
|
+
CheckError: 101,
|
|
38
|
+
Downloading: 102,
|
|
39
|
+
Downloaded: 103,
|
|
40
|
+
DownloadError: 104,
|
|
41
|
+
Extracting: 105,
|
|
42
|
+
Extracted: 106,
|
|
43
|
+
ExtractError: 107,
|
|
44
|
+
Moving: 108,
|
|
45
|
+
MoveError: 109,
|
|
46
|
+
HashNoMatch: 110,
|
|
47
|
+
Finish: 100,
|
|
48
|
+
Cancel: 200,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {Object} options
|
|
53
|
+
* @param {Object} options.api
|
|
54
|
+
* @param {string} options.api.url
|
|
55
|
+
* @param {string|Object} [options.api.body]
|
|
56
|
+
* @param {string} [options.api.method]
|
|
57
|
+
* @param {Object} [options.api.headers]
|
|
58
|
+
* @param {boolean} [options.autoRestart]
|
|
59
|
+
* @param {boolean} [options.debug]
|
|
60
|
+
*/
|
|
61
|
+
constructor(options) {
|
|
62
|
+
super();
|
|
63
|
+
this._isOldNode = !!this._getNodeMajorVersion() < 15;
|
|
64
|
+
options.autoRestart = options.autoRestart ?? true
|
|
65
|
+
this._options = options;
|
|
66
|
+
this._downloadDir = app.getPath('userData');
|
|
67
|
+
if (!this._isOldNode) {
|
|
68
|
+
this._dlAbortController = new AbortController();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
*
|
|
74
|
+
* @returns {Promise<boolean>}
|
|
75
|
+
*/
|
|
76
|
+
async check() {
|
|
77
|
+
const url = this._options.api.url;
|
|
78
|
+
if (!url) return false;
|
|
79
|
+
|
|
80
|
+
this._log(`AppDir: ${this.getAppDir()}`);
|
|
81
|
+
const appVersion = this.getAppVersion();
|
|
82
|
+
this._log(`Check:appVersion:${appVersion}`);
|
|
83
|
+
let respData;
|
|
84
|
+
const fetchOpts = {method: this._options.api.method ?? 'POST'};
|
|
85
|
+
try {
|
|
86
|
+
if (this._options.api.body) {
|
|
87
|
+
if (typeof this._options.api.body !== 'string') {
|
|
88
|
+
this._options.api.body = JSON.stringify(this._options.api.body);
|
|
89
|
+
}
|
|
90
|
+
fetchOpts.body = this._options.api.body;
|
|
91
|
+
}
|
|
92
|
+
if (this._options.api.headers) {
|
|
93
|
+
fetchOpts.headers = this._options.api.headers;
|
|
94
|
+
}
|
|
95
|
+
respData = await (await Fetch.default(url, fetchOpts)).json();
|
|
96
|
+
} catch (error) {
|
|
97
|
+
this._changeStatus(Updater.EnumStatus.Cancel, `Cannot connect to api url,${error.message}`);
|
|
98
|
+
throw new Error(`Cannot connect to api url,${error.message}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!respData.version || !respData.asar) {
|
|
102
|
+
this._changeStatus(Updater.EnumStatus.Cancel, 'Api url response not valid');
|
|
103
|
+
throw new Error('Api url response not valid');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!semverDiff(appVersion, respData.version)) {
|
|
107
|
+
this._changeStatus(Updater.EnumStatus.Cancel, 'No updates available');
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
this._downloadUrl = respData.asar;
|
|
111
|
+
this._sha256 = respData.sha256;
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async update() {
|
|
116
|
+
try {
|
|
117
|
+
this._changeStatus(Updater.EnumStatus.Downloading);
|
|
118
|
+
await this._download();
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (error.name === 'AbortError') {
|
|
121
|
+
this._changeStatus(Updater.EnumStatus.Cancel, 'Download cancelled');
|
|
122
|
+
return;
|
|
123
|
+
} else {
|
|
124
|
+
this._changeStatus(Updater.EnumStatus.DownloadError, `Download Error,${error}`);
|
|
125
|
+
throw new Error(`Download Error,${error}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (this._sha256) {
|
|
130
|
+
const fileBuffer = await fsPromises.readFile(this._downloadFilePath);
|
|
131
|
+
if (this.sha256(fileBuffer) !== this._sha256) {
|
|
132
|
+
this._changeStatus(Updater.EnumStatus.HashNoMatch, `File hash mismatch`);
|
|
133
|
+
throw new Error('File hash mismatch');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (this._downloadFilePath.endsWith('.zip')) {
|
|
138
|
+
try {
|
|
139
|
+
this._changeStatus(Updater.EnumStatus.Extracting, 'Extracting');
|
|
140
|
+
await this._zipExtract();
|
|
141
|
+
this._changeStatus(Updater.EnumStatus.Extracted);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
this._changeStatus(Updater.EnumStatus.ExtractError, 'Extract Error');
|
|
144
|
+
throw new Error('Extract Error');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
this._changeStatus(Updater.EnumStatus.Moving, 'Moving');
|
|
150
|
+
await this._move();
|
|
151
|
+
} catch (error) {
|
|
152
|
+
this._changeStatus(Updater.EnumStatus.MoveError, error.message);
|
|
153
|
+
throw new Error(`Move Error,${error.message}`);
|
|
154
|
+
}
|
|
155
|
+
this._changeStatus(Updater.EnumStatus.Finish);
|
|
156
|
+
|
|
157
|
+
if (this._options.autoRestart) {
|
|
158
|
+
app.relaunch();
|
|
159
|
+
app.quit();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async _download() {
|
|
165
|
+
let receivedBytes = 0;
|
|
166
|
+
const request = net.request({url:this._downloadUrl});
|
|
167
|
+
const responsePromise = this._getResponse(request);
|
|
168
|
+
request.end();
|
|
169
|
+
const response = await responsePromise;
|
|
170
|
+
const headers = response.headers;
|
|
171
|
+
const contentType = headers['content-type'];
|
|
172
|
+
const totalBytes = headers['content-length'] ? parseInt(headers['content-length']) : 0;
|
|
173
|
+
if (!await exists(this._downloadDir)) {
|
|
174
|
+
await fsPromises.mkdir(this._downloadDir, {recursive: true});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const isZipFromUrl = this._downloadUrl.toLowerCase().includes('.zip');
|
|
178
|
+
const isZipFromHeader = contentType && contentType.includes('zip');
|
|
179
|
+
let filePath = path.join(this._downloadDir, this._updateFileName);
|
|
180
|
+
if (isZipFromUrl || isZipFromHeader) {
|
|
181
|
+
let zipName = `${this._updateFileName}.zip`;
|
|
182
|
+
try {
|
|
183
|
+
const urlPathName = new URL(this._downloadUrl).pathname;
|
|
184
|
+
const basename = path.basename(urlPathName);
|
|
185
|
+
if (basename) {
|
|
186
|
+
zipName = basename;
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
filePath = path.join(this._downloadDir, zipName);
|
|
191
|
+
}
|
|
192
|
+
this._downloadFilePath = filePath;
|
|
193
|
+
if (await exists(filePath)) {
|
|
194
|
+
await this.deleteFile(filePath);
|
|
195
|
+
}
|
|
196
|
+
const writeStream = fs.createWriteStream(filePath);
|
|
197
|
+
|
|
198
|
+
response.on('data', (buffer) => {
|
|
199
|
+
receivedBytes += buffer.length;
|
|
200
|
+
const progress = {
|
|
201
|
+
percent: totalBytes === 0 ? 0 : receivedBytes / totalBytes,
|
|
202
|
+
transferred: receivedBytes,
|
|
203
|
+
total: totalBytes,
|
|
204
|
+
}
|
|
205
|
+
this.emit('downloadProgress', progress)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
if (this._isOldNode) {
|
|
210
|
+
const util = require('util');
|
|
211
|
+
const stream = require('stream');
|
|
212
|
+
const pipeline = util.promisify(stream.pipeline);
|
|
213
|
+
await pipeline(response, writeStream);
|
|
214
|
+
} else {
|
|
215
|
+
const {pipeline} = require('node:stream/promises');
|
|
216
|
+
await pipeline(response, writeStream, {signal: this._dlAbortController.signal});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
this._changeStatus(Updater.EnumStatus.Downloaded);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async deleteFile($p) {
|
|
223
|
+
try {
|
|
224
|
+
await fsPromises.unlink($p);
|
|
225
|
+
if (await exists($p)) {
|
|
226
|
+
await fsPromises.rm($p, {force: true, recursive: true});
|
|
227
|
+
}
|
|
228
|
+
} catch {
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async _zipExtract() {
|
|
233
|
+
let zip = new AdmZip(this._downloadFilePath);
|
|
234
|
+
zip.extractAllTo(this._downloadDir, true);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
stopDownload() {
|
|
238
|
+
if (this._isOldNode) {
|
|
239
|
+
throw new Error('This method only supports node v15 and later');
|
|
240
|
+
} else {
|
|
241
|
+
this._dlAbortController.abort();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async _move() {
|
|
246
|
+
const resourcesDir = this.getResourcesDir();
|
|
247
|
+
const updateAsarPath = path.join(this._downloadDir,this._updateFileName);
|
|
248
|
+
const appAsarPath = path.join(resourcesDir, 'app.asar');
|
|
249
|
+
|
|
250
|
+
if (!this.isDev()) {
|
|
251
|
+
try {
|
|
252
|
+
const bakAsarPath = path.join(this._downloadDir, 'app.bak.asar');
|
|
253
|
+
await this.deleteFile(bakAsarPath); //如果已有bak文件是只读,那么必须要先删除才能copy overwrite
|
|
254
|
+
await fsPromises.copyFile(appAsarPath, bakAsarPath);
|
|
255
|
+
} catch (e) {
|
|
256
|
+
this._log(`Backup app.bak.asar error.${e.message}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const canWriteResources = await this._checkWritePermission(appAsarPath);
|
|
261
|
+
this._log(`CanWriteResources ${canWriteResources}`);
|
|
262
|
+
if (canWriteResources) {
|
|
263
|
+
this._log(`Copy ${updateAsarPath} to ${appAsarPath}`);
|
|
264
|
+
await fsPromises.chmod(appAsarPath, 0o666)
|
|
265
|
+
await fsPromises.copyFile(updateAsarPath, appAsarPath);
|
|
266
|
+
} else {
|
|
267
|
+
if (process.platform !== 'win32') {
|
|
268
|
+
throw new Error('app.asar access denied');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const shell = 'powershell';
|
|
272
|
+
const options = {shell: shell, stdio: 'ignore'};
|
|
273
|
+
const command = `Start-Process`;
|
|
274
|
+
const updateAsarPathArg = this._getArg(updateAsarPath, true);
|
|
275
|
+
const appAsarPathArg = this._getArg(appAsarPath, true);
|
|
276
|
+
const args = [
|
|
277
|
+
'-WindowStyle', 'hidden',
|
|
278
|
+
'-FilePath', 'cmd',
|
|
279
|
+
'-ArgumentList', `"/c attrib -r ${appAsarPathArg} & copy /y ${updateAsarPathArg} ${appAsarPathArg}"`,
|
|
280
|
+
'-Verb', 'RunAs'
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
this._log(`Update start shell process. Command:${command}, Args:${args.join(' ')}`);
|
|
284
|
+
try {
|
|
285
|
+
const childProcess = child_process.spawn(command, args, options);
|
|
286
|
+
await new Promise((resolve, reject) => {
|
|
287
|
+
childProcess.on('exit', (code) => {
|
|
288
|
+
if (code == 1) {
|
|
289
|
+
reject(new Error('The operation has been canceled by the user'));
|
|
290
|
+
} else {
|
|
291
|
+
resolve();
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
childProcess.on('error', (error) => {
|
|
295
|
+
reject(error);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
} catch (error) {
|
|
299
|
+
throw new Error('Start shell process Error: ' + error);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async _getResponse(request) {
|
|
305
|
+
return new Promise((resolve, reject) => {
|
|
306
|
+
request.on('response', response => {
|
|
307
|
+
resolve(response);
|
|
308
|
+
});
|
|
309
|
+
request.on('error', error => {
|
|
310
|
+
reject(error);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
_changeStatus(status, logText = '') {
|
|
316
|
+
this._status = status;
|
|
317
|
+
this.emit('status', status);
|
|
318
|
+
if (logText) {
|
|
319
|
+
this._log(logText);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
_log(text) {
|
|
324
|
+
if (this._options.debug) {
|
|
325
|
+
console.log('Updater: ', text)
|
|
326
|
+
}
|
|
327
|
+
electronLog.info('[ electron-asar-updater-pro ]', text)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
_getNodeMajorVersion(version) {
|
|
331
|
+
return parseInt(process.versions.node.split('.')[0]);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
getStatus() {
|
|
335
|
+
return this._status;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
getAppVersion() {
|
|
339
|
+
return app.getVersion();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
getAppDir() {
|
|
343
|
+
if (this.isDev()) {
|
|
344
|
+
return app.getAppPath();
|
|
345
|
+
} else {
|
|
346
|
+
return path.dirname(this.getExePath());
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
getResourcesDir() {
|
|
351
|
+
if (this.isDev()) {
|
|
352
|
+
return app.getAppPath();
|
|
353
|
+
} else {
|
|
354
|
+
return path.dirname(app.getAppPath());
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
getExePath() {
|
|
359
|
+
//可执行文件路径,Mac返回路径为 AppName.app/Contents/MacOS/AppName。dev返回node_modules\electron\dist\updater.exe
|
|
360
|
+
return app.getPath('exe');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
isDev() {
|
|
364
|
+
return !app.isPackaged;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
_getArg(str, isPowershell = false) {
|
|
368
|
+
if (isPowershell) {
|
|
369
|
+
return `\`"${str}\`"`;
|
|
370
|
+
}
|
|
371
|
+
return `"${str}"`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async _checkWritePermission(path) {
|
|
375
|
+
try {
|
|
376
|
+
if (process.platform === 'win32') {
|
|
377
|
+
const fileHandle = await fsPromises.open(path, "w");
|
|
378
|
+
await fileHandle?.close();
|
|
379
|
+
} else {
|
|
380
|
+
await fsPromises.access(path, fs.constants.W_OK);
|
|
381
|
+
}
|
|
382
|
+
return true;
|
|
383
|
+
} catch (err) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
sha256(data) {
|
|
389
|
+
const crypto = require('crypto');
|
|
390
|
+
const hash = crypto.createHash('sha256');
|
|
391
|
+
hash.update(data);
|
|
392
|
+
return hash.digest('hex');
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
module.exports = Updater;
|