Add visitor read receipts
This commit is contained in:
@@ -68,6 +68,7 @@ async function route(req, res) {
|
||||
if (url.pathname === "/api/widget/conversations" && req.method === "POST") return createConversation(req, res);
|
||||
if (url.pathname.match(/^\/api\/widget\/conversations\/[^/]+$/) && req.method === "GET") return getVisitorConversation(res, url);
|
||||
if (url.pathname.match(/^\/api\/widget\/conversations\/[^/]+\/messages$/) && req.method === "POST") return createVisitorMessage(req, res, url);
|
||||
if (url.pathname.match(/^\/api\/widget\/conversations\/[^/]+\/seen$/) && req.method === "POST") return markVisitorSeen(req, res, url);
|
||||
if (url.pathname.match(/^\/api\/widget\/conversations\/[^/]+\/events$/) && req.method === "GET") return eventStream(req, res, url, "visitor");
|
||||
if (url.pathname.match(/^\/api\/widget\/conversations\/[^/]+\/typing$/) && req.method === "POST") return visitorTyping(req, res, url);
|
||||
|
||||
@@ -137,6 +138,7 @@ function initDb() {
|
||||
ip TEXT,
|
||||
country_code TEXT,
|
||||
vpn_risk INTEGER NOT NULL DEFAULT 0,
|
||||
visitor_seen_at TEXT,
|
||||
last_message_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
@@ -192,6 +194,7 @@ function initDb() {
|
||||
if (ensureColumn("visitor_sessions", "last_activity_at", "TEXT")) {
|
||||
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");
|
||||
}
|
||||
|
||||
function ensureColumn(table, column, definition) {
|
||||
@@ -515,6 +518,17 @@ async function createVisitorMessage(req, res, url) {
|
||||
return json(res, 201, conversationDetails(updated));
|
||||
}
|
||||
|
||||
async function markVisitorSeen(req, res, url) {
|
||||
const body = await readJson(req).catch(() => ({}));
|
||||
const conversation = findConversation(publicIdFrom(url));
|
||||
if (!conversation) return json(res, 404, { error: "conversation_not_found" });
|
||||
if (conversation.visitor_token !== body.visitorToken) return json(res, 403, { error: "forbidden" });
|
||||
db.prepare("UPDATE conversations SET visitor_seen_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ?").run(conversation.id);
|
||||
const updated = findConversation(conversation.public_id);
|
||||
publishConversation(updated, { type: "conversation:seen", conversation: formatConversationSummary(updated) });
|
||||
return json(res, 200, { ok: true, conversation: formatConversationSummary(updated) });
|
||||
}
|
||||
|
||||
async function createAdminMessage(req, res, url) {
|
||||
const body = await readJson(req, 8_000_000);
|
||||
if ((!body.message || !String(body.message).trim()) && !(body.attachments || []).length) return json(res, 400, { error: "message_or_image_required" });
|
||||
@@ -734,6 +748,7 @@ function formatConversationSummary(row) {
|
||||
visitorLabel: visitorLabel(row.visitor_token),
|
||||
visitorOnline: Boolean(row.visitor_online),
|
||||
visitorPresence: row.visitor_presence || (row.visitor_online ? "active" : "offline"),
|
||||
visitorSeenAt: row.visitor_seen_at,
|
||||
visitor: {
|
||||
name: row.visitor_name,
|
||||
email: row.visitor_email,
|
||||
|
||||
Reference in New Issue
Block a user