@lvce-editor/update-worker 2.13.0 → 2.15.0
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/README.md +20 -0
- package/dist/updateWorkerMain.js +180 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
Webworker for the update functionality in LVCE Editor.
|
|
4
4
|
|
|
5
|
+
## Emulating the current version
|
|
6
|
+
|
|
7
|
+
Set `update.emulateCurrentVersion` in LVCE Editor's `settings.json` to test update detection without installing an older build:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"update.emulateCurrentVersion": "0.115.14"
|
|
12
|
+
}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Update checks compare the latest GitHub release with this version. Remove the setting to use the normal application version. Empty, non-string, or malformed values also fall back to the application version; overrides must use numeric `major.minor.patch` format. The override only affects update comparisons, not the application's reported version or the selected release asset.
|
|
16
|
+
|
|
17
|
+
The host must expose `Preferences.get` and `Process.getVersion` to the worker and use `Update.getLatestVersion` for manual update detection. Both manual checks and the install flow use the override. This requires an application build containing the worker and host integration; existing installed builds do not gain the setting automatically.
|
|
18
|
+
|
|
5
19
|
## Contributing
|
|
6
20
|
|
|
7
21
|
```sh
|
|
@@ -10,3 +24,9 @@ cd update-worker &&
|
|
|
10
24
|
npm ci &&
|
|
11
25
|
npm test
|
|
12
26
|
```
|
|
27
|
+
|
|
28
|
+
## macOS updates
|
|
29
|
+
|
|
30
|
+
The worker selects the release DMG matching the app's architecture, downloads it, and verifies its size and GitHub SHA-256 digest before writing it through the existing filesystem RPC. It then asks the host's small native helper to stage and validate the bundle. Install Update and Restart are separate confirmations; cancelling Restart leaves the app running. The Windows NSIS path remains in place.
|
|
31
|
+
|
|
32
|
+
The host must expose `AutoUpdater.getPlatform`, `Process.getArch`, `AutoUpdater.stageMacUpdate`, and `AutoUpdater.restartMacUpdate`. Installation errors are returned to the host for notification. A release without the requested architecture or checksum is rejected. The native helper and host wiring must ship together with this worker before the installed macOS app can use the flow.
|
package/dist/updateWorkerMain.js
CHANGED
|
@@ -96,7 +96,6 @@ const walkValue = (value, transferrables, isTransferrable) => {
|
|
|
96
96
|
for (const property of Object.values(value)) {
|
|
97
97
|
walkValue(property, transferrables, isTransferrable);
|
|
98
98
|
}
|
|
99
|
-
return;
|
|
100
99
|
}
|
|
101
100
|
};
|
|
102
101
|
const getTransferrables = value => {
|
|
@@ -250,7 +249,14 @@ class IpcError extends VError {
|
|
|
250
249
|
const cause = new Error(message);
|
|
251
250
|
// @ts-ignore
|
|
252
251
|
cause.code = code;
|
|
253
|
-
|
|
252
|
+
if (stack) {
|
|
253
|
+
Object.defineProperty(cause, 'stack', {
|
|
254
|
+
configurable: true,
|
|
255
|
+
enumerable: false,
|
|
256
|
+
value: stack,
|
|
257
|
+
writable: true
|
|
258
|
+
});
|
|
259
|
+
}
|
|
254
260
|
super(cause, betterMessage);
|
|
255
261
|
} else {
|
|
256
262
|
super(betterMessage);
|
|
@@ -959,6 +965,34 @@ const listen$1 = async (module, options) => {
|
|
|
959
965
|
return ipc;
|
|
960
966
|
};
|
|
961
967
|
|
|
968
|
+
const createSharedLazyRpc = factory => {
|
|
969
|
+
let rpcPromise;
|
|
970
|
+
const getOrCreate = () => {
|
|
971
|
+
if (!rpcPromise) {
|
|
972
|
+
rpcPromise = factory();
|
|
973
|
+
}
|
|
974
|
+
return rpcPromise;
|
|
975
|
+
};
|
|
976
|
+
return {
|
|
977
|
+
async dispose() {
|
|
978
|
+
const rpc = await getOrCreate();
|
|
979
|
+
await rpc.dispose();
|
|
980
|
+
},
|
|
981
|
+
async invoke(method, ...params) {
|
|
982
|
+
const rpc = await getOrCreate();
|
|
983
|
+
return rpc.invoke(method, ...params);
|
|
984
|
+
},
|
|
985
|
+
async invokeAndTransfer(method, ...params) {
|
|
986
|
+
const rpc = await getOrCreate();
|
|
987
|
+
return rpc.invokeAndTransfer(method, ...params);
|
|
988
|
+
},
|
|
989
|
+
async send(method, ...params) {
|
|
990
|
+
const rpc = await getOrCreate();
|
|
991
|
+
rpc.send(method, ...params);
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
};
|
|
995
|
+
|
|
962
996
|
const create$4 = async ({
|
|
963
997
|
commandMap,
|
|
964
998
|
isMessagePortOpen = true,
|
|
@@ -994,34 +1028,6 @@ const create$3 = async ({
|
|
|
994
1028
|
});
|
|
995
1029
|
};
|
|
996
1030
|
|
|
997
|
-
const createSharedLazyRpc = factory => {
|
|
998
|
-
let rpcPromise;
|
|
999
|
-
const getOrCreate = () => {
|
|
1000
|
-
if (!rpcPromise) {
|
|
1001
|
-
rpcPromise = factory();
|
|
1002
|
-
}
|
|
1003
|
-
return rpcPromise;
|
|
1004
|
-
};
|
|
1005
|
-
return {
|
|
1006
|
-
async dispose() {
|
|
1007
|
-
const rpc = await getOrCreate();
|
|
1008
|
-
await rpc.dispose();
|
|
1009
|
-
},
|
|
1010
|
-
async invoke(method, ...params) {
|
|
1011
|
-
const rpc = await getOrCreate();
|
|
1012
|
-
return rpc.invoke(method, ...params);
|
|
1013
|
-
},
|
|
1014
|
-
async invokeAndTransfer(method, ...params) {
|
|
1015
|
-
const rpc = await getOrCreate();
|
|
1016
|
-
return rpc.invokeAndTransfer(method, ...params);
|
|
1017
|
-
},
|
|
1018
|
-
async send(method, ...params) {
|
|
1019
|
-
const rpc = await getOrCreate();
|
|
1020
|
-
rpc.send(method, ...params);
|
|
1021
|
-
}
|
|
1022
|
-
};
|
|
1023
|
-
};
|
|
1024
|
-
|
|
1025
1031
|
const create$2 = async ({
|
|
1026
1032
|
commandMap,
|
|
1027
1033
|
isMessagePortOpen,
|
|
@@ -1115,9 +1121,14 @@ const create = rpcId => {
|
|
|
1115
1121
|
};
|
|
1116
1122
|
};
|
|
1117
1123
|
|
|
1124
|
+
const Electron = 2;
|
|
1125
|
+
|
|
1118
1126
|
const DialogWorker = 7014;
|
|
1119
1127
|
const RendererWorker = 1;
|
|
1120
1128
|
|
|
1129
|
+
const DownloadingUpdate = 2;
|
|
1130
|
+
const DownloadedUpdate = 3;
|
|
1131
|
+
|
|
1121
1132
|
const {
|
|
1122
1133
|
invoke: invoke$1,
|
|
1123
1134
|
set: set$1
|
|
@@ -1133,11 +1144,6 @@ const sendMessagePortToDialogWorker = async port => {
|
|
|
1133
1144
|
await invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToDialogWorker', port, command);
|
|
1134
1145
|
};
|
|
1135
1146
|
|
|
1136
|
-
const Electron = 2;
|
|
1137
|
-
|
|
1138
|
-
const DownloadingUpdate = 2;
|
|
1139
|
-
const DownloadedUpdate = 3;
|
|
1140
|
-
|
|
1141
1147
|
const emptyObject = {};
|
|
1142
1148
|
const RE_PLACEHOLDER = /\{(PH\d+)\}/g;
|
|
1143
1149
|
const i18nString = (key, placeholders = emptyObject) => {
|
|
@@ -1271,6 +1277,30 @@ const getDiskPath = async downloadUrl => {
|
|
|
1271
1277
|
return diskPath;
|
|
1272
1278
|
};
|
|
1273
1279
|
|
|
1280
|
+
const isGreater = (version, otherVersion) => {
|
|
1281
|
+
const versionParts = version.split('.').map(Number);
|
|
1282
|
+
const otherVersionParts = otherVersion.split('.').map(Number);
|
|
1283
|
+
const partCount = Math.max(versionParts.length, otherVersionParts.length);
|
|
1284
|
+
for (let index = 0; index < partCount; index++) {
|
|
1285
|
+
const versionPart = versionParts[index] || 0;
|
|
1286
|
+
const otherVersionPart = otherVersionParts[index] || 0;
|
|
1287
|
+
if (versionPart !== otherVersionPart) {
|
|
1288
|
+
return versionPart > otherVersionPart;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
return false;
|
|
1292
|
+
};
|
|
1293
|
+
|
|
1294
|
+
const getCurrentVersion = async () => {
|
|
1295
|
+
// @ts-ignore
|
|
1296
|
+
const override = await invoke('Preferences.get', 'update.emulateCurrentVersion');
|
|
1297
|
+
if (typeof override === 'string' && /^\d+\.\d+\.\d+$/.test(override)) {
|
|
1298
|
+
return override;
|
|
1299
|
+
}
|
|
1300
|
+
// @ts-ignore
|
|
1301
|
+
return invoke('Process.getVersion');
|
|
1302
|
+
};
|
|
1303
|
+
|
|
1274
1304
|
const parseVersionFromUrl = (url, repository) => {
|
|
1275
1305
|
if (!url.includes('releases/tag')) {
|
|
1276
1306
|
if (url.endsWith('/releases')) {
|
|
@@ -1301,6 +1331,15 @@ const getLatestReleaseVersion = async repository => {
|
|
|
1301
1331
|
}
|
|
1302
1332
|
};
|
|
1303
1333
|
|
|
1334
|
+
const getLatestVersion = async repository => {
|
|
1335
|
+
const currentVersion = await getCurrentVersion();
|
|
1336
|
+
const release = await getLatestReleaseVersion(repository);
|
|
1337
|
+
if (isGreater(release.version, currentVersion)) {
|
|
1338
|
+
return release;
|
|
1339
|
+
}
|
|
1340
|
+
return undefined;
|
|
1341
|
+
};
|
|
1342
|
+
|
|
1304
1343
|
const getUpdateUrl = (repository, fileNameTemplate, version) => {
|
|
1305
1344
|
const fileName = fileNameTemplate.replace('${version}', () => version);
|
|
1306
1345
|
const url = `https://github.com/${repository}/releases/download/v${version}/${fileName}`;
|
|
@@ -1342,6 +1381,103 @@ const installAndRestart = async downloadPath => {
|
|
|
1342
1381
|
}
|
|
1343
1382
|
};
|
|
1344
1383
|
|
|
1384
|
+
const downloadMacUpdate = async (version, arch) => {
|
|
1385
|
+
if (!/^\d+\.\d+\.\d+$/.test(version) || !['arm64', 'x64'].includes(arch)) {
|
|
1386
|
+
throw new Error('Invalid macOS update version or architecture');
|
|
1387
|
+
}
|
|
1388
|
+
const repository = 'lvce-editor/lvce-editor';
|
|
1389
|
+
const assetName = `lvce-v${version}_${arch}.dmg`;
|
|
1390
|
+
const url = `https://github.com/${repository}/releases/download/v${version}/${assetName}`;
|
|
1391
|
+
const metadata = await fetch(`https://api.github.com/repos/${repository}/releases/tags/v${version}`, {
|
|
1392
|
+
headers: {
|
|
1393
|
+
Accept: 'application/vnd.github+json'
|
|
1394
|
+
},
|
|
1395
|
+
signal: AbortSignal.timeout(30_000)
|
|
1396
|
+
});
|
|
1397
|
+
if (!metadata.ok) {
|
|
1398
|
+
throw new Error(`Failed to read release metadata: HTTP ${metadata.status}`);
|
|
1399
|
+
}
|
|
1400
|
+
const release = await metadata.json();
|
|
1401
|
+
const asset = release.assets?.find(item => item.name === assetName);
|
|
1402
|
+
const maxSize = 512 * 1024 * 1024;
|
|
1403
|
+
if (!asset || asset.browser_download_url !== url || !/^sha256:[a-f0-9]{64}$/.test(asset.digest) || !(asset.size > 0 && asset.size <= maxSize)) {
|
|
1404
|
+
throw new Error(`Release is missing a valid ${assetName} asset or SHA-256 digest`);
|
|
1405
|
+
}
|
|
1406
|
+
const response = await fetch(url, {
|
|
1407
|
+
signal: AbortSignal.timeout(600_000)
|
|
1408
|
+
});
|
|
1409
|
+
if (!response.ok || !response.body) {
|
|
1410
|
+
throw new Error(`Failed to download update: HTTP ${response.status}`);
|
|
1411
|
+
}
|
|
1412
|
+
const reader = response.body.getReader();
|
|
1413
|
+
const chunks = [];
|
|
1414
|
+
let size = 0;
|
|
1415
|
+
try {
|
|
1416
|
+
while (true) {
|
|
1417
|
+
const {
|
|
1418
|
+
done,
|
|
1419
|
+
value
|
|
1420
|
+
} = await reader.read();
|
|
1421
|
+
if (done) {
|
|
1422
|
+
break;
|
|
1423
|
+
}
|
|
1424
|
+
size += value.byteLength;
|
|
1425
|
+
if (size > asset.size) {
|
|
1426
|
+
throw new Error('Update download exceeds the expected size');
|
|
1427
|
+
}
|
|
1428
|
+
chunks.push(new Uint8Array(value).buffer);
|
|
1429
|
+
}
|
|
1430
|
+
} finally {
|
|
1431
|
+
await reader.cancel();
|
|
1432
|
+
}
|
|
1433
|
+
const blob = new Blob(chunks);
|
|
1434
|
+
const hash = await crypto.subtle.digest('SHA-256', await blob.arrayBuffer());
|
|
1435
|
+
const digest = `sha256:${Array.from(new Uint8Array(hash), byte => byte.toString(16).padStart(2, '0')).join('')}`;
|
|
1436
|
+
if (size !== asset.size || digest !== asset.digest) {
|
|
1437
|
+
throw new Error('Update download checksum or size does not match');
|
|
1438
|
+
}
|
|
1439
|
+
const diskPath = await getDiskPath(url);
|
|
1440
|
+
await downloadToDisk(diskPath, new Response(blob));
|
|
1441
|
+
return diskPath;
|
|
1442
|
+
};
|
|
1443
|
+
|
|
1444
|
+
const installMacUpdate = async (version, download = downloadMacUpdate) => {
|
|
1445
|
+
const install = await invoke$1('ConfirmPrompt.prompt', `Version ${version} is available. Install this update?`, {
|
|
1446
|
+
confirmMessage: 'Install Update',
|
|
1447
|
+
platform: Electron
|
|
1448
|
+
});
|
|
1449
|
+
if (!install) {
|
|
1450
|
+
return {
|
|
1451
|
+
error: undefined,
|
|
1452
|
+
updated: false
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
// @ts-ignore
|
|
1456
|
+
await invoke('Notification.create', 'info', `Preparing update ${version}...`);
|
|
1457
|
+
// @ts-ignore
|
|
1458
|
+
const arch = await invoke('Process.getArch');
|
|
1459
|
+
const diskPath = await download(version, arch);
|
|
1460
|
+
// @ts-ignore
|
|
1461
|
+
await invoke('AutoUpdater.stageMacUpdate', diskPath, version);
|
|
1462
|
+
const restart = await invoke$1('ConfirmPrompt.prompt', `Update ${version} is ready. Restart to finish installing?`, {
|
|
1463
|
+
cancelMessage: 'Later',
|
|
1464
|
+
confirmMessage: 'Restart',
|
|
1465
|
+
platform: Electron
|
|
1466
|
+
});
|
|
1467
|
+
if (!restart) {
|
|
1468
|
+
return {
|
|
1469
|
+
error: undefined,
|
|
1470
|
+
updated: false
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
// @ts-ignore
|
|
1474
|
+
await invoke('AutoUpdater.restartMacUpdate');
|
|
1475
|
+
return {
|
|
1476
|
+
error: undefined,
|
|
1477
|
+
updated: true
|
|
1478
|
+
};
|
|
1479
|
+
};
|
|
1480
|
+
|
|
1345
1481
|
const isCached = async (url, cache) => {
|
|
1346
1482
|
const match = await cache.match(url);
|
|
1347
1483
|
return !!match;
|
|
@@ -1368,13 +1504,18 @@ const doCheckForUpdates = async (updateSetting, repository, fileNameTemplate, bu
|
|
|
1368
1504
|
updated: false
|
|
1369
1505
|
};
|
|
1370
1506
|
}
|
|
1371
|
-
const info = await
|
|
1507
|
+
const info = await getLatestVersion(repository);
|
|
1372
1508
|
if (!info || !info.version) {
|
|
1373
1509
|
return {
|
|
1374
1510
|
error: undefined,
|
|
1375
1511
|
updated: false
|
|
1376
1512
|
};
|
|
1377
1513
|
}
|
|
1514
|
+
// @ts-ignore
|
|
1515
|
+
const platform = await invoke('AutoUpdater.getPlatform');
|
|
1516
|
+
if (platform === 'darwin') {
|
|
1517
|
+
return await installMacUpdate(info.version);
|
|
1518
|
+
}
|
|
1378
1519
|
const cache = await getCache(bucketName, cacheName);
|
|
1379
1520
|
if (!(await shouldUpdate(updateSetting, info.version))) {
|
|
1380
1521
|
return {
|
|
@@ -1424,7 +1565,7 @@ const doCheckForUpdates = async (updateSetting, repository, fileNameTemplate, bu
|
|
|
1424
1565
|
} catch (error) {
|
|
1425
1566
|
console.error(error);
|
|
1426
1567
|
return {
|
|
1427
|
-
error:
|
|
1568
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1428
1569
|
updated: false
|
|
1429
1570
|
};
|
|
1430
1571
|
}
|
|
@@ -1457,7 +1598,8 @@ const checkForUpdates = async (updateSetting, repository) => {
|
|
|
1457
1598
|
|
|
1458
1599
|
const commandMap = {
|
|
1459
1600
|
'Update.checkForUpdates': checkForUpdates,
|
|
1460
|
-
'Update.downloadToCache': downloadUpdateToCache
|
|
1601
|
+
'Update.downloadToCache': downloadUpdateToCache,
|
|
1602
|
+
'Update.getLatestVersion': getLatestVersion
|
|
1461
1603
|
};
|
|
1462
1604
|
|
|
1463
1605
|
const listen = async () => {
|