Expo Updates v0

编辑页面


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

版本 0

更新于 2021-12-01


简介

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

一致性

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

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

该协议的符合规范的实现可以提供额外功能,但在明确禁止的情况下不得如此,或不得因此导致不符合规范。

概述

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

update 定义为一个 manifest 以及 manifest 中引用的资源。 Expo Updates 是一种用于组装并向运行在多个平台上的应用交付更新的协议。

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

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

  1. 客户端库将针对最新的 manifest 发起一个 请求,并在请求头中指定约束。
  2. 如果下载到新的 manifest,客户端库将继续发起额外请求,下载并存储 manifest 中指定的任何缺失资源。
  3. 客户端库将修改其本地状态,以反映新的更新已被添加到本地缓存中。它还会使用响应 headers 中找到的新 expo-manifest-filtersexpo-server-defined-headers 更新本地状态。

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

Manifest 请求

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

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

符合规范的客户端库 MUST 至少根据支持的响应结构发送以下之一:accept: application/expo+jsonaccept: application/jsonaccept: multipart/mixed,但 SHOULD 发送 accept: application/expo+json, application/json, multipart/mixed。符合规范的客户端库 MAY 使用 RFC 7231 中规定的 "q" 参数表达偏好,其默认值为 1

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

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

示例:

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"

Manifest 响应

