Add reliable widget message outbox
This commit is contained in:
@@ -153,6 +153,7 @@ function initDb() {
|
||||
conversation_id INTEGER NOT NULL REFERENCES conversations(id),
|
||||
sender_type TEXT NOT NULL,
|
||||
sender_name TEXT,
|
||||
client_message_id TEXT,
|
||||
body TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
@@ -211,6 +212,8 @@ function initDb() {
|
||||
db.prepare("UPDATE visitor_sessions SET last_activity_at = last_seen_at WHERE last_activity_at IS NULL").run();
|
||||
}
|
||||
ensureColumn("conversations", "visitor_seen_at", "TEXT");
|
||||
ensureColumn("messages", "client_message_id", "TEXT");
|
||||
db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_client_id ON messages(conversation_id, client_message_id) WHERE client_message_id IS NOT NULL");
|
||||
}
|
||||
|
||||
function ensureColumn(table, column, definition) {
|
||||
@@ -523,6 +526,9 @@ async function createConversation(req, res) {
|
||||
if (!site) return json(res, 404, { error: "site_not_found" });
|
||||
|
||||
const visitorToken = body.visitorToken || id("vis");
|
||||
const clientMessageId = normalizeClientMessageId(body.clientMessageId);
|
||||
const existingConversation = findConversationByClientMessage(site.id, visitorToken, clientMessageId);
|
||||
if (existingConversation) return json(res, 200, conversationDetails(existingConversation));
|
||||
const publicId = id("cnv");
|
||||
const info = body.visitorInfo || {};
|
||||
const ip = clientIp(req);
|
||||
@@ -552,7 +558,7 @@ async function createConversation(req, res) {
|
||||
nullish(countryFromHeaders(req))
|
||||
).lastInsertRowid;
|
||||
|
||||
const messageId = insertMessage(conversationId, "visitor", info.name || "Visitor", String(body.message || "").trim());
|
||||
const messageId = insertMessage(conversationId, "visitor", info.name || "Visitor", String(body.message || "").trim(), clientMessageId);
|
||||
saveAttachments(conversationId, messageId, body.attachments || []);
|
||||
return conversationId;
|
||||
});
|
||||
@@ -570,7 +576,12 @@ async function createVisitorMessage(req, res, url) {
|
||||
if (!attachmentsAreAllowed(body.attachments || [])) return json(res, 400, { error: "only_images_allowed" });
|
||||
const conversation = findConversation(publicIdFrom(url));
|
||||
if (!conversation) return json(res, 404, { error: "conversation_not_found" });
|
||||
const messageId = insertMessage(conversation.id, "visitor", conversation.visitor_name || "Visitor", String(body.message || "").trim());
|
||||
const clientMessageId = normalizeClientMessageId(body.clientMessageId);
|
||||
if (clientMessageId && messageExists(conversation.id, clientMessageId)) {
|
||||
updateVisitorContext(conversation.id, body);
|
||||
return json(res, 200, conversationDetails(findConversation(conversation.public_id)));
|
||||
}
|
||||
const messageId = insertMessage(conversation.id, "visitor", conversation.visitor_name || "Visitor", String(body.message || "").trim(), clientMessageId);
|
||||
saveAttachments(conversation.id, messageId, body.attachments || []);
|
||||
updateVisitorContext(conversation.id, body);
|
||||
markConversationUnread(conversation.id);
|
||||
@@ -667,11 +678,33 @@ function publish(key, event) {
|
||||
}
|
||||
}
|
||||
|
||||
function insertMessage(conversationId, senderType, senderName, body) {
|
||||
function insertMessage(conversationId, senderType, senderName, body, clientMessageId = null) {
|
||||
return db.prepare(`
|
||||
INSERT INTO messages (conversation_id, sender_type, sender_name, body)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`).run(conversationId, senderType, senderName, body).lastInsertRowid;
|
||||
INSERT INTO messages (conversation_id, sender_type, sender_name, client_message_id, body)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`).run(conversationId, senderType, senderName, clientMessageId, body).lastInsertRowid;
|
||||
}
|
||||
|
||||
function messageExists(conversationId, clientMessageId) {
|
||||
return Boolean(db.prepare("SELECT 1 FROM messages WHERE conversation_id = ? AND client_message_id = ?").get(conversationId, clientMessageId));
|
||||
}
|
||||
|
||||
function findConversationByClientMessage(siteId, visitorToken, clientMessageId) {
|
||||
if (!clientMessageId) return null;
|
||||
const row = db.prepare(`
|
||||
SELECT c.public_id
|
||||
FROM messages m
|
||||
JOIN conversations c ON c.id = m.conversation_id
|
||||
WHERE c.site_id = ? AND c.visitor_token = ? AND m.client_message_id = ?
|
||||
ORDER BY m.id DESC
|
||||
LIMIT 1
|
||||
`).get(siteId, visitorToken, clientMessageId);
|
||||
return row ? findConversation(row.public_id) : null;
|
||||
}
|
||||
|
||||
function normalizeClientMessageId(value) {
|
||||
const text = String(value || "").trim();
|
||||
return /^[a-zA-Z0-9_-]{8,80}$/.test(text) ? text : null;
|
||||
}
|
||||
|
||||
function saveAttachments(conversationId, messageId, attachments) {
|
||||
|
||||
Reference in New Issue
Block a user