Add reliable widget message outbox
This commit is contained in:
+94
-7
@@ -4,6 +4,7 @@
|
||||
const siteKey = boot.siteKey || "9b-plus";
|
||||
const lang = (boot.lang || document.documentElement.lang || navigator.language || "cs").slice(0, 2).toLowerCase();
|
||||
const storageKey = `maalflows:${siteKey}`;
|
||||
const pendingStorageKey = `${storageKey}:pending`;
|
||||
const session = loadSession();
|
||||
const allowedImageTypes = new Set(["image/jpeg", "image/png", "image/webp", "image/gif", "image/avif"]);
|
||||
const maxAttachmentBytes = 3_000_000;
|
||||
@@ -148,14 +149,13 @@
|
||||
event.preventDefault();
|
||||
const body = await payloadFromForm(form, selectedFiles);
|
||||
if (!body.message.trim() && !body.attachments.length) return;
|
||||
const pendingMessage = queuePendingMessage(body);
|
||||
const submitButton = root.querySelector(".mf-send");
|
||||
submitButton.disabled = true;
|
||||
|
||||
const url = session.conversationId
|
||||
? `${apiBase}/api/widget/conversations/${session.conversationId}/messages`
|
||||
: `${apiBase}/api/widget/conversations`;
|
||||
try {
|
||||
const data = await postJson(url, withVisitorContext(body));
|
||||
const data = await sendPendingMessage(pendingMessage);
|
||||
removePendingMessage(pendingMessage.clientMessageId);
|
||||
session.conversationId = data.conversation.id;
|
||||
saveSession();
|
||||
form.message.value = "";
|
||||
@@ -164,6 +164,7 @@
|
||||
handleConversationUpdate(data, messages, panel, launcher, settings, messageEffectState, { notify: false });
|
||||
connectEvents(messages, typing, panel, launcher, settings, messageEffectState);
|
||||
startConversationPolling(messages, panel, launcher, settings, messageEffectState);
|
||||
flushPendingMessages(messages, typing, panel, launcher, settings, messageEffectState);
|
||||
} catch {
|
||||
showFormError(root, "Zpravu se nepodarilo odeslat. Zkuste to prosim znovu.");
|
||||
} finally {
|
||||
@@ -179,8 +180,10 @@
|
||||
if (!document.hidden) {
|
||||
noteActivity();
|
||||
pingVisitor();
|
||||
flushPendingMessages(messages, typing, panel, launcher, settings, messageEffectState);
|
||||
}
|
||||
});
|
||||
window.addEventListener("online", () => flushPendingMessages(messages, typing, panel, launcher, settings, messageEffectState));
|
||||
if (session.conversationId) {
|
||||
loadExistingConversation(messages, panel)
|
||||
.then((data) => {
|
||||
@@ -189,7 +192,10 @@
|
||||
.finally(() => {
|
||||
connectEvents(messages, typing, panel, launcher, settings, messageEffectState);
|
||||
startConversationPolling(messages, panel, launcher, settings, messageEffectState);
|
||||
flushPendingMessages(messages, typing, panel, launcher, settings, messageEffectState);
|
||||
});
|
||||
} else {
|
||||
flushPendingMessages(messages, typing, panel, launcher, settings, messageEffectState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,6 +231,51 @@
|
||||
};
|
||||
}
|
||||
|
||||
async function flushPendingMessages(messages, typing, panel, launcher, settings, messageEffectState) {
|
||||
if (session.flushingPending) return;
|
||||
const pendingMessages = loadPendingMessages();
|
||||
if (!pendingMessages.length) return;
|
||||
session.flushingPending = true;
|
||||
try {
|
||||
for (const pendingMessage of pendingMessages) {
|
||||
try {
|
||||
const data = await sendPendingMessage(pendingMessage);
|
||||
removePendingMessage(pendingMessage.clientMessageId);
|
||||
session.conversationId = data.conversation.id;
|
||||
saveSession();
|
||||
handleConversationUpdate(data, messages, panel, launcher, settings, messageEffectState, { notify: false });
|
||||
connectEvents(messages, typing, panel, launcher, settings, messageEffectState);
|
||||
startConversationPolling(messages, panel, launcher, settings, messageEffectState);
|
||||
} catch {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
session.flushingPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function sendPendingMessage(pendingMessage) {
|
||||
const { conversationId, ...body } = pendingMessage;
|
||||
return postWidgetMessage(body, conversationId || session.conversationId);
|
||||
}
|
||||
|
||||
async function postWidgetMessage(body, conversationId) {
|
||||
const url = conversationId
|
||||
? `${apiBase}/api/widget/conversations/${conversationId}/messages`
|
||||
: `${apiBase}/api/widget/conversations`;
|
||||
try {
|
||||
return await postJson(url, withVisitorContext(body));
|
||||
} catch (error) {
|
||||
if (conversationId && (error.status === 403 || error.status === 404)) {
|
||||
session.conversationId = null;
|
||||
saveSession();
|
||||
return postJson(`${apiBase}/api/widget/conversations`, withVisitorContext(body));
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function createHeaderActions(root, closeButton) {
|
||||
const actions = document.createElement("div");
|
||||
actions.className = "mf-header-actions";
|
||||
@@ -605,13 +656,49 @@
|
||||
}));
|
||||
}
|
||||
|
||||
function queuePendingMessage(body) {
|
||||
const pendingMessage = {
|
||||
...body,
|
||||
conversationId: session.conversationId,
|
||||
clientMessageId: body.clientMessageId || randomId("msg"),
|
||||
queuedAt: new Date().toISOString()
|
||||
};
|
||||
const pendingMessages = loadPendingMessages().filter((item) => item.clientMessageId !== pendingMessage.clientMessageId);
|
||||
pendingMessages.push(pendingMessage);
|
||||
savePendingMessages(pendingMessages.slice(-10));
|
||||
return pendingMessage;
|
||||
}
|
||||
|
||||
function removePendingMessage(clientMessageId) {
|
||||
savePendingMessages(loadPendingMessages().filter((item) => item.clientMessageId !== clientMessageId));
|
||||
}
|
||||
|
||||
function loadPendingMessages() {
|
||||
try {
|
||||
const items = JSON.parse(localStorage.getItem(pendingStorageKey) || "[]");
|
||||
return Array.isArray(items) ? items.filter((item) => item?.clientMessageId) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function savePendingMessages(messages) {
|
||||
try {
|
||||
localStorage.setItem(pendingStorageKey, JSON.stringify(messages));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function postJson(url, payload) {
|
||||
return fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload)
|
||||
}).then((res) => {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
if (!res.ok) {
|
||||
const error = new Error(`HTTP ${res.status}`);
|
||||
error.status = res.status;
|
||||
throw error;
|
||||
}
|
||||
return res.json();
|
||||
});
|
||||
}
|
||||
@@ -649,7 +736,7 @@
|
||||
})[char]);
|
||||
}
|
||||
|
||||
function randomId() {
|
||||
return `vis_${Math.random().toString(16).slice(2)}${Date.now().toString(16)}`;
|
||||
function randomId(prefix = "vis") {
|
||||
return `${prefix}_${Math.random().toString(16).slice(2)}${Date.now().toString(16)}`;
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user