From f4f0915e7087f687def144373eafc11dc061dbb2 Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Wed, 23 Sep 2026 00:47:55 -0400 Subject: [PATCH] refactor(mobile-web): drop a test cast and shape-named field maps The bomb test's inflation log is typed by its hoisted factory's return instead of an assertion, and the zod field maps shared by the bundle window schemas are windowParamsFields and windowHeaderFields. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb --- .../transport/mobile-web-bundle-fetch-range.test.ts | 5 ++--- .../src/transport/mobile-web-bundle-reply-schemas.ts | 6 +++--- src/shared/mobile-web-bundle/bundle-rpc-contract.ts | 12 ++++++------ 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/mobile/src/transport/mobile-web-bundle-fetch-range.test.ts b/mobile/src/transport/mobile-web-bundle-fetch-range.test.ts index bd2bf362b20..0d58d2fad52 100644 --- a/mobile/src/transport/mobile-web-bundle-fetch-range.test.ts +++ b/mobile/src/transport/mobile-web-bundle-fetch-range.test.ts @@ -12,12 +12,11 @@ import type { RpcClient } from './rpc-client' import type { RpcResponse } from './types' type Inflation = { readonly outLength: number; readonly resultLength: number } +type LoggedInflation = Inflation & { readonly body: Uint8Array } /** Every inflation in the process, keyed by the gzip body it was handed; a host reads back only * the bodies it sent, so a read left running by an earlier test's fetch never lands in its sink. */ -const inflationLog = vi.hoisted( - () => [] as { body: Uint8Array; outLength: number; resultLength: number }[] -) +const inflationLog = vi.hoisted((): LoggedInflation[] => []) // Observes the bound the decoder hands fflate, and what fflate hands back inside it. vi.mock('fflate', async (importOriginal) => { diff --git a/mobile/src/transport/mobile-web-bundle-reply-schemas.ts b/mobile/src/transport/mobile-web-bundle-reply-schemas.ts index e8bcb3c3230..ea68e7dbd14 100644 --- a/mobile/src/transport/mobile-web-bundle-reply-schemas.ts +++ b/mobile/src/transport/mobile-web-bundle-reply-schemas.ts @@ -130,7 +130,7 @@ export const MobileWebBundleManifestReplySchema = z.looseObject({ /** Self-describing on purpose: `buildId`, `path` and `offset` are echoed so a reassembler cannot * misplace a reply, and `sha256`/`assetByteLength` describe the whole asset rather than this * window, which is what lets the fetch verify without a second index. Shared by both read replies. */ -const windowHeaderShape = { +const windowHeaderFields = { buildId: z.string().regex(SHA256_PATTERN), path: MobileWebBundleAssetPathSchema, offset: z.number().int().nonnegative().max(MOBILE_WEB_BUNDLE_MAX_ASSET_BYTES), @@ -140,7 +140,7 @@ const windowHeaderShape = { } export const MobileWebBundleChunkReplySchema = z.looseObject({ - ...windowHeaderShape, + ...windowHeaderFields, dataBase64: z.string().max(MAX_DATA_BASE64_LENGTH) }) @@ -148,7 +148,7 @@ export const MobileWebBundleChunkReplySchema = z.looseObject({ * enum: an encoding this build cannot decode is a typed refusal at the decoder, which names it, * rather than a reply-shape failure that names nothing. */ export const MobileWebBundleRangeReplySchema = z.looseObject({ - ...windowHeaderShape, + ...windowHeaderFields, encoding: z.string().min(1).max(32), dataBase64: z.string().max(MOBILE_WEB_BUNDLE_RANGE_MAX_DATA_BASE64_LENGTH) }) diff --git a/src/shared/mobile-web-bundle/bundle-rpc-contract.ts b/src/shared/mobile-web-bundle/bundle-rpc-contract.ts index d740e78fee9..9541985b5a8 100644 --- a/src/shared/mobile-web-bundle/bundle-rpc-contract.ts +++ b/src/shared/mobile-web-bundle/bundle-rpc-contract.ts @@ -64,7 +64,7 @@ export const MobileWebBundleManifestResultSchema = z }) .strict() -const windowParamsShape = { +const windowParamsFields = { buildId: z.string().regex(SHA256_PATTERN), path: MobileWebBundleAssetPathSchema, offset: z.number().int().nonnegative().max(MOBILE_WEB_BUNDLE_MAX_ASSET_BYTES) @@ -72,14 +72,14 @@ const windowParamsShape = { /** No `multipleOf` pin on `offset`: alignment is against the host's advertised `chunkBytes`, which * may be smaller than the constant, so the host rejects a misaligned offset instead. */ -export const MobileWebBundleChunkParamsSchema = z.object(windowParamsShape).strict() +export const MobileWebBundleChunkParamsSchema = z.object(windowParamsFields).strict() /** The chunk params exactly, on the `rangeBytes` grid the manifest reply advertised. */ export const MobileWebBundleRangeParamsSchema = MobileWebBundleChunkParamsSchema /** What every chunk or range reply restates about the window it answers. */ -const windowHeaderShape = { - ...windowParamsShape, +const windowHeaderFields = { + ...windowParamsFields, /** The whole asset, not this window: named for it so a reassembler cannot misread the two, and * paired with `sha256` it describes the asset without a second index. */ assetByteLength: z.number().int().nonnegative().max(MOBILE_WEB_BUNDLE_MAX_ASSET_BYTES), @@ -90,14 +90,14 @@ const windowHeaderShape = { /** Strict, so a later `contentEncoding` is only a Rule 1 optional-field addition for clients whose * own reply readers are not strict. */ export const MobileWebBundleChunkResultSchema = z - .object({ ...windowHeaderShape, dataBase64: z.string().max(MAX_DATA_BASE64_LENGTH) }) + .object({ ...windowHeaderFields, dataBase64: z.string().max(MAX_DATA_BASE64_LENGTH) }) .strict() /** A chunk result plus the encoding of `dataBase64`, whose decoded length is * `min(rangeBytes, assetByteLength - offset)`. */ export const MobileWebBundleRangeResultSchema = z .object({ - ...windowHeaderShape, + ...windowHeaderFields, encoding: z.enum(MOBILE_WEB_BUNDLE_RANGE_ENCODINGS), dataBase64: z.string().max(MOBILE_WEB_BUNDLE_RANGE_MAX_DATA_BASE64_LENGTH) })