问题一:
我使用Postman 来验证我的自动更新服务器 URL 是否返回了我期望的响应。当我知道这些 URL 提供了预期的结果时,我知道我可以在我的应用程序的 Electron's Auto Updater 中使用这些 URL。
使用 Postman 测试 Mac 端点的示例:
请求:
https://my-server.com/api/macupdates/checkforupdate.php?appversion=1.0.5&cpuarchitecture=x64
有可用更新时的 JSON 响应:
{
"url": "https:/my-server.com/updates/darwin/x64/my-electron=app-x64-1.1.0.zip",
"name": "1.1.0",
"pub_date": "2021-07-03T15:17:12+00:00"
}
问题2:
是的,your Electron App must be code signed to use the auto-update feature on Mac。在 Windows 上我不确定,因为我的 Windows Electron 应用程序是代码签名的,没有它我没有尝试。虽然建议您签署您的应用程序,即使自动更新可以在没有它的情况下运行(不仅出于安全原因,而且主要是因为否则您的用户在第一次安装您的应用程序时会收到来自 Windows 的可怕危险警告,他们可能立即删除它)。
问题3:
要获得好的文档,您应该从 official Electron Auto Updater documentation 开始,截至 2021-07-07,它真的很好。
困难的部分是弄清楚如何让 Mac 工作。对于 Windows,只需几分钟即可完成。其实……
对于 Windows 自动更新,设置很容易 - 您只需将 RELEASES 和 nupkg 文件放在服务器上,然后将该 URL 用作 FeedURL在您的 Electron App 的 autoUpdater 中。因此,如果您的应用的更新文件位于 https://my-server.com/updates/win32/x64/ - 您可以将 Electron Auto Updater 指向该 URL,就是这样。
对于 Mac 自动更新,您需要手动将最新的 Electron App .zip 文件的绝对 URL 指定到 Electron autoUpdater。因此,为了使 Mac 自动更新程序正常工作,您需要有一种方法来获取 a JSON response in a very specific format。可悲的是,您不能只是将 Electron 应用程序的文件放在服务器上,并期望它可以像这样与 Mac 一起使用。相反,autoUpdater 需要一个返回上述 JSON 响应的 URL。为此,您需要将能够返回这种预期类型的 JSON 响应的 URL 传递给 Electron 的自动更新程序 feedURL。
实现这一点的方式可以是任何东西,但我使用 PHP 只是因为那是我已经支付的服务器。
因此,总而言之,对于 Mac,即使您的文件位于 https://my-server.com/updates/darwin/x64/ - 您也不会将该 URL 提供给 Electron 的 Auto Updater FeedURL。而是提供另一个返回预期 JSON 响应的 URL。
以下是我的应用的 Electron 主进程的 main.js 文件示例:
// main.js (Electron main process)
function registerAutoUpdater() {
const appVersion = app.getVersion();
const os = require('os');
const cpuArchitecture = os.arch();
const domain = 'https://my-server.com';
const windowsURL = `${domain}/updates/win32/x64`;
const macURL = `${domain}/api/macupdates/checkforupdate.php?appversion=${appVersion}&cpuarchitecture=${cpuArchitecture}`;
//init the autoUpdater with proper update feed URL
const autoUpdateURL = `${isMac ? macURL : windowsURL}`;
autoUpdater.setFeedURL({url: autoUpdateURL});
log.info('Registered autoUpdateURL = ' + (isMac ? 'macURL' : 'windowsURL'));
//initial checkForUpdates
autoUpdater.checkForUpdates();
//Automatic 2-hours interval loop checkForUpdates
setInterval(() => {
autoUpdater.checkForUpdates();
}, 7200000);
}
以下是 checkforupdate.php 文件 的示例,该文件将预期的 JSON 响应返回给 Electron 自动更新程序:
<?php
//FD Electron App Mac auto update API endpoint.
// The way Squirrel.Mac works is by checking a given API endpoint to see if there is a new version.
// If there is no new version, the endpoint should return HTTP 204. If there is a new version,
// however, it will expect a HTTP 200 JSON-formatted response, containing a url to a .zip file:
// https://github.com/Squirrel/Squirrel.Mac#server-support
$clientAppVersion = $_GET["appversion"] ?? null;
if (!isValidVersionString($clientAppVersion)) {
http_response_code(204);
exit();
}
$clientCpuArchitecture = $_GET["cpuarchitecture"] ?? null;
$latestVersionInfo = getLatestVersionInfo($clientAppVersion, $clientCpuArchitecture);
if (!isset($latestVersionInfo["versionNumber"])) {
http_response_code(204);
exit();
}
// Real logic starts here when basics did not fail
$isUpdateVailable = isUpdateAvailable($clientAppVersion, $latestVersionInfo["versionNumber"]);
if ($isUpdateVailable) {
http_response_code(200);
header('Content-Type: application/json;charset=utf-8');
$jsonResponse = array(
"url" => $latestVersionInfo["directZipFileURL"],
"name" => $latestVersionInfo["versionNumber"],
"pub_date" => date('c', $latestVersionInfo["createdAtUnixTimeStamp"]),
);
echo json_encode($jsonResponse);
} else {
//no update: must respond with a status code of 204 No Content.
http_response_code(204);
}
exit();
// End of execution.
// Everything bellow here are function declarations.
function getLatestVersionInfo($clientAppVersion, $clientCpuArchitecture): array {
// override path if client requests an arm64 build
if ($clientCpuArchitecture === 'arm64') {
$directory = "../../updates/darwin/arm64/";
$baseUrl = "https://my-server.com/updates/darwin/arm64/";
} else if (!$clientCpuArchitecture || $clientCpuArchitecture === 'x64') {
$directory = "../../updates/darwin/";
$baseUrl = "https://my-server.com/updates/darwin/";
}
// default name with version 0.0.0 avoids failing
$latestVersionFileName = "Finance D - Tenue de livres-darwin-x64-0.0.0.zip";
$arrayOfFiles = scandir($directory);
foreach ($arrayOfFiles as $file) {
if (is_file($directory . $file)) {
$serverFileVersion = getVersionNumberFromFileName($file);
if (isVersionNumberGreater($serverFileVersion, $clientAppVersion)) {
$latestVersionFileName = $file;
}
}
}
return array(
"versionNumber" => getVersionNumberFromFileName($latestVersionFileName),
"directZipFileURL" => $baseUrl . rawurlencode($latestVersionFileName),
"createdAtUnixTimeStamp" => filemtime(realpath($directory . $latestVersionFileName))
);
}
function isUpdateAvailable($clientVersion, $serverVersion): bool {
return
isValidVersionString($clientVersion) &&
isValidVersionString($serverVersion) &&
isVersionNumberGreater($serverVersion, $clientVersion);
}
function getVersionNumberFromFileName($fileName) {
// extract the version number with regEx replacement
return preg_replace("/Finance D - Tenue de livres-darwin-(x64|arm64)-|\.zip/", "", $fileName);
}
function removeAllNonDigits($semanticVersionString) {
// use regex replacement to keep only numeric values in the semantic version string
return preg_replace("/\D+/", "", $semanticVersionString);
}
function isVersionNumberGreater($serverFileVersion, $clientFileVersion): bool {
// receives two semantic versions (1.0.4) and compares their numeric value (104)
// true when server version is greater than client version (105 > 104)
return removeAllNonDigits($serverFileVersion) > removeAllNonDigits($clientFileVersion);
}
function isValidVersionString($versionString) {
// true when matches semantic version numbering: 0.0.0
return preg_match("/\d\.\d\.\d/", $versionString);
}