import { MachineConfig, send, assign } from "xstate"; import { Chat, ChatMessage, User } from "../types"; // import differenceInMinutes from 'date-fns/differenceInMinutes' interface ChatSchema { states: { init: {}; getChatData: {}; getSystemToken: {}; closed: {}; failChat: {}; failToken: {}; start: { states: { websocket: { messageReceived: {}; customerMessage: {}; customerWaiting: {}; staffMessage: {}; staffWaiting: {}; }; ui: { start: {}; idle: {}; sending: {}; deleting: {}; msgPopupClicked: {}; }; }; }; }; } type MSG_POPUP_CLICK_EVT = { type: "MESSAGE_POPUP_CLICK"; data: { message: ChatMessage; buttonId: string; event: ChatEvent }; }; export type ChatEvent = | { type: "START"; data: Chat } | { type: "MESSAGE"; data: ChatMessage } | { type: "ON_MESSAGE"; data: ChatMessage } | { type: "SEND_MESSAGE"; data: ChatMessage } | { type: "DELETE_MESSAGE"; data: { eventId: string } } | { type: "CLOSE"; data: undefined } | { type: "MESSAGE_CLICK"; data: ChatMessage } | MSG_POPUP_CLICK_EVT | { type: "PLUGIN_CLICK"; pluginId: string; }; export interface ChatContext { me: User; msgStream: any; systemToken?: string; apClient: any; chat?: any; webSocketActor: any; } const isStaff = (_: ChatContext, event: ChatEvent) => { if (event.type === "ON_MESSAGE") { const msg = event.data; return msg.sender.roles?.find((role) => role.indexOf("STAFF") > 0) != null; } return false; }; // @ts-ignore const DefaultMachine: MachineConfig = { id: "chat", initial: "init", states: { init: { on: { START: "getChatData", }, }, getChatData: { invoke: [ { src: "getChatData", onDone: { target: "getSystemToken", actions: [assign({ chat: (_, evt) => evt.data })], }, onError: "failChat", }, ], }, getSystemToken: { invoke: [ { src: "getSystemToken", onDone: { target: "start", actions: [assign({ systemToken: (_, evt) => evt.data })], }, onError: "failToken", }, ], }, start: { id: "start", type: "parallel", states: { websocket: { //@ts-ignore initial: "start", states: { start: { entry: "spawnWebSocket", }, messageReceived: { entry: [ send((_, event) => ({ type: "ON_MESSAGE", // @ts-ignore data: event.data, })), "setReadIndex", ], on: { ON_MESSAGE: [ { target: "staffMessage", cond: isStaff }, { target: "customerMessage" }, ], }, }, customerMessage: { on: { MESSAGE: "messageReceived", }, after: { 5000: "customerWaiting" }, }, customerWaiting: { on: { MESSAGE: "messageReceived", }, }, staffMessage: { on: { MESSAGE: "messageReceived", }, after: { 5000: "staffWaiting" }, }, staffWaiting: { on: { MESSAGE: "messageReceived", }, }, }, }, ui: { // @ts-ignore initial: "start", on: { CLOSE: "#chat.closed", }, states: { start: { after: { 10: "idle" }, }, disallowSend: { after: { 10: "idle" }, }, sendingMessage: { invoke: { src: "sendMessage", onDone: "idle", onError: "idle" }, }, deleting: { invoke: { src: "deleteMessage", onDone: [ { cond: (ctx: any, evt: any) => { return !evt.data; }, target: "error", actions: assign({ error: "删除失败,请重试" }), }, { target: "idle", }, ], onError: { target: "error", actions: assign({ error: "删除失败,请重试" }), }, }, }, msgPopupClicked: { after: { 3000: "idle" }, }, idle: { on: { CLOSE: "#chat.closed", SEND_MESSAGE: [ { target: "sendingMessage", cond: "canSend" }, { target: "disallowSend" }, ], DELETE_MESSAGE: "deleting", MESSAGE_POPUP_CLICK: [ { target: "deleting", cond: (_: ChatContext, evt: MSG_POPUP_CLICK_EVT) => evt.data.buttonId === "breezechat.delete", }, { target: "sendingMessage", cond: (_: ChatContext, evt: MSG_POPUP_CLICK_EVT) => evt.data.buttonId === "breezechat.resend", }, { target: "msgPopupClicked" }, ], PLUGIN_CLICK: "idle", }, }, error: { entry: () => console.log("entry error state"), after: { 3000: { actions: assign({ error: null }), target: "idle", }, }, }, }, }, }, }, closed: { type: "final", }, failChat: { after: { 5000: { target: "getChatData", }, }, }, failToken: { after: { 5000: { target: "getSystemToken", }, }, }, }, }; export default DefaultMachine;