Update Healer Man server
This commit is contained in:
1 parent
3e30af4c98
commit
97ee0c13f9
25 files changed
+576
-179
No files matched your search
@@ -2,6 +2,7 @@ import { Capacitor } from "@capacitor/core";
|
||||
import {
|
||||
appVersionMeetsMinimum,
|
||||
changedFilesForPack,
|
||||
contentPackIdsForLogicalUrls,
|
||||
filesForContentPack,
|
||||
parseContentManifest,
|
||||
type ContentManifest,
|
||||
@@ -636,6 +637,26 @@ export async function installContentPack(packId: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/** Installs only the packs owning the selected run's logical model URLs. */
|
||||
export async function installContentForLogicalUrls(logicalUrls: readonly string[]): Promise<void> {
|
||||
await validateInstalledContentStorage();
|
||||
const logicalPaths = logicalUrls.map((logicalUrl) => {
|
||||
try { return new URL(logicalUrl, "https://healer-man.invalid").pathname; } catch { return logicalUrl; }
|
||||
});
|
||||
const alreadyAvailable = logicalPaths.every((logicalPath) => (
|
||||
Boolean(snapshot.bundledManifest?.files[logicalPath])
|
||||
|| Boolean(installedState.files[logicalPath] && validatedInstalledFiles.has(logicalPath))
|
||||
));
|
||||
if (alreadyAvailable) return;
|
||||
const manifest = snapshot.manifest ?? await checkForContentUpdates();
|
||||
if (!manifest) throw new Error(snapshot.error || "Content server is unavailable.");
|
||||
for (const packId of contentPackIdsForLogicalUrls(manifest, logicalUrls)) {
|
||||
if (!contentPackIsUsable(packId) || contentPackHasUpdate(packId)) {
|
||||
await installContentPack(packId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeContentPack(packId: string): Promise<void> {
|
||||
if (contentPackIsBundled(packId)) return;
|
||||
const remainingFiles = { ...installedState.files };
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
appVersionMeetsMinimum,
|
||||
changedFilesForPack,
|
||||
contentPackIdsForLogicalUrls,
|
||||
filesForContentPack,
|
||||
parseContentManifest,
|
||||
type ContentManifest,
|
||||
@@ -46,4 +47,28 @@ describe("content manifest", () => {
|
||||
"/assets/a.glb": { sha256: "a".repeat(64) },
|
||||
}).map(([logicalPath]) => logicalPath)).toEqual(["/assets/b.glb"]);
|
||||
});
|
||||
|
||||
it("resolves a seeded run's logical models to distinct owning packs", () => {
|
||||
const withSecondPack: ContentManifest = {
|
||||
...manifest,
|
||||
packs: [...manifest.packs, {
|
||||
id: "shared:creatures", label: "Shared", bytes: 1, fileCount: 1,
|
||||
bundledOnAndroid: false, dependencies: [],
|
||||
}],
|
||||
files: {
|
||||
...manifest.files,
|
||||
"/assets/shared.glb": {
|
||||
packId: "shared:creatures", url: "/content/objects/shared.glb",
|
||||
sha256: "c".repeat(64), bytes: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(contentPackIdsForLogicalUrls(withSecondPack, [
|
||||
"/assets/shared.glb",
|
||||
"/assets/a.glb",
|
||||
"/assets/shared.glb?cache=1",
|
||||
])).toEqual(["dungeon:test", "shared:creatures"]);
|
||||
expect(() => contentPackIdsForLogicalUrls(withSecondPack, ["/assets/missing.glb"]))
|
||||
.toThrow(/missing randomized asset/i);
|
||||
});
|
||||
});
|
||||
@@ -32,6 +32,26 @@ export interface ContentManifest {
|
||||
readonly files: Readonly<Record<string, ContentManifestFile>>;
|
||||
}
|
||||
|
||||
/** Resolves runtime logical asset URLs to the independently installable packs that own them. */
|
||||
export function contentPackIdsForLogicalUrls(
|
||||
manifest: ContentManifest,
|
||||
logicalUrls: readonly string[],
|
||||
): readonly string[] {
|
||||
const packIds = new Set<string>();
|
||||
for (const logicalUrl of logicalUrls) {
|
||||
let pathname: string;
|
||||
try {
|
||||
pathname = new URL(logicalUrl, "https://healer-man.invalid").pathname;
|
||||
} catch {
|
||||
throw new Error(`Invalid content asset URL: ${logicalUrl}`);
|
||||
}
|
||||
const entry = manifest.files[pathname];
|
||||
if (!entry) throw new Error(`The content catalog is missing randomized asset ${pathname}.`);
|
||||
packIds.add(entry.packId);
|
||||
}
|
||||
return [...packIds].sort();
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user