153 lines
4.6 KiB
TypeScript
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);
|
|
}
|
|
}
|