Expo 更新 v1
版本 1
介绍
🌐 Introduction
这是 Expo Updates 的规范,该协议用于向在多个平台上运行的 Expo 应用提供更新。
🌐 This is the specification for Expo Updates, a protocol for delivering updates to Expo apps running on multiple platforms.
一致性
🌐 Conformance
符合规范的服务器和客户端库必须满足所有规范性要求。符合性要求在本文件中通过描述性断言和具有明确定义含义的关键字进行说明。
🌐 Conforming servers and client libraries must fulfill all normative requirements. Conformance requirements are described in this document by both descriptive assertions and key words with clearly defined meanings.
本文件规范部分中的关键词“必须(MUST)”、“不得(MUST NOT)”、“必需(REQUIRED)”、“应当(SHALL)”、“不应(SHALL NOT)”、“应该(SHOULD)”、“不应该(SHOULD NOT)”、“推荐(RECOMMENDED)”、“可以(MAY)”和“可选(OPTIONAL)”应按照 IETF RFC 2119 中的描述进行理解。这些关键词可以以小写形式出现,但仍保持其含义,除非明确声明为非规范性的。
🌐 The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in the normative portions of this document are to be interpreted as described in IETF RFC 2119. These key words may appear in lowercase and still retain their meaning unless explicitly declared as non-normative.
遵循该协议的实现可以提供额外的功能,但在明确禁止或会导致不符合规范的情况下,必须不能提供。在相关情况下,应允许未知字段的存在,并由遵循协议的客户端忽略。
🌐 A conforming implementation of this protocol MAY provide additional functionality, but MUST NOT where explicitly disallowed or would otherwise result in non-conformance. Where relevant, unknown fields should be allowed and ignored by conforming clients.
概述
🌐 Overview
兼容的服务器和客户端库必须遵循 RFC 7231 中描述的 HTTP 规范,以及本规范中描述的更为详细的指南。
🌐 Conforming servers and client libraries MUST follow the HTTP spec as described in RFC 7231 as well as the more precise guidance described in this spec.
- “更新”被定义为包含清单及清单中引用的资源的组合。
- directive 被定义为服务器发送的一条消息,指示客户端执行某个操作。
Expo Updates 是一种用于向客户端组装和交付更新和指令的协议。
🌐 Expo Updates is a protocol for assembling and delivering updates and directives to clients.
该规范的主要受众是 Expo 应用服务和希望管理自己的更新服务器以满足内部需求的组织。
🌐 The primary audiences of this spec are Expo Application Services and organizations that wish to manage their own update server to satisfy internal requirements.
客户端
🌐 Client
请参阅参考客户端库。
运行符合规范的 Expo Updates 客户端库的应用必须加载客户端库更新数据库中保存的最新 update,可能需要先根据更新的清单 metadata 的内容进行筛选。
🌐 An app running a conformant Expo Updates client library MUST load the most recent update saved in the client library's update database, possibly after filtering by the contents of the update's manifest metadata.
下面描述了一致的 Expo Updates 客户端库必须如何从一致的服务器检索新的更新:
🌐 The following describes how a conformant Expo Updates client library MUST retrieve a new update from a conformant server:
- 客户端库必须根据头部中指定的约束条件,发出获取最新更新和指令的请求。
- 如果收到 response,客户端库必须处理其内容:
- 对于包含 update 的响应,客户端库应继续发起额外请求以下载并存储清单中指定的任何新资源。清单和资源一起被视为新的 update。客户端库将修改其本地状态,以反映新的更新已被添加到本地存储中。它还将使用响应 headers 中找到的新
expo-manifest-filters和expo-server-defined-headers更新本地状态。 - 对于包含 directive 的响应,客户端库将根据指令类型处理该指令,并相应地编辑其本地状态。
- 对于包含 update 的响应,客户端库应继续发起额外请求以下载并存储清单中指定的任何新资源。清单和资源一起被视为新的 update。客户端库将修改其本地状态,以反映新的更新已被添加到本地存储中。它还将使用响应 headers 中找到的新
要求
🌐 Request
符合要求的客户端库必须发出带有标头的 GET 请求:
🌐 A conformant client library MUST make a GET request with the headers:
expo-protocol-version: 1,用于指定此 Expo 更新规范的版本 1。expo-platform,用于指定客户端运行的平台类型。- iOS 必须是
expo-platform: ios。 - Android 必须是
expo-platform: android。 - 如果不是这些平台之一,服务器应该返回 400 或 404
- iOS 必须是
expo-runtime-version必须是与客户端兼容的运行时版本。运行时版本规定了客户端正在运行的本地代码设置。它应在客户端构建时设置。例如,在 iOS 客户端中,该值可能会在 plist 文件中设置。- 任何由之前响应的服务器定义的头规定的头信息。
符合规范的客户端库可以根据支持的响应结构发送accept: application/expo+json、accept: application/json或accept: multipart/mixed中的任意一个,但应发送accept: application/expo+json, application/json, multipart/mixed。符合规范的客户端库可以使用RFC 7231中指定的“q”参数表示偏好,该参数默认为1。
🌐 A conformant client library MAY send one of accept: application/expo+json, accept: application/json, or accept: multipart/mixed based on the supported response structures, though it SHOULD send accept: application/expo+json, application/json, multipart/mixed. A conformant client library MAY express preference using "q" parameters as specified in RFC 7231, which default to 1.
配置为执行代码签名验证的符合规范的客户端库必须发送 expo-expect-signature 头,以表明它期望符合规范的服务器在清单响应中包含 expo-signature 头。expo-expect-signature 是一个Expo SFV 字典,可以包含以下任意键值对:
🌐 A conformant client library configured to perform code signing verification MUST send a expo-expect-signature header to indicate that it expects the conformant server to include the expo-signature header in the manifest response. expo-expect-signature is an Expo SFV dictionary which MAY contain any of the following key value pairs:
sig应该包含布尔值true,以指示它需要兼容的服务器在sig键中返回签名。keyid应该包含客户端用于验证签名的公钥的 keyIdalg应包含客户端将用于验证签名的算法
示例:
🌐 Example:
expo-protocol-version: 1 accept: application/expo+json;q=0.9, application/json;q=0.8, multipart/mixed expo-platform: * expo-runtime-version: * expo-expect-signature: sig, keyid="root", alg="rsa-v1_5-sha256"
响应
🌐 Response
符合规范的服务器必须返回至少符合以下两种响应结构之一的响应,可以支持任一或两种响应结构,当请求的不支持的响应结构时,服务器应返回 HTTP 406 错误状态。希望针对所请求的协议版本响应不兼容响应的服务器,也应返回 HTTP 406 错误状态。
🌐 A conformant server MUST return a response structured in at least one of the two following response structures, MAY support either or both response structures, and when an unsupported response structure is requested the server SHOULD respond with an HTTP 406 error status. A server that wishes to respond with an incompatible response for the requested protocol version SHOULD also respond with an HTTP 406 error status instead.
- 对于带有
content-type: application/json或content-type: application/expo+json的响应,常用响应头 和 其他响应头 必须包含在响应头中,清单主体 必须包含在响应体中。这种格式的响应不支持多部分响应,因此不支持 指令,当待提供的最新响应不是 更新 时,应该返回 HTTP406错误状态。 - 对于含有
content-type: multipart/mixed的回应,回应必须按照 多部分响应 部分中规定的结构进行。 - 一个没有部分的多部分响应 可能会返回 HTTP
204状态且没有内容,因此也不会有content-type响应头。
更新和头信息的选择取决于请求头的值。符合规范的服务器必须响应最新的更新,按创建时间排序,并满足请求头所规定的所有参数和约束条件。服务器可以使用请求的任何属性,例如其头信息和源IP地址,在多个都满足请求约束条件的更新中进行选择。
🌐 The choice of update and headers are dependent on the values of the request headers. A conformant server MUST respond with the most recent update, ordered by creation time, satisfying all parameters and constraints imposed by the request headers. The server MAY use any properties of the request like its headers and source IP address to choose among several updates that all satisfy the request's constraints.
常见响应头
🌐 Common response headers
expo-protocol-version: 1 expo-sfv-version: 0 expo-manifest-filters: <expo-sfv> expo-server-defined-headers: <expo-sfv> cache-control: * content-type: *
expo-protocol-version描述了本规范中定义的协议版本,并且必须为1。expo-sfv-version必须是0。expo-manifest-filters是一个 Expo SFV 字典。它用于根据 manifest 中找到的metadata属性过滤客户端库存储的更新。如果过滤器中提到了某个字段,则为了包含该更新,元数据中相应的字段必须要么不存在,要么相等。客户端库必须存储 manifest 过滤器,直到被更新的响应覆盖。expo-server-defined-headers是一个 Expo SFV 字典。它定义了客户端库必须保存的标头,直到被更新的字典覆盖,并且这些标头必须包含在每个后续的 更新请求 中。cache-control必须设置为适当较短的时间。建议使用cache-control: private, max-age=0的值以确保返回最新的清单。设置较长的缓存时间可能会导致获取过时的更新。content-type必须通过 RFC 7231 中定义的 主动协商 来确定。由于客户端库 必须 在每个清单请求中发送accept头,这总是会是application/expo+json或application/json;否则请求将返回406错误。
其他响应头
🌐 Other response headers
expo-signature: *
expo-signature应包含在 代码签名 验证步骤中使用的清单的签名,如果清单的请求包含expo-expect-signature头。它是一个 Expo SFV 字典,可能包含以下任意键值对:sig必须包含清单的签名。此字段的名称与expo-expect-signature相同。keyid可能包含服务器用来签署响应的密钥的 keyId。客户端应使用与此keyid匹配的证书来验证签名。alg可能包含服务器用来签署响应的算法。客户端仅在该字段与匹配keyid的证书所定义的算法一致时才应使用它。
多部分响应
🌐 Multipart response
这种格式的更新响应由 RFC 2046 定义的 multipart/mixed MIME 类型定义。
🌐 An update response of this format is defined by the multipart/mixed MIME type as defined by RFC 2046.
此响应格式的标题为常用响应标题,但有以下例外:
🌐 Headers for this response format are the common response headers, with the following exceptions:
content-type应具有由 RFC 2046 定义的multipart/mixed值
零件顺序不严格。一个没有任何部分(长度为零的主体)的多部分响应应被视为无操作(没有可用的更新或指令),尽管响应的头部仍然应发送并由客户端处理。
🌐 Part order is not strict. A multipart response with no parts (zero-length body) should be considered a no-op (no updates or directives available), though headers for the response SHOULD be sent nevertheless and processed by the client.
各部分定义如下:
🌐 Each part is defined as follows:
- 可选
"manifest"部分: - 可选
"extensions"部分:- 必须包含部件头
content-disposition: form-data; name="extensions"。第一个参数 (form-data) 不必是form-data,但name参数必须具有extensions作为值。 - 必须有部件标题
content-type: application/json。 - extensions-body 必须在消息体中发送。
- 必须包含部件头
- 可选
"directive"部分:
清单主体
🌐 Manifest body
定义为符合以下两个条件的 JSON:在 TypeScript 中表示的 Manifest 定义以及对每个字段的详细描述:
🌐 Defined as JSON conforming to both the following Manifest definition expressed in TypeScript and the detailed descriptions for each field:
type Manifest = { id: string; createdAt: string; runtimeVersion: string; launchAsset: Asset; assets: Asset[]; metadata: { [key: string]: string }; extra: { [key: string]: any }; }; type Asset = { hash?: string; key: string; contentType: string; fileExtension?: string; url: string; };
-
id:ID 必须唯一地指定清单,并且必须是 UUID。 -
createdAt:更新创建的日期和时间非常重要,因为客户端库会选择最新的更新(受expo-manifest-filters头提供的任何约束条件限制)。日期时间应按照 ISO 8601 格式化。 -
runtimeVersion:可以是开发者定义的任何字符串。它规定了运行相关更新所需的本地代码设置。 -
launchAsset:一个特殊的资源,是应用代码的入口点。此资源将忽略fileExtension字段,应当省略该字段。 -
assets:更新包使用的一系列资源,例如 JavaScript、图片和字体。所有资源(包括launchAsset)都应该在执行更新之前下载到本地磁盘,并且应向应用代码提供资源key到磁盘位置的映射。 -
每个资源对象的属性:
hash:文件的 Base64URL 编码 SHA-256 哈希,以保证完整性。Base64URL 编码由 IETF RFC 4648 定义。key:用于在更新的应用代码中引用此资源的键。例如,这个键可能由处理应用代码的单独构建步骤生成,例如打包工具。contentType:根据 RFC 2045 定义的文件 MIME 类型。例如,application/javascript、image/jpeg。fileExtension:建议在客户端保存文件时使用的扩展名。一些平台,例如 iOS,需要某些文件类型以扩展名保存。扩展名必须以.为前缀。例如,.jpeg。在某些情况下,例如 launchAsset,这个字段将被忽略,而使用本地确定的扩展名。如果省略该字段且没有本地规定的扩展名,资源将保存为无扩展名的文件。例如,仅为./filename,末尾没有.。符合规范的客户端如果文件扩展名不为空且缺少.前缀,应在文件扩展名前加上.前缀。url:可以获取文件的位置。
-
metadata:与更新相关的元数据。它是一个字符串值字典。服务器可以返回其希望用于过滤更新的任何内容。元数据必须通过随附expo-manifest-filters头中定义的过滤器。 -
extra:用于存储可选的“额外”信息,例如第三方配置。例如,如果更新托管在 Expo 应用服务(EAS)上,可能会包含 EAS 项目 ID:"extra": { "eas": { "projectId": "00000000-0000-0000-0000-000000000000" } }
扩展主体
🌐 Extensions body
定义为符合以下两个条件的 JSON:在 TypeScript 中表示的 Extensions 定义以及对每个字段的详细描述:
🌐 Defined as JSON conforming to both the following Extensions definition expressed in TypeScript and the detailed descriptions for each field:
type Extensions = { assetRequestHeaders: ExpoAssetHeaderDictionary; ... } type ExpoAssetHeaderDictionary = { [assetKey: string]: { [headerName: string]: string, }; }
assetRequestHeaders:可能包含用于资源请求的头部(键、值)对字典。键和值都必须是字符串。
指令体
🌐 Directive body
定义为符合以下两个条件的 JSON:在 TypeScript 中表示的 Directive 定义以及对每个字段的详细描述:
🌐 Defined as JSON conforming to both the following Directive definition expressed in TypeScript and the detailed descriptions for each field:
type Directive = { type: string; parameters?: { [key: string]: any }; extra?: { [key: string]: any }; };
type:指令的类型。parameters:可能包含与type相关的任何额外信息。extra:用于存储可选的“额外”信息,例如第三方信息。例如,如果更新托管在 Expo 应用服务(EAS)上,可能会包含 EAS 项目 ID。
符合规范的客户端库和服务器可以指定并实现特定于应用需求的指令类型。例如,到目前为止,Expo 应用服务使用了一种类型 rollBackToEmbedded,它指示 expo-updates 库使用嵌入在主应用中的更新,而不是任何其他下载的更新。
🌐 A conformant client library and server MAY specify and implement directive types specific to the needs of the application. For example, Expo Application Services makes use of one type thus far, rollBackToEmbedded, which directs the expo-updates library to use the update embedded in the host application instead of any other downloaded updates.
资源请求
🌐 Asset request
符合规范的客户端库必须对清单中指定的资源 URL 发出 GET 请求。客户端库应包含一个接受清单中指定的资源内容类型的头。此外,客户端库还应指定其能够处理的压缩编码。
🌐 A conformant client library MUST make a GET request to the asset URLs specified by the manifest. The client library SHOULD include a header accepting the asset's content type as specified in the manifest. Additionally, the client library SHOULD specify the compression encoding the client library is capable of handling.
标题示例:
🌐 Example headers:
accept: image/jpeg, */* accept-encoding: br, gzip
符合规范的客户端库还必须包含为此资源键在assetRequestHeaders中包含的任何头(键,值)对。
🌐 A conformant client library MUST also include any header (key, value) pairs included in assetRequestHeaders for this asset key.
资源响应
🌐 Asset response
位于特定 URL 的资源不得更改或删除,因为客户端库可能随时获取资源进行任何更新。符合规范的客户端必须验证该资源的 base64url 编码的 SHA-256 哈希值是否与清单中该资源的 hash 字段匹配。
🌐 An asset located at a particular URL MUST NOT be changed or removed since client libraries may fetch assets for any update at any time. A conformant client MUST verify that the base64url-encoded SHA-256 hash of the asset matches the hash field for the asset from the manifest.
资源响应标头
🌐 Asset response headers
根据请求的 accept-encoding 头部,该资源必须使用客户端支持的压缩格式进行编码。服务器可以提供未压缩的资源。响应必须包含一个 content-type 头部,标明资源的 MIME 类型。例如:
🌐 The asset MUST be encoded using a compression format that the client supports according to the request's accept-encoding header. The server MAY serve uncompressed assets. The response MUST include a content-type header with the MIME type of the asset.
For example:
content-encoding: br content-type: application/javascript
建议将资源与设置为长时间的 cache-control 头一起提供,因为位于给定 URL 的资源不应更改。例如:
🌐 An asset is RECOMMENDED to be served with a cache-control header set to a long duration as an asset located at a given URL must not change. For example:
cache-control: public, max-age=31536000, immutable
压缩
🌐 Compression
🌐 Assets SHOULD be capable of being served with Gzip and Brotli compression.
代码签名
🌐 Code signing
Expo 更新支持对清单和指令内容进行代码签名。对清单进行代码签名也会传递性地对资源进行签名,因为资源的哈希包含在清单中,并由符合规范的客户端进行验证。符合规范的客户端可以使用私钥请求对清单或指令进行签名,然后在使用清单或指令或下载任何对应的清单资源之前,必须使用相应的代码签名证书验证其签名。客户端必须验证签名证书要么是自签名的受信根证书,要么是在受信根证书签名的证书链中。无论哪种情况,根证书必须嵌入在应用或设备的操作系统中。
🌐 Expo Updates supports code signing the manifest and directive bodies. Code signing the manifest also transitively signs the assets since their hashes are present in the manifest and verified by a conformant client. A conformant client MAY request the manifest or directive be signed using a private key, and then MUST verify the signature of the manifest or directive using the corresponding code signing certificate before it is used or any corresponding manifest assets are downloaded. The client MUST verify that the signing certificate is either a self-signed, trusted root certificate or is in a certificate chain signed by a trusted root certificate. In either case, the root certificate MUST be embedded in the application or device's operating system.