diff --git a/mobile/packages/expo-mobile-web-shell/android/src/main/java/expo/modules/mobilewebshell/MobileWebPackageStore.kt b/mobile/packages/expo-mobile-web-shell/android/src/main/java/expo/modules/mobilewebshell/MobileWebPackageStore.kt index b2e5e23ccd8..685b3e60262 100644 --- a/mobile/packages/expo-mobile-web-shell/android/src/main/java/expo/modules/mobilewebshell/MobileWebPackageStore.kt +++ b/mobile/packages/expo-mobile-web-shell/android/src/main/java/expo/modules/mobilewebshell/MobileWebPackageStore.kt @@ -173,7 +173,11 @@ internal class MobileWebPackageStore internal constructor( @Synchronized fun closeSession(sessionId: String) { - sessions.remove(sessionId) + val session = sessions.remove(sessionId) ?: return + runCatching { + val generations = File(File(cacheRoot, session.hostKey), "generations") + removeOtherGenerations(generations, session.hostKey, requestedBuildId(generations, null)) + } } @Synchronized @@ -329,7 +333,7 @@ internal class MobileWebPackageStore internal constructor( val assets = parsedAssets(assetValues) require( assets.values.sumOf { it.byteLength } == declaredTotalBytes && - assets.values.count { it.role == "document" } in 1..2 && + assets.values.count { it.role == "document" } in 1..MOBILE_WEB_DOCUMENT_PATHS.size && entrypoint == "index.html" && assets[entrypoint]?.role == "document" ) { "mobile_web_manifest_invalid" } diff --git a/mobile/packages/expo-mobile-web-shell/android/src/test/java/expo/modules/mobilewebshell/MobileWebPackageStoreTest.kt b/mobile/packages/expo-mobile-web-shell/android/src/test/java/expo/modules/mobilewebshell/MobileWebPackageStoreTest.kt index eae8fe5c9e1..489f2b7fc93 100644 --- a/mobile/packages/expo-mobile-web-shell/android/src/test/java/expo/modules/mobilewebshell/MobileWebPackageStoreTest.kt +++ b/mobile/packages/expo-mobile-web-shell/android/src/test/java/expo/modules/mobilewebshell/MobileWebPackageStoreTest.kt @@ -51,6 +51,31 @@ class MobileWebPackageStoreTest { ) } + @Test + fun commitsAllPackagedDocuments() { + val store = jvmMobileWebPackageStore(temporary.newFolder()) + val paths = listOf("index.html", "markdown-editor.html", "mermaid-frame.html") + val fixture = mobileWebStoreFixture { manifest -> + val asset = manifest.getJSONArray("assets").getJSONObject(0) + manifest.put("assets", org.json.JSONArray(paths.map { path -> + JSONObject(asset.toString()).put("path", path) + })) + manifest.put("totalBytes", manifest.getInt("totalBytes") * paths.size) + } + paths.forEach { path -> + store.writeStagedAsset( + "paired-host", fixture.buildId, path, Base64.getEncoder().encodeToString(fixture.bytes) + ) + } + store.commitGeneration("paired-host", fixture.buildId, fixture.manifestJson) + val session = store.openSession("paired-host", fixture.buildId, 1) + paths.forEach { path -> + val asset = store.readAsset(session.getValue("sessionId"), path) + assertArrayEquals(fixture.bytes, asset.bytes) + assertTrue(asset.isDocument) + } + } + @Test fun rejectsManifestsThatDoNotHashToTheBuildId() { val root = temporary.newFolder() @@ -363,7 +388,6 @@ class MobileWebPackageStoreTest { store.closeSession(previousSession.getValue("sessionId")) store.closeSession(active.getValue("sessionId")) - store.commitFixture("paired-host", current) assertEquals( listOf(current.buildId), diff --git a/mobile/packages/expo-mobile-web-shell/ios-tests/MobileWebPackageStoreTests.swift b/mobile/packages/expo-mobile-web-shell/ios-tests/MobileWebPackageStoreTests.swift index fa0f668c470..074d2e46153 100644 --- a/mobile/packages/expo-mobile-web-shell/ios-tests/MobileWebPackageStoreTests.swift +++ b/mobile/packages/expo-mobile-web-shell/ios-tests/MobileWebPackageStoreTests.swift @@ -11,6 +11,7 @@ enum MobileWebPackageStoreTests { defer { try? FileManager.default.removeItem(at: root) } try commitsAndReadsExactGeneration(root: root.appendingPathComponent("verified")) + try commitsAllPackagedDocuments(root: root.appendingPathComponent("all-documents")) try rejectsMalformedManifests(root: root.appendingPathComponent("manifests")) acceptsOnlyExactCanonicalAssetPaths() acceptsOnlyExactSha256Tokens() @@ -94,6 +95,36 @@ enum MobileWebPackageStoreTests { precondition(staged?.isEmpty != false) } + private static func commitsAllPackagedDocuments(root: URL) throws { + let store = MobileWebPackageStore(cacheRoot: root) + let paths = ["index.html", "markdown-editor.html", "mermaid-frame.html"] + let fixture = try mobileWebStoreFixture { manifest in + let asset = (manifest["assets"] as! [[String: Any]])[0] + manifest["assets"] = paths.map { path in + var document = asset + document["path"] = path + return document + } + manifest["totalBytes"] = (manifest["totalBytes"] as! Int) * paths.count + } + for path in paths { + try store.writeStagedAsset( + hostIdentity: "paired-host", buildId: fixture.buildId, path: path, + dataBase64: fixture.bytes.base64EncodedString() + ) + } + try store.commitGeneration( + hostIdentity: "paired-host", buildId: fixture.buildId, manifestJson: fixture.manifestJson + ) + let session = try store.openSession( + hostIdentity: "paired-host", buildId: fixture.buildId, bridgeVersion: 1 + ) + for path in paths { + let asset = try store.readAsset(sessionId: session["sessionId"]!, path: path) + precondition(asset.data == fixture.bytes && asset.isDocument) + } + } + private static func rejectsMalformedManifests(root: URL) throws { let store = MobileWebPackageStore(cacheRoot: root) let valid = try mobileWebStoreFixture() @@ -526,7 +557,6 @@ enum MobileWebPackageStoreTests { store.closeSession(sessionId: previousSession["sessionId"]!) store.closeSession(sessionId: active["sessionId"]!) - try mobileWebStoreCommit(store: store, host: "paired-host", fixture: current) let generations = mobileWebStoreHostRoot(cacheRoot: root, host: "paired-host") .appendingPathComponent("generations") diff --git a/mobile/packages/expo-mobile-web-shell/ios/MobileWebPackageStore.swift b/mobile/packages/expo-mobile-web-shell/ios/MobileWebPackageStore.swift index 54e120b3bc1..6067585c5a3 100644 --- a/mobile/packages/expo-mobile-web-shell/ios/MobileWebPackageStore.swift +++ b/mobile/packages/expo-mobile-web-shell/ios/MobileWebPackageStore.swift @@ -210,7 +210,15 @@ final class MobileWebPackageStore { func closeSession(sessionId: String) { locked { - _ = sessions.removeValue(forKey: sessionId) + guard let session = sessions.removeValue(forKey: sessionId), + let root = try? cacheRoot() + else { return } + let generations = root.appendingPathComponent(session.hostKey, isDirectory: true) + .appendingPathComponent("generations", isDirectory: true) + guard let newest = try? requestedBuildId(generations: generations, buildId: nil) else { return } + try? removeOtherGenerations( + generations: generations, hostKey: session.hostKey, keeping: newest, cacheRoot: root + ) } } @@ -403,7 +411,7 @@ final class MobileWebPackageStore { } guard totalBytes == declaredTotalBytes, - (1...2).contains(documentCount), + (1...mobileWebDocumentPaths.count).contains(documentCount), entrypoint == "index.html", records[entrypoint]?.role == "document" else {