在运行时覆盖更新配置

了解如何在运行时覆盖更新 URL 和请求标头,以控制在客户端加载哪些更新。


本指南中描述的功能在 Expo SDK 52 和 expo-updates 版本 0.27.0 及更高版本中可用。不建议在生产应用中使用 disableAntiBrickingMeasures 选项,它目前主要用于预览环境。

使用 EAS 更新的典型方法是将单个更新 URL 和一组请求标头(例如更新渠道名称)嵌入到应用的构建中。要控制加载哪个更新,你可以通过 eas update 命令或 EAS 仪表板在服务器上进行更改。例如,你将新更新发布到构建指向的通道,然后构建将在下次启动时获取该更新。使用此方法不会下载发布到与你的构建指向不同的通道的更新。

¥The typical way to use EAS Update is to have a single update URL and a set of request headers (such as update channel name) embedded in a build of your app. To control which update is loaded you make changes on the server through the eas update command or the EAS dashboard. For example, you publish a new update to a channel that your build is pointing to, then the build fetches that update on the next launch. Updates published to a channel different from the one your build is pointing to will not be downloaded with this approach.

本指南解释了如何在运行时更改更新 URL 和请求标头,从而可以通过 ID 加载特定更新或更改从中提取更新的渠道,而无需创建和安装新版本。

¥This guide explains how you can change the update URL and request headers at runtime, making it possible to load a specific update by ID or change the channel that updates are pulled from without creating and installing a new build.

在运行时更改更新 URL 和请求标头的用例

¥Use cases for changing the update URL and request headers at runtime

这主要用于在预览版本中启用更新之间的切换,类似于在开发版本中可能实现的切换。这有助于非技术利益相关者测试和验证正在进行的工作的更新(例如来自拉取请求或不同的功能分支),而无需使用开发版本或为每个更改创建单独的预览版本。

¥The primary use case that this is intended for is to enable switching between updates in a preview build, similar to what is possible in a development build. This is useful to enable non-technical stakeholders to test and validate updates of work-in-progress (such as from a pull request or different feature branches) without having to use a development build or create a separate preview build for each change.

另一个潜在用例是向不同的用户提供不同的更新,例如,让一组内部用户(如员工)在终端用户之前收到更新。在决定在生产中使用此功能之前,熟悉 安全注意事项 非常重要。将来,我们可能会添加对更适合此用例的更受限制的功能版本的支持。

¥Another potential use case is to provide different updates to different users, for example so that a group of internal users (such as employees) receive updates before end-users. It is important to be familiar with the security considerations before deciding to use this feature in production. In the future, we may add support for a more restricted version of the feature that would be more suitable for this use case.

工作原理

¥How it works

有两个相关的 API:

¥There are two relevant APIs:

  1. Updates.setUpdateURLAndRequestHeadersOverride({ url: string, requestHeaders: Object }) - 此方法将覆盖更新 URL 和 app.json / Expo.plist / AndroidManifest.xml 中指定的请求标头,例如 expo-channel-name 标头。

    ¥Updates.setUpdateURLAndRequestHeadersOverride({ url: string, requestHeaders: Object }) - this method overrides the update URL and the request headers that are specified in app.json / Expo.plist / AndroidManifest.xml, such as the expo-channel-name header.

  2. disableAntiBrickingMeasures - 应用配置中的此字段禁用 expo-updates 内置的防砖措施,以确保始终可以发布后续更新以修复先前安装的更新中的问题。更改此值时,你需要创建一个新的构建才能使其生效。请勿在生产版本中启用此功能。使用这个名字的原因是为了清楚地表明,当你覆盖更新 URL/标头时,我们将无法再安全地回滚到之前加载的更新。因此,如果你加载的新更新导致应用崩溃,则 expo-updates 无法自动恢复,因为此字段与 setUpdateURLAndRequestHeadersOverride 结合将禁用嵌入式更新,因此不会有任何更新可回滚。用户需要卸载并重新安装应用。你只应在预览版本中使用此功能。

    ¥disableAntiBrickingMeasures - this field in the app config disables anti-bricking measures built-in to expo-updates which ensure subsequent updates can always be published to fix issues in previously-installed update. When you change this value, you will need to create a new build for it to take effect. Do not enable this in your production builds. The reason for this name is to clearly indicate that when you override the update URL/headers, we're no longer able to safely rollback to the previous update that was loaded. So, if the new update you have loaded causes the app to crash then expo-updatescannot automatically recover, because this field in conjunction with setUpdateURLAndRequestHeadersOverride will disable embedded updates and therefore there will not be any update to rollback to. The user would need to uninstall and reinstall the app. You should only use this feature in preview builds.

如何使用这些 API:

¥How to use these APIs:

  1. 覆盖更新 URL/标头,指示用户关闭应用:在你的应用中的某个地方,你将为用户提供一种触发对 URL 和/或请求标头的更改的方法。这可能位于只有受信任用户才能访问的隐藏菜单中,或者根据你的用例使用其他机制。当参数发生变化时,通知用户他们需要关闭并重新打开应用,例如通过警报。expo-updates 库具有类似 checkForUpdateAsync() 的方法,在应用关闭并重新打开之前,不会使用新的覆盖 URL 和请求标头。

    ¥Override the update URL/headers, instruct user to close the app: Somewhere in your app, you would provide a way for users to trigger the change to the URL and/or request headers. This may be in a hidden menu that only trusted users have access to, or some other mechanism depending on your use case. When the parameters are changed, notify the user that they need to close and re-open the app, such as via an alert. The expo-updates library, with methods like checkForUpdateAsync(), will not use the new overridden URL and request headers until the app is closed and reopened.

  2. 新的更新将在下次打开应用时下载并启动:应用完全关闭("killed" 而不仅仅是后台)并重新打开后,更新及其相关资源将全部下载。一旦它们准备就绪,应用就会启动。在下载时,用户必须在启动画面上等待。我们理解在启动画面上等待并不理想,如果此功能被广泛使用,我们打算在未来改善这种体验。对于当前推荐的用例(预览),这可能是一个可以接受的折衷方案。

    ¥The new update will be downloaded and launched on the next app open: After the app is completely closed ("killed" and not just backgrounded) and re-opened, the update and its related assets will all be downloaded. Once they are ready, the app will launch. While it's downloading, the user will have to wait on the splash screen. We understand that waiting on the splash screen is not ideal, and we intend to improve this experience in the future if this feature is widely used. For the currently recommended use case (previews), this is likely an acceptable compromise.

安全考虑

¥Security considerations

可以使用 disableAntiBrickingMeasures 禁用的防砖措施可确保无论发布什么更新,你都可以随时发布将要应用的另一个更新。通过禁用防砖措施,某些类别的攻击和漏洞利用成为可能,尤其是在内部(恶意员工)发布恶意更新时。例如,具有发布更新能力的员工可以发布恶意更新,更改更新 URL 和请求标头以指向他们自己的服务器,并接管应用的安装。通过使用 代码签名 进行生产更新并限制对密钥的访问,可以减轻但不能消除这种风险。

¥The anti-bricking measures that can be disabled with disableAntiBrickingMeasures ensure that, no matter what update is published, you can always publish another update afterwards that will be applied. By disabling the anti-bricking measures, certain categories of attacks and exploits become possible, especially around in-house (malicious employee) publishing of malicious updates. For example, an employee with the ability to publish updates could publish a malicious update that changes the update URL and request headers to point to their own server, and take over installations of the app. This risk can be mitigated, but not eliminated, by using code signing for production updates and limiting access to the key.

Did similar usage of CodePush carry the same risk?

是。CodePush 允许开发者与 sync({ deploymentKey: string }) 交换部署密钥,这些密钥可以以同样的方式被恶意用来接管应用安装。

¥Yes. CodePush allowed developers to swap deployment keys with sync({ deploymentKey: string }) which could be used maliciously take over an app installation in this same way.

示例代码

¥Example code

以下是如何使用这些 API 的示例:

¥Here's an example of how you might use these APIs:

import * as Updates from 'expo-updates';

// Where you call this method depends on your use case - it may make sense to
// have a menu in your preview builds that allows testers to pick from available
// pull requests, for example.
function overrideUpdateURLAndHeaders() {
  Updates.setUpdateURLAndRequestHeadersOverride({
    url: '<https://u.expo.dev/>...',
    requestHeaders: { 'expo-channel-name': 'pr-123' },
  });

  alert('Close and re-open the app to load the latest version.');
}
{
  "expo": {
    "updates": {
      // We recommend only enabling this in preview builds.
      // You can use app.config.js to configure it dynamically.
      "disableAntiBrickingMeasures": true
      // etc..
    }
  }
}