fix(chat): isolate pending flows and cancel stale requests
This commit is contained in:
@@ -20,6 +20,7 @@ const defaultScope = {
|
||||
};
|
||||
|
||||
const promotionSession: PendingChatPromotion = {
|
||||
characterId: "character_elio",
|
||||
promotionType: "image",
|
||||
lockType: "image_paywall",
|
||||
clientLockId: "promotion-1",
|
||||
@@ -181,6 +182,7 @@ function createPendingUnlock(
|
||||
): PendingChatUnlock {
|
||||
return {
|
||||
reason: "single_message_unlock",
|
||||
characterId: "character_elio",
|
||||
...input,
|
||||
stage: "auth",
|
||||
createdAt: 1,
|
||||
|
||||
@@ -58,7 +58,9 @@ export function ChatScreen() {
|
||||
loginStatus: authState.loginStatus,
|
||||
messages: state.historyMessages,
|
||||
});
|
||||
const isPromotionBootstrapReady = useChatPromotionBootstrap();
|
||||
const isPromotionBootstrapReady = useChatPromotionBootstrap(
|
||||
state.characterId,
|
||||
);
|
||||
const visibleMessages = isPromotionBootstrapReady
|
||||
? state.messages
|
||||
: state.historyMessages;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Logger } from "@/utils/logger";
|
||||
|
||||
const log = new Logger("UseChatPromotionBootstrap");
|
||||
|
||||
export function useChatPromotionBootstrap(): boolean {
|
||||
export function useChatPromotionBootstrap(characterId: string): boolean {
|
||||
const chatDispatch = useChatDispatch();
|
||||
const startedRef = useRef(false);
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
@@ -23,8 +23,8 @@ export function useChatPromotionBootstrap(): boolean {
|
||||
void (async () => {
|
||||
try {
|
||||
const [entryPromotion, pendingUnlock] = await Promise.all([
|
||||
consumePendingChatPromotion(),
|
||||
peekPendingChatUnlock(),
|
||||
consumePendingChatPromotion(characterId),
|
||||
peekPendingChatUnlock(characterId),
|
||||
]);
|
||||
const promotion = entryPromotion ?? pendingUnlock?.promotion ?? null;
|
||||
|
||||
@@ -46,7 +46,7 @@ export function useChatPromotionBootstrap(): boolean {
|
||||
setIsReady(true);
|
||||
}
|
||||
})();
|
||||
}, [chatDispatch]);
|
||||
}, [characterId, chatDispatch]);
|
||||
|
||||
return isReady;
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ export function useChatUnlockCoordinator({
|
||||
const trackedPaywallRef = useRef<string | null>(null);
|
||||
const chatState = useChatSelector(
|
||||
(state) => ({
|
||||
characterId: state.context.characterId,
|
||||
historyLoaded: state.context.historyLoaded,
|
||||
isUnlockingHistory: state.matches({ userSession: "unlockingHistory" }),
|
||||
lockedHistoryCount: state.context.lockedHistoryCount,
|
||||
@@ -125,7 +126,7 @@ export function useChatUnlockCoordinator({
|
||||
let cancelled = false;
|
||||
|
||||
const resumePendingUnlock = async () => {
|
||||
const pending = await peekPendingChatUnlock();
|
||||
const pending = await peekPendingChatUnlock(chatState.characterId);
|
||||
if (
|
||||
cancelled ||
|
||||
!pending ||
|
||||
@@ -138,7 +139,7 @@ export function useChatUnlockCoordinator({
|
||||
return;
|
||||
}
|
||||
|
||||
const consumed = await consumePendingChatUnlock();
|
||||
const consumed = await consumePendingChatUnlock(chatState.characterId);
|
||||
if (cancelled || !consumed) return;
|
||||
|
||||
chatDispatch({
|
||||
@@ -157,6 +158,7 @@ export function useChatUnlockCoordinator({
|
||||
};
|
||||
}, [
|
||||
chatDispatch,
|
||||
chatState.characterId,
|
||||
chatState.historyLoaded,
|
||||
defaultReturnUrl,
|
||||
enabled,
|
||||
|
||||
@@ -15,6 +15,10 @@ import {
|
||||
savePendingChatPromotion,
|
||||
} from "@/lib/navigation/chat_unlock_session";
|
||||
import { ROUTES } from "@/router/routes";
|
||||
import {
|
||||
DEFAULT_CHARACTER_ID,
|
||||
getCharacterBySlug,
|
||||
} from "@/data/constants/character";
|
||||
import { Logger } from "@/utils/logger";
|
||||
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
|
||||
|
||||
@@ -49,6 +53,8 @@ export default function ExternalEntryPersist({
|
||||
const hasReinitializedForPsidRef = useRef(false);
|
||||
const targetRoute = resolveExternalEntryTarget({ target });
|
||||
const destination = resolveExternalEntryDestination({ target, character });
|
||||
const characterId =
|
||||
getCharacterBySlug(character)?.id ?? DEFAULT_CHARACTER_ID;
|
||||
const resolvedPromotionType = resolveExternalEntryPromotionType({
|
||||
mode,
|
||||
promotionType,
|
||||
@@ -66,7 +72,7 @@ export default function ExternalEntryPersist({
|
||||
avatarUrl,
|
||||
}),
|
||||
targetRoute === ROUTES.chat && resolvedPromotionType
|
||||
? savePendingChatPromotion(resolvedPromotionType)
|
||||
? savePendingChatPromotion(resolvedPromotionType, characterId)
|
||||
: clearPendingChatPromotion(),
|
||||
]);
|
||||
for (const result of results) {
|
||||
@@ -87,6 +93,7 @@ export default function ExternalEntryPersist({
|
||||
avatarUrl,
|
||||
asid,
|
||||
character,
|
||||
characterId,
|
||||
destination,
|
||||
deviceId,
|
||||
mode,
|
||||
|
||||
Reference in New Issue
Block a user