mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 08:03:20 +00:00
fix(mobile): accept three packaged documents and drop stale generations on close
Codex review of the native store: Swift and Kotlin both accept the mermaid frame as a third document, remove superseded generations once their last session closes, and the session hook retries once per build id so a desktop upgrade gets its own recovery. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
This commit is contained in:
+6
-2
@@ -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" }
|
||||
|
||||
+25
-1
@@ -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),
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user