desktop/chat: fallback de polling se WS falhar
This commit is contained in:
parent
fe361ff4d8
commit
e08dc21003
1 changed files with 136 additions and 6 deletions
|
|
@ -34,6 +34,11 @@ type MachineUpdatePayload = {
|
|||
totalUnread: number
|
||||
}
|
||||
|
||||
type MessagesPayload = {
|
||||
messages: ChatMessage[]
|
||||
hasSession: boolean
|
||||
}
|
||||
|
||||
const FN_CHECK_UPDATES = "liveChat.checkMachineUpdates" as const
|
||||
const FN_LIST_MESSAGES = "liveChat.listMachineMessages" as const
|
||||
const FN_POST_MESSAGE = "liveChat.postMachineMessage" as const
|
||||
|
|
@ -92,13 +97,71 @@ export async function subscribeMachineUpdates(
|
|||
onError?: (error: Error) => void
|
||||
): Promise<() => void> {
|
||||
const { client, token } = await ensureClient()
|
||||
let stopped = false
|
||||
let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
const stopPoll = () => {
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer)
|
||||
pollTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
const startPoll = async () => {
|
||||
stopPoll()
|
||||
try {
|
||||
const { apiBaseUrl } = await getMachineStoreConfig()
|
||||
const poll = async () => {
|
||||
try {
|
||||
const res = await fetch(`${apiBaseUrl}/api/machines/chat/poll`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ machineToken: token }),
|
||||
})
|
||||
if (!res.ok) throw new Error(`poll failed: ${res.status}`)
|
||||
const data = (await res.json()) as MachineUpdatePayload
|
||||
if (!stopped) callback(data)
|
||||
} catch (err) {
|
||||
if (!stopped) onError?.(err as Error)
|
||||
}
|
||||
}
|
||||
await poll()
|
||||
pollTimer = setInterval(poll, 4000)
|
||||
} catch (err) {
|
||||
onError?.(err as Error)
|
||||
}
|
||||
}
|
||||
|
||||
const sub = client.onUpdate(
|
||||
FN_CHECK_UPDATES as unknown as FunctionReference<"query">,
|
||||
{ machineToken: token },
|
||||
(value) => callback(value),
|
||||
onError
|
||||
(value) => {
|
||||
stopPoll()
|
||||
callback(value)
|
||||
},
|
||||
(err) => {
|
||||
onError?.(err)
|
||||
startPoll().catch(() => {
|
||||
// erro já reportado via onError
|
||||
})
|
||||
}
|
||||
)
|
||||
return () => sub.unsubscribe()
|
||||
|
||||
// fallback caso a inscrição falhe imediatamente
|
||||
setTimeout(() => {
|
||||
if (stopped) return
|
||||
// Se não houver mensagens recebidas pelo WS em 3s, inicia polling
|
||||
// (o callback do WS encerra o polling caso volte a funcionar)
|
||||
startPoll().catch(() => {
|
||||
// erro já reportado via onError
|
||||
})
|
||||
}, 3000)
|
||||
|
||||
return () => {
|
||||
stopped = true
|
||||
stopPoll()
|
||||
sub.unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
export async function subscribeMachineMessages(
|
||||
|
|
@ -107,16 +170,83 @@ export async function subscribeMachineMessages(
|
|||
onError?: (error: Error) => void
|
||||
): Promise<() => void> {
|
||||
const { client, token } = await ensureClient()
|
||||
let stopped = false
|
||||
let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
let lastSince = 0
|
||||
|
||||
const stopPoll = () => {
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer)
|
||||
pollTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
const startPoll = async () => {
|
||||
stopPoll()
|
||||
try {
|
||||
const { apiBaseUrl } = await getMachineStoreConfig()
|
||||
const poll = async () => {
|
||||
try {
|
||||
const res = await fetch(`${apiBaseUrl}/api/machines/chat/messages`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
action: "list",
|
||||
machineToken: token,
|
||||
ticketId,
|
||||
since: lastSince || undefined,
|
||||
}),
|
||||
})
|
||||
if (!res.ok) throw new Error(`messages poll failed: ${res.status}`)
|
||||
const data = (await res.json()) as MessagesPayload
|
||||
if (!stopped) {
|
||||
callback(data)
|
||||
const newest = data.messages.reduce((max, msg) => Math.max(max, msg.createdAt), lastSince)
|
||||
lastSince = newest
|
||||
}
|
||||
} catch (err) {
|
||||
if (!stopped) onError?.(err as Error)
|
||||
}
|
||||
}
|
||||
await poll()
|
||||
pollTimer = setInterval(poll, 4000)
|
||||
} catch (err) {
|
||||
onError?.(err as Error)
|
||||
}
|
||||
}
|
||||
|
||||
const sub = client.onUpdate(
|
||||
FN_LIST_MESSAGES as unknown as FunctionReference<"query">,
|
||||
{
|
||||
machineToken: token,
|
||||
ticketId,
|
||||
},
|
||||
(value) => callback(value),
|
||||
onError
|
||||
(value) => {
|
||||
stopPoll()
|
||||
callback(value)
|
||||
const newest = value.messages.reduce((max, msg) => Math.max(max, msg.createdAt), lastSince)
|
||||
lastSince = newest
|
||||
},
|
||||
(err) => {
|
||||
onError?.(err)
|
||||
startPoll().catch(() => {
|
||||
// erro já reportado via onError
|
||||
})
|
||||
}
|
||||
)
|
||||
return () => sub.unsubscribe()
|
||||
|
||||
setTimeout(() => {
|
||||
if (stopped) return
|
||||
startPoll().catch(() => {
|
||||
// erro já reportado via onError
|
||||
})
|
||||
}, 3000)
|
||||
|
||||
return () => {
|
||||
stopped = true
|
||||
stopPoll()
|
||||
sub.unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendMachineMessage(input: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue