在运行时覆盖更新配置
了解如何在运行时覆盖更新 URL 和请求标头,以控制在客户端加载哪些更新。
使用 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.
覆盖请求标头
🌐 Override request headers
警告 本节中描述的功能在 Expo SDK 54 中可用,前提是使用
expo-updates版本 0.29.0 及更高版本。
此功能的主要用例是通道切换,它允许在生产版本中运行时切换更新通道。这使非技术相关的利益相关者能够测试和验证进行中的更新(例如来自拉取请求或不同功能分支的更新),而无需使用开发版本或为每个更改创建单独的预览版本。
🌐 The primary use case for this feature is channel surfing, which allows switching between update channels in a production build at runtime. This enables 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.
例如,如果你有一个用于生产更新的 default 通道和一个用于预览更新的 preview 通道,你可以覆盖 expo-channel-name 请求头来指向 preview 通道。这使你能够在当前的生产构建中测试预览更新。
🌐 For example, if you have a default channel for production updates and a preview channel for preview updates, you can override the expo-channel-name request header to point to the preview channel. This enables you to test preview updates in your current production builds.
另一个潜在的用例是向不同的用户提供不同的更新,例如,让一组内部用户(例如员工)先于终端用户收到更新。
🌐 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.
什么是换台?
🌐 What is channel surfing?
通道切换是指在运行时切换已安装应用获取更新的渠道的做法。与在构建时固定绑定到某个渠道不同,运行中的应用可以被重定向到另一个渠道(例如 preview 渠道),并从那里加载更新。
🌐 Channel surfing is the practice of switching the update channel an installed app pulls updates from at runtime. Instead of being permanently tied to the channel configured at build time, a running app can be redirected to another channel (such as a preview channel) and load updates from there.
你可以使用通道切换来:
🌐 You can use channel surfing to:
- 允许单个已安装的应用按需在不同通道之间切换。应用可以在运行时切换通道,而不是永久锁定在构建时定义的通道。
- 在真实的构建版本上启用预览和测试工作流程,以便开发者、QA或其他相关人员可以使用用户已安装的相同生产构建来尝试进行中的更新。
- 通过将应用重定向到另一个渠道,可以立即测试、审查或验证加快迭代和验证的更新,而无需等待新版本的构建。
想了解更多关于通道切换解决的问题及其应用方法,请阅读 关于通道切换的博客文章。
🌐 For more information about problem channel surfing solves and how to apply it, read the blog post on channel surfing.
工作原理
🌐 How it works
你可以通过调用 Updates.setUpdateRequestHeadersOverride 来覆盖 expo-channel-name 请求头。这将覆盖更新请求,从指定的通道获取更新。
🌐 You can override the expo-channel-name request header by calling Updates.setUpdateRequestHeadersOverride. This will override update requests to fetch updates from the specified channel.
在你的应用的某个地方,你需要提供一种方式让用户触发请求头的更改。这可能是在一个只有受信任用户才能访问的隐藏菜单中,或根据你的使用情况选择其他机制。参数更改后,你可以调用 fetchUpdateAsync() 来获取更新,然后调用 reloadAsync() 来重新加载应用。或者你也可以等待下一次启动,届时应用会自动获取并安装更新。
🌐 Somewhere in your app, you would provide a way for users to trigger the change to 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. After the parameters are changed, you can call fetchUpdateAsync() to fetch the update, and then reloadAsync() to reload the app. Or you can wait for the next launch, which will automatically fetch and install the update.
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 channels, // for example: Updates.setUpdateRequestHeadersOverride({ 'expo-channel-name': 'preview' }); // You can fetch and reload the update immediately, or wait for the next launch await Updates.fetchUpdateAsync(); await Updates.reloadAsync();
切换渠道时的风险和注意事项
🌐 Risks and considerations when switching channels
切换通道会改变应用运行的 JavaScript 包。如果你的应用依赖于在各通道间不兼容的迁移或数据结构,来回切换可能会造成问题。
🌐 Switching channels changes the JavaScript bundle the app runs. If your app depends on migrations or data shapes that are not compatible across channels, switching back and forth may cause issues.
例如,如果一个测试版更新应用了数据库迁移,生产版本可能无法理解新的模式。开发者应确保他们的更新在切换时仍然安全,或者在需要时限制只能单向切换。
🌐 For example, if a beta update applies a database migration, the production version might not understand the new schema. Developers should ensure their updates remain safe to switch between or restrict switching to one direction when needed.
覆盖更新 URL 和请求标头
🌐 Override both update URL and request headers
警告 本节描述的功能在 Expo SDK 52 中可用,需使用
expo-updates版本 0.27.0 及更高版本。不建议在生产应用中使用disableAntiBrickingMeasures选项,目前它主要用于预览环境。
类似于覆盖请求头,如果你想进一步将更新 URL 覆盖为特定更新,你可以使用Updates.setUpdateURLAndRequestHeadersOverride 方法。这允许你通过 ID 加载特定更新,即使该更新在当前构建创建之前已发布。
🌐 Similar to override request headers, if you want to further override the update URL to a specific update, you can use the Updates.setUpdateURLAndRequestHeadersOverride method. This allows you to load a specific update by ID, even if the update is published before the current build is created.
在决定在生产环境中使用此功能之前,熟悉安全注意事项非常重要。将来,我们可能会增加对该功能的更受限制版本的支持,这将更适合此用例。
🌐 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:
Updates.setUpdateURLAndRequestHeadersOverride({ url: string, requestHeaders: Object })- 此方法会覆盖在 app.json / Expo.plist / AndroidManifest.xml 中指定的更新 URL 和请求头,例如expo-channel-name头。disableAntiBrickingMeasures- 应用配置中的此字段会禁用内置于expo-updates的防止设备变砖的措施,这些措施确保后续更新总是可以发布以修复之前安装的更新中的问题。当你更改此值时,需要创建一个新版本才能生效。不要在生产版本中启用此功能。 之所以使用这个名称,是为了明确表示,当你覆盖更新的 URL/头信息时,我们将无法安全地回滚到之前加载的更新。因此,如果你加载的新更新导致应用崩溃,expo-updates将无法自动恢复,因为此字段与setUpdateURLAndRequestHeadersOverride结合使用将禁用嵌入式更新,从而没有可回滚的更新。用户将需要卸载并重新安装应用。你应仅在预览版本中使用此功能。
如何使用这些 API:
🌐 How to use these APIs:
- 覆盖更新 URL/请求头,提示用户关闭应用:在你的应用中的某个地方,你会提供一种方式让用户触发 URL 和/或请求头的更改。这可能是在只有受信任用户才能访问的隐藏菜单中,或者根据你的使用场景,通过其他机制实现。当参数被更改后,请通知用户需要关闭并重新打开应用,例如通过弹出提示。
expo-updates库(具有类似checkForUpdateAsync()的方法)在应用关闭并重新打开之前,不会使用新的覆盖 URL 和请求头。 - 新更新将在下次打开应用时下载并启动:在应用完全关闭(被“强制退出”,而不仅仅是置于后台)并重新打开后,更新及其相关资源将全部下载完成。一旦准备就绪,应用将启动。在下载过程中,用户需要停留在启动屏幕上。我们理解在启动屏等待并非理想体验,如果这一功能被广泛使用,我们计划在未来改善这一体验。对于当前推荐的使用场景(预览),这可能是一个可接受的权衡。
安全考虑
🌐 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 (compromised 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.
类似使用 CodePush 会带来同样的风险吗?
是的。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/{updateId}/group/{groupId}', requestHeaders: {}, }); 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.. } } }