73 lines
2.7 KiB
TypeScript
73 lines
2.7 KiB
TypeScript
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<ResultT<string>>;
|
|
export type ChatConversationKeyResolver = (
|
|
characterId: string,
|
|
) => Promise<ResultT<string>>;
|
|
|
|
type ChatCacheAuthStorage = Pick<
|
|
IAuthStorage,
|
|
"getLoginProvider" | "getDeviceId"
|
|
>;
|
|
type ChatCacheUserStorage = Pick<IUserStorage, "getUserId">;
|
|
|
|
/**
|
|
* 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<ResultT<string>> {
|
|
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<ResultT<string>> {
|
|
const ownerResult = await resolveChatCacheOwnerKey();
|
|
if (Result.isErr(ownerResult)) return ownerResult;
|
|
return Result.ok(buildChatConversationKey(ownerResult.data, characterId));
|
|
}
|