Expo Updates v1

编辑页面

版本 1


For the complete documentation index, see llms.txt. Use this Use this file to discover all available pages.

介绍

这是 Expo Updates 的规范,它是一个用于向运行在多个平台上的 Expo 应用交付更新的协议。

一致性

符合规范的服务器和客户端库必须满足所有规范性要求。本文档通过描述性断言和具有明确定义含义的关键词来描述一致性要求。

本文档规范性部分中的关键词 “MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY” 和 “OPTIONAL” 应按照 IETF RFC 2119 中的描述进行解释。这些关键词可以以小写形式出现,但除非明确声明为非规范性,否则仍保留其含义。

该协议的符合规范实现 MAY 提供额外功能,但在明确禁止的地方或会导致不符合规范的情况下 MUST NOT 提供额外功能。凡相关之处,符合规范的客户端应允许并忽略未知字段。

概述

符合规范的服务器和客户端库 MUST 遵循 RFC 7231 中描述的 HTTP 规范,以及本规范中更精确的指导。

  • update 定义为一个 manifest 以及 manifest 内引用的资源。
  • directive 定义为来自服务器的一条消息,用于指示客户端执行某个操作。

Expo Updates 是一种用于向客户端组装并交付更新和指令的协议。

本规范的主要受众是 Expo Application Services,以及希望管理自己的更新服务器以满足内部需求的组织。

客户端

参见 参考客户端库

运行符合规范的 Expo Updates 客户端库的应用 MUST 加载保存在客户端库更新数据库中的最新 update,并可能根据更新 manifest 的 metadata 内容进行过滤。

以下内容描述了符合规范的 Expo Updates 客户端库 MUST 如何从符合规范的服务器获取新更新:

  1. 客户端库 MUST 发起一个针对最新更新和指令的 请求,并在请求头中指定约束条件。
  2. 如果收到了一个 响应,客户端库 MUST 处理其内容:
    • 对于包含 update 的响应,客户端库 SHALL 继续发起额外请求,下载并存储 manifest 中指定的任何新资源。manifest 和资源一起被视为一个新的 update。客户端库将编辑其本地状态,以反映新的 update 已被添加到本地存储中。它还会使用响应 headers 中找到的新 expo-manifest-filtersexpo-server-defined-headers 更新本地状态。
    • 对于包含 directive 的响应,客户端库将根据指令类型消费该指令,并相应地编辑其本地状态。

请求

符合规范的客户端库 MUST 使用以下请求头发起 GET 请求:

  1. expo-protocol-version: 1,用于指定本 Expo Updates 规范的第 1 版。
  2. expo-platform,用于指定客户端运行的平台类型。
    • iOS MUST 为 expo-platform: ios
    • Android MUST 为 expo-platform: android
    • 如果不是这些平台之一,服务器 SHOULD 返回 400 或 404
  3. expo-runtime-version MUST 是与客户端兼容的运行时版本。运行时版本规定了客户端所运行的原生代码配置。它应在客户端构建时设置。例如,在 iOS 客户端中,该值可以设置在 plist 文件中。
  4. 任何前一个响应的 server defined headers 所规定的请求头。

符合规范的客户端库 MAY 基于 支持的响应结构 发送 accept: application/expo+jsonaccept: application/jsonaccept: multipart/mixed 之一,尽管它 SHOULD 发送 accept: application/expo+json, application/json, multipart/mixed。符合规范的客户端库 MAY 按照 RFC 7231 的规定使用 "q" 参数表达偏好,其默认值为 1

配置为执行 code signing 验证的符合规范的客户端库 MUST 发送 expo-expect-signature 请求头,以表明它期望符合规范的服务器在 manifest 响应中包含 expo-signature 请求头。expo-expect-signature 是一个 Expo SFV 字典,其中 MAY 包含以下任意键值对:

  • sig SHOULD 包含布尔值 true,以表明它要求符合规范的服务器在 sig 键中返回签名。
  • keyid SHOULD 包含客户端将用于验证签名的公钥的 keyId
  • alg SHOULD 包含客户端将用于验证签名的算法

示例:

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"

响应

符合规范的服务器 MUST 返回至少符合以下两种响应结构之一的响应,MAY 支持其中一种或两种响应结构;当请求了不受支持的响应结构时,服务器 SHOULD 返回 HTTP 406 错误状态。如果服务器希望针对所请求的协议版本返回不兼容的响应,也 SHOULD 改为返回 HTTP 406 错误状态。

  • 对于 content-type: application/jsoncontent-type: application/expo+json 的响应,common response headersother response headers MUST 在响应头中发送,而 manifest body MUST 在响应体中发送。此响应格式不支持多个响应部分,因此不支持 directives;当要提供的最新响应不是 update 时,SHOULD 返回 HTTP 406 错误状态。
  • 对于 content-type: multipart/mixed 的响应,响应 MUST 按照 multipart response 部分中的规定进行结构化。
  • 没有任何部分的 multipart response MAY 返回 HTTP 204 状态且无内容,因此也没有 content-type 响应头。

