ai-chatbot/lib/openclaw-chat/viewer-session.ts

132 lines
4.1 KiB
TypeScript

const OPENCLAW_3D_VIEWER_SESSION_PREFIX = "openclaw-3d-viewer:v1:";
const OPENCLAW_3D_VIEWER_SESSION_MAX_AGE_MS = 24 * 60 * 60 * 1000;
export type OpenClaw3DViewerSession = {
messageId: string;
viewMode: "product" | "packaging";
explodeAmount: number;
packagingExplodeAmount: number;
selectedNodeName: string | null;
selectedPackagingNodeName: string | null;
updatedAt: number;
};
type ViewerSessionStorage = Pick<Storage, "getItem" | "setItem" | "removeItem">;
function getViewerSessionKey(chatId: string) {
return `${OPENCLAW_3D_VIEWER_SESSION_PREFIX}${chatId}`;
}
function removeViewerSession(storage: ViewerSessionStorage, key: string) {
try {
storage.removeItem(key);
} catch {
// Storage can be unavailable under strict browser privacy policies.
}
}
function normalizeExplodeAmount(value: unknown) {
const amount = typeof value === "number" ? value : Number(value);
if (!Number.isFinite(amount)) return 0;
return Math.max(0, Math.min(1, amount));
}
export function readOpenClaw3DViewerSession(params: {
storage: ViewerSessionStorage;
chatId: string;
now?: number;
}): OpenClaw3DViewerSession | null {
const key = getViewerSessionKey(params.chatId);
try {
const raw = params.storage.getItem(key);
if (!raw) return null;
const value = JSON.parse(raw) as Record<string, unknown>;
const messageId =
typeof value.messageId === "string" ? value.messageId.trim() : "";
const updatedAt = Number(value.updatedAt);
const now = params.now ?? Date.now();
if (
!messageId ||
!Number.isFinite(updatedAt) ||
updatedAt <= 0 ||
now - updatedAt > OPENCLAW_3D_VIEWER_SESSION_MAX_AGE_MS
) {
removeViewerSession(params.storage, key);
return null;
}
return {
messageId,
viewMode: value.viewMode === "packaging" ? "packaging" : "product",
explodeAmount: normalizeExplodeAmount(
value.productExplodeAmount ?? value.explodeAmount
),
packagingExplodeAmount:
value.packagingExplodeAmount === undefined
? 1
: normalizeExplodeAmount(value.packagingExplodeAmount),
selectedNodeName:
typeof value.selectedNodeName === "string" &&
value.selectedNodeName.trim()
? value.selectedNodeName.trim()
: null,
selectedPackagingNodeName:
typeof value.selectedPackagingNodeName === "string" &&
value.selectedPackagingNodeName.trim()
? value.selectedPackagingNodeName.trim()
: null,
updatedAt,
};
} catch {
removeViewerSession(params.storage, key);
return null;
}
}
export function writeOpenClaw3DViewerSession(params: {
storage: ViewerSessionStorage;
chatId: string;
messageId: string;
explodeAmount: number;
viewMode?: "product" | "packaging";
packagingExplodeAmount?: number;
selectedNodeName?: string | null;
selectedPackagingNodeName?: string | null;
now?: number;
}) {
const messageId = params.messageId.trim();
if (!messageId) return;
try {
params.storage.setItem(
getViewerSessionKey(params.chatId),
JSON.stringify({
messageId,
viewMode: params.viewMode || "product",
explodeAmount: normalizeExplodeAmount(params.explodeAmount),
packagingExplodeAmount:
params.packagingExplodeAmount === undefined
? 1
: normalizeExplodeAmount(params.packagingExplodeAmount),
selectedNodeName: params.selectedNodeName?.trim() || null,
selectedPackagingNodeName:
params.selectedPackagingNodeName?.trim() || null,
updatedAt: params.now ?? Date.now(),
} satisfies OpenClaw3DViewerSession)
);
} catch {
// Private browsing and storage policies may disable sessionStorage.
}
}
export function clearOpenClaw3DViewerSession(params: {
storage: ViewerSessionStorage;
chatId: string;
messageId: string;
}) {
const current = readOpenClaw3DViewerSession({
storage: params.storage,
chatId: params.chatId,
});
if (current?.messageId !== params.messageId) return;
removeViewerSession(params.storage, getViewerSessionKey(params.chatId));
}