Release Healer Man 0.1.28

This commit is contained in:
phenom committed 2026-09-10 14:14:02 -04:00
1 parent ed8b9e3719
commit 8c9809d3e0
23 files changed
+975 -55

No files matched your search

+21 -2
View File
@@ -1,8 +1,11 @@
import { Capacitor } from "@capacitor/core";
import { create } from "zustand";
import {
listCharacters,
replaceCharactersForOwner,
setRosterChangeListener,
loadUiPreferences,
saveUiPreferences,
type AccountResult,
} from "./accountRepository";
import type { CharacterProfile, PlayerSession } from "./types";
@@ -20,7 +23,7 @@ interface AuthResponse {
interface CloudSaveResponse {
revision: number;
updatedAt: number | null;
data: { version?: number; characters?: unknown };
data: { version?: number; characters?: unknown; uiPreferences?: unknown };
}
interface MeResponse {
@@ -39,6 +42,8 @@ let suppressUpload = false;
let pendingCharacters: CharacterProfile[] | null = null;
let saveTimer: ReturnType<typeof setTimeout> | null = null;
let saveQueue = Promise.resolve();
export const useCloudSaveStatus = create<{ state: "local" | "pending" | "synced" | "error"; message: string }>(() => ({ state: "local", message: "" }));
let saveSequence = 0;
export function onlineApiOrigin(): string {
const configured = String(import.meta.env.VITE_API_BASE_URL ?? "").replace(/\/+$/, "");
@@ -100,18 +105,24 @@ function flushCloudSave(): void {
const characters = pendingCharacters;
pendingCharacters = null;
if (!session?.accessToken || !characters) return;
const uiPreferences = loadUiPreferences(session.ownerId);
const sequence = saveSequence;
saveQueue = saveQueue.then(async () => {
await requestOnlineJson<CloudSaveResponse>("/api/cloud-save", {
method: "PUT",
body: JSON.stringify({ data: { version: 1, characters } }),
body: JSON.stringify({ data: { version: 1, characters, uiPreferences } }),
}, session.accessToken);
if (activeSession?.ownerId === session.ownerId && sequence === saveSequence) useCloudSaveStatus.setState({ state: "synced", message: "Saved to your account." });
}).catch((error) => {
console.error("Healer Man cloud save failed", error);
if (activeSession?.ownerId === session.ownerId && sequence === saveSequence) useCloudSaveStatus.setState({ state: "error", message: "Saved on this device. Cloud upload failed; retry before switching devices." });
});
}
function queueCloudSave(ownerId: string, characters: readonly CharacterProfile[]): void {
if (suppressUpload || !activeSession?.accessToken || activeSession.ownerId !== ownerId) return;
saveSequence += 1;
useCloudSaveStatus.setState({ state: "pending", message: "Uploading to your account…" });
pendingCharacters = characters.map((character) => ({ ...character }));
if (saveTimer) clearTimeout(saveTimer);
saveTimer = setTimeout(() => {
@@ -123,15 +134,23 @@ function queueCloudSave(ownerId: string, characters: readonly CharacterProfile[]
setRosterChangeListener(queueCloudSave);
export function activateOnlineSession(session: PlayerSession): void {
if (activeSession?.ownerId !== session.ownerId && saveTimer) {
clearTimeout(saveTimer); saveTimer = null; flushCloudSave();
}
activeSession = session.accessToken ? session : null;
}
export function retryCloudSave(): void {
if (activeSession) queueCloudSave(activeSession.ownerId, listCharacters(activeSession.ownerId));
}
export async function hydrateOnlineSession(session: PlayerSession): Promise<CharacterProfile[]> {
if (!session.accessToken) return listCharacters(session.ownerId);
activateOnlineSession(session);
const cloud = await requestOnlineJson<CloudSaveResponse>("/api/cloud-save", {}, session.accessToken);
suppressUpload = true;
try {
saveUiPreferences(session.ownerId, cloud.data?.uiPreferences);
return replaceCharactersForOwner(session.ownerId, cloud.data?.characters ?? []);
} finally {
suppressUpload = false;