import { LoginStatus } from "@/data/dto/auth"; import { AuthStorage, type IAuthStorage } from "@/data/storage/auth"; import { UserStorage, type IUserStorage } from "@/data/storage/user"; import { Result, type Result as ResultT } from "@/utils/result"; export type ChatCacheIdentityResolver = () => Promise>; export type ChatConversationKeyResolver = ( characterId: string, ) => Promise>; type ChatCacheAuthStorage = Pick< IAuthStorage, "getLoginProvider" | "getDeviceId" >; type ChatCacheUserStorage = Pick; /** * Resolve the only identity allowed to access the current chat cache. * * A user id is shared by neither account switches nor guest/account changes, * so it is the preferred namespace for both authenticated and guest users. * The device id is only a safe fallback for an active guest session. Real * accounts without a persisted user id must skip the cache instead of falling * back to a device-wide or anonymous namespace. */ export async function resolveChatCacheOwnerKey( authStorage: ChatCacheAuthStorage = AuthStorage.getInstance(), userStorage: ChatCacheUserStorage = UserStorage.getInstance(), ): Promise> { const providerResult = await authStorage.getLoginProvider(); if (Result.isErr(providerResult)) return providerResult; if ( providerResult.data === null || providerResult.data === LoginStatus.NotLoggedIn ) { return Result.err(new Error("Chat cache identity is unavailable.")); } const userIdResult = await userStorage.getUserId(); if (Result.isErr(userIdResult)) return userIdResult; if (userIdResult.data && userIdResult.data.length > 0) { return Result.ok(`user:${userIdResult.data}`); } if (providerResult.data === LoginStatus.Guest) { const deviceIdResult = await authStorage.getDeviceId(); if (Result.isErr(deviceIdResult)) return deviceIdResult; if (deviceIdResult.data && deviceIdResult.data.length > 0) { return Result.ok(`device:${deviceIdResult.data}`); } } return Result.err(new Error("Chat cache identity is unavailable.")); } export function buildChatConversationKey( ownerKey: string, characterId: string, ): string { if (ownerKey.trim().length === 0 || characterId.trim().length === 0) { throw new Error("Chat owner and character identities must not be empty."); } return `${ownerKey}::character:${encodeURIComponent(characterId)}`; } export async function resolveChatConversationKey( characterId: string, ): Promise> { const ownerResult = await resolveChatCacheOwnerKey(); if (Result.isErr(ownerResult)) return ownerResult; return Result.ok(buildChatConversationKey(ownerResult.data, characterId)); }