符合规范的服务器 MUST 以至少两种方式中的一种返回结构化响应。符合规范的服务器 MAY 支持其中一种或两种响应结构,当请求了不受支持的响应结构时,服务器 SHOULD 返回 HTTP 406 错误状态。

  • 对于 content-type: application/jsoncontent-type: application/expo+json 的响应,[manifest headers](#manifest-response-headers) MUST 在响应 headers 中发送,且 [manifest body](#manifest-response-body) MUST 在响应 body 中发送。
  • 对于 content-type: multipart/mixed 的响应,响应 MUST 按照 multipart manifest response 部分所指定的方式进行结构化。

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

Manifest 响应头

expo-protocol-version: 0 expo-sfv-version: 0 expo-manifest-filters: <expo-sfv> expo-server-defined-headers: <expo-sfv> cache-control: * content-type: * expo-signature: *
  • expo-protocol-version 描述本规范中定义的协议版本,且 MUST 为 0
  • expo-sfv-version MUST 为 0
  • expo-manifest-filters 是一个 Expo SFV 字典。它用于根据客户端库存储的更新中,在 manifest 里找到的 metadata 属性来过滤更新。如果过滤器中提到了某个字段,则要将对应字段包含在更新中,该字段在 metadata 中必须缺失或与其值相等。客户端库 MUST 保存 manifest filters,直到它被较新的响应覆盖。
  • expo-server-defined-headers 是一个 Expo SFV 字典。它定义了客户端库 MUST 保存的 headers,直到它们被较新的字典覆盖,并且它们 MUST 被包含在之后每一次 manifest request 中。
  • cache-control MUST 设置为适当的较短时间。推荐使用 cache-control: private, max-age=0,以确保返回最新的 manifest。设置更长的缓存时间可能导致更新过期。
  • content-type MUST 由 RFC 7231 中定义的 主动协商 决定。由于客户端库 要求 在每次 manifest 请求中发送 accept header,因此这将始终是 application/expo+jsonapplication/json;否则请求将返回 406 错误。
  • 如果对 manifest 的请求包含了 expo-expect-signature header,则 expo-signature SHOULD 包含将在 代码签名 验证步骤中使用的 manifest 签名。这是一个 Expo SFV 字典,MAY 包含以下任意键值对:
    • sig MUST 包含 manifest 的签名。该字段名称与 expo-expect-signature 的名称一致。
    • keyid MAY 包含服务器用于签署响应的密钥的 keyId。客户端 SHOULD 使用与此 keyid 匹配的证书来验证签名。
    • alg MAY 包含服务器用于签署响应的算法。客户端 SHOULD 仅在该算法与 keyid 所匹配证书定义的算法一致时使用此字段。

Manifest 响应体

响应 body MUST 是一个 manifest,它被定义为同时符合以下用 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。
  • createdAt:创建该更新的日期和时间至关重要,因为客户端库会选择最新的更新(受 expo-manifest-filters header 提供的任何约束影响)。datetime 应按照 ISO 8601 格式化。
  • runtimeVersion:可以是开发者定义的任意字符串。它规定了运行关联更新所需的原生代码配置。
  • launchAsset:一个特殊资源,作为应用代码的入口点。该资源的 fileExtension 字段将被忽略,并 SHOULD 省略。
  • assets:更新 bundle 使用的资源数组,例如 JavaScript、图片和字体。在执行更新之前,所有资源(包括 launchAsset)都应下载到磁盘,并且应向应用代码提供从资源 key 到磁盘位置的映射。
  • 每个 asset 对象的属性:
    • hash:文件的 Base64URL 编码 SHA-256 哈希,用于保证完整性。Base64URL 编码由 IETF RFC 4648 定义。
    • key:用于在更新的应用代码中引用此资源的 key。例如,此 key 可由处理应用代码的单独构建步骤生成,例如 bundler。
    • contentType:文件的 MIME 类型,如 RFC 2045 所定义。例如,application/javascriptimage/jpeg
    • fileExtension:在客户端保存文件时建议使用的扩展名。某些平台(如 iOS)要求某些文件类型保存时带有扩展名。扩展名前 MUST 带有 . 前缀。例如,.jpeg。在某些情况下,例如 launchAsset,该字段会被忽略,转而使用本地确定的扩展名。如果省略该字段且本地没有规定扩展名,则资源将不带扩展名保存。例如,./filename 末尾没有 .。 如果文件扩展名不为空但缺少 . 前缀,符合规范的客户端 SHOULD 为文件扩展名加上 . 前缀。
    • url:可获取该文件的位置。
  • metadata:与更新关联的元数据。它是一个具有字符串值的字典。服务器 MUST 至少返回一个空对象,但 MAY 在对象中返回任何希望用于过滤更新的内容。metadata MUST 通过随附的 expo-manifest-filters header 定义的过滤器。
  • extra:用于指定可选的“额外”信息,例如第三方配置。服务器 MUST 至少返回一个空对象。Expo Updates 不定义也不依赖此字段,但其他库可能会。例如,如果更新托管在 Expo Application Services(EAS)上,可能会包含 EAS 项目 ID 和 app config(许多 Expo 库通过 expo-constants 使用它):
"extra": { "eas": { "projectId": "00000000-0000-0000-0000-000000000000" }, "expoConfig": { "name": "...", "version": "...", "iconUrl": "...", %%placeholder-start%%... %%placeholder-end%% }, }

Multipart manifest 响应

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

此响应格式的 headers 与 manifest response headers 相同,但有以下例外:

  • content-type 应具有 RFC 2046 定义的 multipart/mixed
  • 如果使用代码签名,则 expo-signature 应包含在下方 manifest part 的 headers 中

每个 part 定义如下:

  1. 必需的 "manifest" part:
    • MUST 具有 part header content-disposition: inline; name="manifest"
    • MUST 具有 part header content-type: application/jsonapplication/expo+json
    • 如果使用代码签名,SHOULD 具有 manifest response headers 中定义的 expo-signature part header。
    • [manifest body](#manifest-response-body) MUST 在 part body 中发送。
  2. 可选的 "extensions" part:
    • MUST 具有 part header content-disposition: inline; name="extensions"
    • MUST 具有 part header content-type: application/json
    • [manifest extensions](#manifest-extensions) MUST 在 part body 中发送。

Manifest 扩展

定义为同时符合以下用 TypeScript 表达的 ManifestExtensions 定义以及每个字段的详细说明的 JSON:

type ManifestExtensions = { assetRequestHeaders: ExpoAssetHeaderDictionary; ... } type ExpoAssetHeaderDictionary = { [assetKey: string]: { [headerName: string]: string, }; }
  • assetRequestHeaders:MAY 包含一个 header(键、值)对字典,用于在资源请求中一并包含。键和值 MUST 都是字符串。

资源请求

符合规范的客户端库 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 包含一个带有该资源 MIME 类型的 content-type 头。 例如:

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

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

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

压缩

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

代码签名

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

客户端库

参见 参考客户端库