263 lines
8.5 KiB
TypeScript
263 lines
8.5 KiB
TypeScript
import { type DoneActorEvent } from "xstate";
|
|
|
|
import { applyPromotionUnlockOutput } from "../helper/promotion";
|
|
import {
|
|
applySingleUnlockOutput,
|
|
countLockedHistoryMessages,
|
|
type UnlockMessageOutput,
|
|
} from "../helper/unlock";
|
|
import type { UnlockHistoryOutput } from "./actors/unlock";
|
|
import { sendMachineSetup } from "./send-flow";
|
|
import { createChatActorActionSetup } from "./setup";
|
|
import { getCharacterErrorCode } from "@/data/services/api";
|
|
|
|
const markPaymentUnlockPendingAction = sendMachineSetup.assign(() => ({
|
|
paymentUnlockPending: true,
|
|
unlockHistoryError: null,
|
|
}));
|
|
|
|
const showUnlockHistoryPromptAction = sendMachineSetup.assign(
|
|
({ context }) => ({
|
|
paymentUnlockPending: false,
|
|
unlockHistoryPromptVisible: true,
|
|
lockedHistoryCount: countLockedHistoryMessages(context.messages),
|
|
unlockHistoryError: null,
|
|
unlockHistoryPaymentGuidance: null,
|
|
}),
|
|
);
|
|
|
|
const dismissUnlockHistoryPromptAction = sendMachineSetup.assign(() => ({
|
|
paymentUnlockPending: false,
|
|
unlockHistoryPromptVisible: false,
|
|
unlockHistoryError: null,
|
|
unlockHistoryPaymentGuidance: null,
|
|
}));
|
|
|
|
const markUnlockHistoryStartedAction = sendMachineSetup.assign(() => ({
|
|
paymentUnlockPending: false,
|
|
unlockHistoryPromptVisible: false,
|
|
unlockHistoryError: null,
|
|
unlockHistoryPaymentGuidance: null,
|
|
}));
|
|
|
|
const markUnlockHistoryFailedAction = sendMachineSetup.assign(() => ({
|
|
unlockHistoryPromptVisible: true,
|
|
unlockHistoryError: "Failed to unlock messages. Please try again.",
|
|
}));
|
|
|
|
const markUnlockMessageStartedAction = sendMachineSetup.assign(
|
|
({ event }) => {
|
|
if (event.type !== "ChatUnlockMessageRequested") return {};
|
|
return {
|
|
unlockingMessage: {
|
|
displayMessageId: event.displayMessageId,
|
|
...(event.remoteMessageId
|
|
? { messageId: event.remoteMessageId }
|
|
: {}),
|
|
kind: event.kind,
|
|
...(event.lockType ? { lockType: event.lockType } : {}),
|
|
...(event.clientLockId
|
|
? { clientLockId: event.clientLockId }
|
|
: {}),
|
|
},
|
|
unlockMessageError: null,
|
|
unlockPaywallRequest: null,
|
|
};
|
|
},
|
|
);
|
|
|
|
const applyUnlockMessageSucceededAction = sendMachineSetup.assign(
|
|
({ context, event }) => {
|
|
const { output } = event as unknown as DoneActorEvent<UnlockMessageOutput>;
|
|
return {
|
|
messages: applySingleUnlockOutput(context.messages, output),
|
|
promotion: applyPromotionUnlockOutput(context.promotion, output),
|
|
unlockingMessage: null,
|
|
unlockMessageError: null,
|
|
unlockPaywallRequest: null,
|
|
creditBalance: output.response.creditBalance,
|
|
creditsCharged: output.response.creditsCharged,
|
|
requiredCredits: 0,
|
|
shortfallCredits: 0,
|
|
paymentGuidance: null,
|
|
};
|
|
},
|
|
);
|
|
|
|
const requestUnlockPaymentFromOutputAction = sendMachineSetup.assign(
|
|
({ context, event }) => {
|
|
const { output } = event as unknown as DoneActorEvent<UnlockMessageOutput>;
|
|
const shouldOpenPaywall = output.response.reason !== "not_found";
|
|
return {
|
|
promotion: applyPromotionUnlockOutput(context.promotion, output),
|
|
unlockingMessage: null,
|
|
unlockMessageError: output.response.reason,
|
|
unlockPaywallRequest: shouldOpenPaywall
|
|
? {
|
|
displayMessageId: output.displayMessageId,
|
|
...(output.response.messageId || output.request.messageId
|
|
? {
|
|
messageId:
|
|
output.response.messageId || output.request.messageId,
|
|
}
|
|
: {}),
|
|
kind: context.unlockingMessage?.kind ?? "private",
|
|
...(output.request.lockType
|
|
? { lockType: output.request.lockType }
|
|
: {}),
|
|
...(output.request.clientLockId
|
|
? { clientLockId: output.request.clientLockId }
|
|
: {}),
|
|
...(context.promotion?.message.displayId ===
|
|
output.displayMessageId
|
|
? { promotion: context.promotion.session }
|
|
: {}),
|
|
reason: output.response.reason,
|
|
creditBalance: output.response.creditBalance,
|
|
requiredCredits: output.response.requiredCredits,
|
|
shortfallCredits: output.response.shortfallCredits,
|
|
...(output.response.paymentGuidance
|
|
? { paymentGuidance: output.response.paymentGuidance }
|
|
: {}),
|
|
}
|
|
: null,
|
|
creditBalance: output.response.creditBalance,
|
|
requiredCredits: output.response.requiredCredits,
|
|
shortfallCredits: output.response.shortfallCredits,
|
|
paymentGuidance: output.response.paymentGuidance,
|
|
};
|
|
},
|
|
);
|
|
|
|
const requestUnlockPaymentFromErrorAction = sendMachineSetup.assign(
|
|
({ context }) => {
|
|
const request = context.unlockingMessage;
|
|
return {
|
|
unlockingMessage: null,
|
|
unlockMessageError: "unlock_failed",
|
|
unlockPaywallRequest: request
|
|
? {
|
|
...request,
|
|
...(context.promotion?.message.displayId ===
|
|
request.displayMessageId
|
|
? { promotion: context.promotion.session }
|
|
: {}),
|
|
reason: "unlock_failed",
|
|
creditBalance: context.creditBalance,
|
|
requiredCredits: context.requiredCredits,
|
|
shortfallCredits: context.shortfallCredits,
|
|
}
|
|
: null,
|
|
};
|
|
},
|
|
);
|
|
|
|
const clearUnlockPaywallRequestAction = sendMachineSetup.assign(() => ({
|
|
unlockPaywallRequest: null,
|
|
}));
|
|
|
|
const handleCharacterMismatchAction = sendMachineSetup.assign(() => ({
|
|
unlockingMessage: null,
|
|
unlockMessageError: "character_mismatch",
|
|
unlockPaywallRequest: null,
|
|
characterErrorCode: "CHARACTER_MISMATCH" as const,
|
|
}));
|
|
|
|
export const unlockMachineSetup = sendMachineSetup.extend({
|
|
actions: {
|
|
markPaymentUnlockPending: markPaymentUnlockPendingAction,
|
|
showUnlockHistoryPrompt: showUnlockHistoryPromptAction,
|
|
dismissUnlockHistoryPrompt: dismissUnlockHistoryPromptAction,
|
|
markUnlockHistoryStarted: markUnlockHistoryStartedAction,
|
|
markUnlockHistoryFailed: markUnlockHistoryFailedAction,
|
|
markUnlockMessageStarted: markUnlockMessageStartedAction,
|
|
applyUnlockMessageSucceeded: applyUnlockMessageSucceededAction,
|
|
requestUnlockPaymentFromOutput: requestUnlockPaymentFromOutputAction,
|
|
requestUnlockPaymentFromError: requestUnlockPaymentFromErrorAction,
|
|
clearUnlockPaywallRequest: clearUnlockPaywallRequestAction,
|
|
handleCharacterMismatch: handleCharacterMismatchAction,
|
|
},
|
|
});
|
|
|
|
const unlockHistoryDoneActionSetup =
|
|
createChatActorActionSetup<DoneActorEvent<UnlockHistoryOutput>>();
|
|
|
|
const applyUnlockHistoryOutputAction =
|
|
unlockHistoryDoneActionSetup.assign(({ event }) => {
|
|
const lockedHistoryCount = countLockedHistoryMessages(
|
|
event.output.messages,
|
|
);
|
|
const insufficientBalance =
|
|
!event.output.unlocked &&
|
|
event.output.reason === "insufficient_balance";
|
|
return {
|
|
messages: event.output.messages,
|
|
historyLoaded: true,
|
|
paymentUnlockPending: false,
|
|
unlockHistoryPromptVisible: insufficientBalance,
|
|
lockedHistoryCount,
|
|
unlockHistoryError: insufficientBalance
|
|
? `Insufficient credits. You still need ${event.output.shortfallCredits} credits.`
|
|
: null,
|
|
unlockHistoryPaymentGuidance: insufficientBalance
|
|
? event.output.paymentGuidance ?? null
|
|
: null,
|
|
};
|
|
});
|
|
|
|
export const unlockingHistoryState = unlockMachineSetup.createStateConfig({
|
|
invoke: {
|
|
id: "unlockHistory",
|
|
src: "unlockHistory",
|
|
input: ({ context }) => ({
|
|
characterId: context.characterId,
|
|
emptyChatGreeting: context.emptyChatGreeting,
|
|
}),
|
|
onDone: {
|
|
target: "ready",
|
|
actions: applyUnlockHistoryOutputAction,
|
|
},
|
|
onError: {
|
|
target: "ready",
|
|
actions: "markUnlockHistoryFailed",
|
|
},
|
|
},
|
|
});
|
|
|
|
export const unlockingMessageState = unlockMachineSetup.createStateConfig({
|
|
invoke: {
|
|
id: "unlockMessage",
|
|
src: "unlockMessage",
|
|
input: ({ context }) => ({
|
|
characterId: context.characterId,
|
|
...(context.unlockingMessage ?? {
|
|
displayMessageId: "",
|
|
kind: "private" as const,
|
|
}),
|
|
}),
|
|
onDone: [
|
|
{
|
|
guard: ({ event }) => event.output.response.unlocked,
|
|
target: "ready",
|
|
actions: "applyUnlockMessageSucceeded",
|
|
},
|
|
{
|
|
target: "ready",
|
|
actions: "requestUnlockPaymentFromOutput",
|
|
},
|
|
],
|
|
onError: [
|
|
{
|
|
guard: ({ event }) =>
|
|
getCharacterErrorCode(event.error) === "CHARACTER_MISMATCH",
|
|
target: "ready",
|
|
actions: "handleCharacterMismatch",
|
|
},
|
|
{
|
|
target: "ready",
|
|
actions: "requestUnlockPaymentFromError",
|
|
},
|
|
],
|
|
},
|
|
});
|