update 和 headers 的选择取决于请求头的值。符合规范的服务器 MUST 返回按创建时间排序、满足 request headers 所施加的所有参数和约束的最新 update。服务器 MAY 使用请求的任何属性,如其 headers 和源 IP 地址,在多个都满足请求约束的更新之间进行选择。

通用响应头

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 描述了本规范中定义的协议版本,且 MUST 为 1
  • expo-sfv-version MUST 为 0
  • expo-manifest-filters 是一个 Expo SFV 字典。它用于根据 manifestmetadata 属性的值,过滤客户端库存储的更新。如果筛选器中提到了某个字段,则相应 metadata 字段必须缺失或相等,更新才会被包含。客户端库 MUST 存储 manifest filters,直到它被更新的响应覆盖。
  • expo-server-defined-headers 是一个 Expo SFV 字典。它定义了客户端库 MUST 存储的请求头,直到被更新的字典覆盖,并且它们 MUST 包含在之后每次 update request 中。
  • cache-control MUST 设置为一个足够短的时间段。建议使用 cache-control: private, max-age=0,以确保返回最新 manifest。设置更长的缓存时间可能导致返回过期更新。
  • content-type MUST 通过 RFC 7231 中定义的 proactive negotiation 决定。由于客户端库 要求 在每次 manifest 请求中发送 accept 请求头,因此这里总是会是 application/expo+jsonapplication/json;否则请求将返回 406 错误。

其他响应头

expo-signature: *
  • expo-signature SHOULD 包含 manifest 的签名,以便在 code signing 的验证步骤中使用,前提是对 manifest 的请求包含了 expo-expect-signature 请求头。这是一个 Expo SFV 字典,其中 MAY 包含以下任意键值对:
    • sig MUST 包含 manifest 的签名。此字段的名称与 expo-expect-signature 一致。
    • keyid MAY 包含服务器用于对响应签名的密钥的 keyId。客户端 SHOULD 使用与此 keyid 匹配的证书来验证签名。
    • alg MAY 包含服务器用于对响应签名的算法。客户端 SHOULD 仅在其与 keyid 所匹配证书定义的算法一致时使用此字段。

多部分响应

这种格式的更新响应由 RFC 2046 中定义的 multipart/mixed MIME 类型定义。

此响应格式的头部是 common response headers,但有以下例外:

  • content-type SHOULD 具有 RFC 2046 中定义的 multipart/mixed

部分的顺序不受严格限制。没有任何部分(零长度 body)的多部分响应应被视为无操作(没有可用的更新或指令),不过响应头 SHOULD 仍然发送,并由客户端处理。

每个部分定义如下:

  1. 可选的 "manifest" 部分:
    • MUST 具有分部头 content-disposition: form-data; name="manifest"。第一个参数(form-data)不一定需要是 form-data,但 name 参数必须取值为 manifest
    • MUST 具有分部头 content-type: application/jsonapplication/expo+json
    • 如果正在使用 code signing,则 SHOULD 具有 other response headers 中定义的分部头 expo-signature
    • manifest body MUST 作为分部内容发送。
  2. 可选的 "extensions" 部分:
    • MUST 具有分部头 content-disposition: form-data; name="extensions"。第一个参数(form-data)不一定需要是 form-data,但 name 参数必须取值为 extensions
    • MUST 具有分部头 content-type: application/json
    • extensions-body MUST 作为分部内容发送。
  3. 可选的 "directive" 部分:
    • MUST 具有分部头 content-disposition: form-data; name="directive"。第一个参数(form-data)不一定需要是 form-data,但 name 参数必须取值为 directive
    • MUST 具有分部头 content-type: application/jsonapplication/expo+json
    • 如果正在使用 code signing,则 SHOULD 具有 other response headers 中定义的分部头 expo-signature
    • directive body MUST 作为分部内容发送。

Manifest body

