自动更新电子

时间:2015-12-07 09:57:21

标签: windows macos air electron

我希望将自动更新功能部署到我拥有的Electron安装中,但是我发现很难在网络上找到任何资源。

我以前使用Adobe Air构建了一个自包含的应用程序,编写更新代码可以更轻松地编写有效检查URL并自动下载并在Windows和MAC OSX上安装更新。

我目前正在使用electron-boilerplate来简化构建。

我有几个问题:

  • 如何调试自动更新功能?我是否使用本地节点服务器设置本地连接并进行测试,还是可以使用任何Web服务器?
  • 在签署应用程序方面,我只想在MAC OSX,特别是Windows上运行应用程序。我是否必须签署应用程序才能运行自动更新? (我设法使用本地证书与Adobe Air合作。
  • 有没有详细说明如何实施自动更新功能的好资源?因为我很难找到关于如何做到这一点的好文档。

5 个答案:

答案 0 :(得分:4)

我也是Electron的新手,但我认为电子样板(我也使用)没有简单的自动更新。 Electron的自动更新程序使用Squirrel.Windows安装程序,您还需要将其实施到您的解决方案中才能使用它。

我目前正在尝试使用此功能:

可在此处找到更多信息:

编辑:我刚刚打开项目尝试了一段时间,它看起来很有效。它很简单。这些是我的gulpfile中的碎片。

在目前的配置中,我使用electronic-packager创建一个包。

var packager = require('electron-packager')
var createPackage = function () {
    var deferred = Q.defer();


    packager({
        //OPTIONS
    }, function done(err, appPath) {
        if (err) {
            gulpUtil.log(err);
        }

        deferred.resolve();
    });

    return deferred.promise;
};

然后我使用electron-installer-squirrel-windows创建一个安装程序。

var squirrelBuilder = require('electron-installer-squirrel-windows');
var createInstaller = function () {
    var deferred = Q.defer();

squirrelBuilder({
// OPTIONS
}, function (err) {
        if (err)
            gulpUtil.log(err);

        deferred.resolve();
    });

    return deferred.promise;
}

此外,您需要为您的电子背景/主要代码添加一些Squirrel代码。我用模板电子松鼠启动。

if(require('electron-squirrel-startup')) return;

上面提到的electron-installer-squirrel-windows npm文档中描述了整个内容。看起来有点文档就足以让它开始了。 现在我正在通过Squirrel开发电子品牌,并为自动化创建适当的gulp脚本。

答案 1 :(得分:0)

你也可以在OS X上使用标准Electron的autoUpdater模块,在Windows上使用它的简单端口:https://www.npmjs.com/package/electron-windows-updater

答案 2 :(得分:0)

我遵循了这个tutorial并使用我的电子应用程序,虽然它需要签名才能工作,所以你需要:

 certificateFile: './path/to/cert.pfx'

在任务配置中。

"build": {
  "win": {
    "certificateFile": "./path/to/cert.pfx",
    "certificatePassword": "password"
  }
},

在package.json

答案 3 :(得分:0)

  

是否有任何好的资源详细说明如何实现自动更新功能?由于我很难找到一些有关如何执行此操作的文档。

您不必自己实施。您可以使用Electron提供的autoUpdater,而只需设置feedUrl。您需要一个提供符合Squirrel协议更新信息的服务器。

有几个自托管(https://electronjs.org/docs/tutorial/updates#deploying-an-update-server)或托管服务,例如https://www.update.rocks

答案 4 :(得分:0)

问题 1:

我使用 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在您的电子应用程序的自动更新程序中。因此,如果您的应用程序的更新文件位于 https://my-server.com/updates/win32/x64/ - 您可以将 Electron Auto Updater 指向该 URL,就是这样。

对于 Mac 自动更新,您需要手动指定最新 Electron App .zip 文件的绝对 URL 到 Electron autoUpdater。因此,为了使 Mac autoUpdater 工作,您需要有一种方法来获取 a JSON response in a very specific format。遗憾的是,您不能将 Electron 应用程序的文件放在您的服务器上,并期望它像那样与 Mac 一起使用。相反,autoUpdater 需要一个将返回上述 JSON 响应的 URL。为此,您需要向 Electron 的自动更新程序 feedURL 传递能够返回这种预期类型的​​ JSON 响应的 URL。 您实现这一目标的方式可以是任何方式,但我使用 PHP 只是因为那是我已经付费购买的服务器。

总而言之,对于 Mac,即使您的文件位于 https://my-server.com/updates/darwin/x64/,您也不会将该 URL 提供给 Electron 的 Auto Updater FeedURL。相反,将提供另一个返回预期 JSON 响应的 URL。

以下是我的应用程序的 Electron 主进程的 ma​​in.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 Auto Updater:

<?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);
}