Files
boss/src/lib/boss-app-client.ts
2026-03-26 23:16:56 +08:00

153 lines
4.6 KiB
TypeScript

"use client";
const APP_HISTORY_KEY = "boss.app.history.v1";
const NATIVE_SESSION_KEY = "boss.native.session.v1";
const MAX_HISTORY_ENTRIES = 48;
export type NativeSessionSnapshot = {
restoreToken: string;
account: string;
displayName: string;
expiresAt: string;
lastSyncedAt: string;
};
export type AppBackAction =
| { mode: "history" }
| { mode: "replace"; target: string }
| { mode: "noop" };
function isBrowser() {
return typeof window !== "undefined";
}
function readJsonStorage<T>(key: string, storage: Storage): T | null {
try {
const raw = storage.getItem(key);
return raw ? (JSON.parse(raw) as T) : null;
} catch {
return null;
}
}
function writeJsonStorage(key: string, value: unknown, storage: Storage) {
try {
storage.setItem(key, JSON.stringify(value));
} catch {
// Ignore storage write failures in constrained webviews.
}
}
function basePathFor(input: string) {
return input.split("#")[0]?.split("?")[0] || "/";
}
function fallbackForPath(input: string) {
const pathname = basePathFor(input);
if (pathname === "/auth/login") return null;
if (pathname.startsWith("/auth/")) return "/auth/login";
if (pathname === "/conversations") return null;
if (pathname.startsWith("/conversations/")) return "/conversations";
if (pathname === "/devices") {
return input.includes("?") ? "/devices" : "/conversations";
}
if (pathname.startsWith("/devices/")) return "/devices";
if (pathname === "/me") return "/conversations";
if (pathname.startsWith("/me/")) return "/me";
return "/conversations";
}
export function currentAppLocation() {
if (!isBrowser()) return "/";
return `${window.location.pathname}${window.location.search}${window.location.hash}`;
}
export function pushAppHistoryEntry(path: string) {
if (!isBrowser()) return;
const current = readJsonStorage<string[]>(APP_HISTORY_KEY, window.sessionStorage) ?? [];
const normalized = path || "/";
const next = current.filter((entry) => entry !== normalized);
next.push(normalized);
writeJsonStorage(APP_HISTORY_KEY, next.slice(-MAX_HISTORY_ENTRIES), window.sessionStorage);
}
export function popAppHistoryEntry(expectedPath?: string) {
if (!isBrowser()) return;
const current = readJsonStorage<string[]>(APP_HISTORY_KEY, window.sessionStorage) ?? [];
if (!current.length) return;
if (expectedPath && current[current.length - 1] !== expectedPath) {
writeJsonStorage(
APP_HISTORY_KEY,
current.filter((entry) => entry !== expectedPath),
window.sessionStorage,
);
return;
}
current.pop();
writeJsonStorage(APP_HISTORY_KEY, current, window.sessionStorage);
}
export function resolveAppBackAction(currentPath: string, explicitFallback?: string): AppBackAction {
if (!isBrowser()) {
return explicitFallback ? { mode: "replace", target: explicitFallback } : { mode: "noop" };
}
const history = readJsonStorage<string[]>(APP_HISTORY_KEY, window.sessionStorage) ?? [];
const previous = [...history].reverse().find((entry) => entry !== currentPath);
if (previous) {
return { mode: "history" };
}
const fallback = explicitFallback ?? fallbackForPath(currentPath);
if (fallback && fallback !== currentPath) {
return { mode: "replace", target: fallback };
}
return { mode: "noop" };
}
export async function isNativeBossApp() {
if (!isBrowser()) return false;
const { Capacitor } = await import("@capacitor/core");
return Capacitor.getPlatform() !== "web";
}
async function preferencesApi() {
if (!(await isNativeBossApp())) return null;
const { Preferences } = await import("@capacitor/preferences");
return Preferences;
}
export async function persistNativeSessionSnapshot(snapshot: NativeSessionSnapshot) {
const serialized = JSON.stringify(snapshot);
const preferences = await preferencesApi();
if (preferences) {
await preferences.set({ key: NATIVE_SESSION_KEY, value: serialized });
return;
}
if (isBrowser()) {
window.localStorage.setItem(NATIVE_SESSION_KEY, serialized);
}
}
export async function readNativeSessionSnapshot() {
const preferences = await preferencesApi();
if (preferences) {
const result = await preferences.get({ key: NATIVE_SESSION_KEY });
return result.value ? (JSON.parse(result.value) as NativeSessionSnapshot) : null;
}
if (!isBrowser()) return null;
return readJsonStorage<NativeSessionSnapshot>(NATIVE_SESSION_KEY, window.localStorage);
}
export async function clearNativeSessionSnapshot() {
const preferences = await preferencesApi();
if (preferences) {
await preferences.remove({ key: NATIVE_SESSION_KEY });
return;
}
if (isBrowser()) {
window.localStorage.removeItem(NATIVE_SESSION_KEY);
}
}