定义为符合以下以 TypeScript 表示的 Manifest 定义以及每个字段详细说明的 JSON:

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 MUST 唯一标识该 manifest,并且 MUST 是一个 UUID。

  • createdAt:创建该更新的日期和时间至关重要,因为客户端库会选择最新的更新(受 expo-manifest-filters 请求头提供的任何约束影响)。该 datetime 应按照 ISO 8601 格式化。

  • runtimeVersion:可以是开发者定义的任意字符串。它规定了运行相关更新所需的原生代码配置。

  • launchAsset:作为应用代码入口点的特殊资源。该资源会忽略 fileExtension 字段,并 SHOULD 省略。

  • assets:更新 bundle 使用的资源数组,例如 JavaScript、图片和字体。在执行更新之前,所有资源(包括 launchAsset)都应下载到磁盘,并且应向应用代码提供 asset key 到磁盘位置的映射。

  • 每个 asset 对象的属性:

    • hash:文件的 Base64URL 编码 SHA-256 哈希,用于保证完整性。Base64URL 编码由 IETF RFC 4648 定义。
    • key:用于在更新的应用代码中引用此资源的键。例如,该键可由处理应用代码的单独构建步骤生成,例如打包器。
    • contentType:文件的 MIME 类型,由 RFC 2045 定义。例如 application/javascriptimage/jpeg
    • fileExtension:在客户端保存文件时建议使用的扩展名。某些平台(如 iOS)要求某些文件类型必须带扩展名保存。扩展名 MUST 以前缀 . 开头。例如,.jpeg。在某些情况下,例如 launchAsset,此字段会被本地确定的扩展名所取代而忽略。如果该字段被省略且没有本地规定的扩展名,则资源将不带扩展名保存。例如,./filename 末尾没有 .。 符合规范的客户端 SHOULD 在文件扩展名非空但缺少 . 前缀时,为其添加 . 前缀。
    • url:文件可获取的位置。
  • metadata:与更新相关联的元数据。它是一个以字符串为值的字典。服务器 MAY 返回它希望用于过滤更新的任何内容。metadata MUST 满足随附的 expo-manifest-filters 请求头定义的过滤条件。

  • extra:用于存储可选的 “extra” 信息,例如第三方配置。例如,如果更新托管在 Expo Application Services (EAS) 上,则可以包含 EAS 项目 ID:

    "extra": { "eas": { "projectId": "00000000-0000-0000-0000-000000000000" } }

扩展 body

定义为符合以下以 TypeScript 表示的 Extensions 定义以及每个字段详细说明的 JSON:

type Extensions = { assetRequestHeaders: ExpoAssetHeaderDictionary; ... } type ExpoAssetHeaderDictionary = { [assetKey: string]: { [headerName: string]: string, }; }
  • assetRequestHeaders:MAY 包含一个头(key、value)对的字典,用于与资源请求一起发送。Key 和 value MUST 都是字符串。

指令 body

定义为符合以下以 TypeScript 表示的 Directive 定义以及每个字段详细说明的 JSON:

type Directive = { type: string; parameters?: { [key: string]: any }; extra?: { [key: string]: any }; };
  • type:指令的类型。
  • parameters:MAY 包含任何与 type 相关的附加信息。
  • extra:用于存储可选的 “extra” 信息,例如第三方信息。例如,如果更新托管在 Expo Application Services (EAS) 上,则可以包含 EAS 项目 ID。

符合规范的客户端库和服务器 MAY 指定并实现适用于应用需求的特定指令类型。例如,到目前为止,Expo Application Services 使用了一种类型:rollBackToEmbedded,它指示 expo-updates 库使用宿主应用中嵌入的更新,而不是任何其他已下载的更新。

资源请求

符合规范的客户端库 MUST 对清单中指定的资源 URL 发起 GET 请求。客户端库 SHOULD 包含一个接受清单中指定的资源内容类型的标头。此外,客户端库 SHOULD 指定客户端库能够处理的压缩编码。

示例标头:

accept: image/jpeg, */* accept-encoding: br, gzip

符合规范的客户端库 MUST 还要为此资源键包含 assetRequestHeaders 中包含的任何标头(键、值)对。

资源响应

位于特定 URL 的资源 MUST NOT 自创建以来被更改或移除,因为客户端库可能会在任何更新时随时获取资源。符合规范的客户端 MUST 验证资源的 base64url 编码 SHA-256 哈希与清单中该资源的 hash 字段匹配。

资源响应标头

资源 MUST 使用客户端根据请求的 accept-encoding 标头所支持的压缩格式进行编码。服务器 MAY 提供未压缩的资源。响应 MUST 包含一个 content-type 标头,其中包含该资源的 MIME 类型。 例如:

content-encoding: br content-type: application/javascript

建议为资源提供 cache-control 标头,并将其设置为较长的持续时间,因为位于给定 URL 的资源不得更改。例如:

cache-control: public, max-age=31536000, immutable

压缩

资源 SHOULD 能够以 GzipBrotli 压缩方式提供。

代码签名

Expo Updates 支持对清单和指令正文进行代码签名。对清单进行代码签名也会连带对资源进行签名,因为资源的哈希值存在于清单中,并会由符合规范的客户端进行验证。符合规范的客户端 MAY 使用私钥请求对清单或指令进行签名,然后在使用之前或在下载任何相应的清单资源之前,MUST 使用相应的代码签名证书验证清单或指令的签名。客户端 MUST 验证签名证书要么是自签名的受信任根证书,要么是由受信任根证书签名的证书链中的一部分。在任一情况下,根证书 MUST 嵌入在应用程序或设备的操作系统中。