- chatStore.js: per-shop JSON storage in data/chats/, message history, unread tracking, markRead on admin open - adminPanel.js: new Chat tab (WhatsApp-style left sidebar + right panel), 3s polling, admin reply, unread badge on tab - server.js: public POST /chat/:shop and GET /chat/:shop for widget, GET /chat/widget.js serves embeddable script - Widget: floating chat button, popup window, customer sends messages, polls for admin replies, visitor ID persisted in localStorage Usage: <script src="https://backend.data4autos.com/chat/widget.js?shop=SHOP"></script> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
70 lines
2.1 KiB
JavaScript
70 lines
2.1 KiB
JavaScript
// chatStore.js — per-shop customer chat message storage
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { v4: uuid } = require('uuid');
|
|
|
|
const chatsDir = path.resolve(__dirname, 'data', 'chats');
|
|
if (!fs.existsSync(chatsDir)) fs.mkdirSync(chatsDir, { recursive: true });
|
|
|
|
function safeKey(shop) {
|
|
return shop.toLowerCase().trim().replace(/[^a-z0-9.-]/g, '_');
|
|
}
|
|
|
|
function chatFile(shop) {
|
|
return path.join(chatsDir, safeKey(shop) + '.json');
|
|
}
|
|
|
|
function readChat(shop) {
|
|
const f = chatFile(shop);
|
|
if (!fs.existsSync(f)) return { shop, messages: [] };
|
|
try { return JSON.parse(fs.readFileSync(f, 'utf8')); }
|
|
catch { return { shop, messages: [] }; }
|
|
}
|
|
|
|
function saveChat(shop, data) {
|
|
fs.writeFileSync(chatFile(shop), JSON.stringify(data, null, 2), 'utf8');
|
|
}
|
|
|
|
function addMessage(shop, from, text, visitorId = null) {
|
|
const chat = readChat(shop);
|
|
const msg = {
|
|
id: uuid(),
|
|
from, // 'customer' | 'admin'
|
|
text: String(text).trim().slice(0, 2000),
|
|
timestamp: new Date().toISOString(),
|
|
read: from === 'admin',
|
|
visitorId: visitorId || null,
|
|
};
|
|
chat.messages.push(msg);
|
|
saveChat(shop, chat);
|
|
return msg;
|
|
}
|
|
|
|
function markRead(shop) {
|
|
const chat = readChat(shop);
|
|
let changed = false;
|
|
chat.messages.forEach(m => {
|
|
if (m.from === 'customer' && !m.read) { m.read = true; changed = true; }
|
|
});
|
|
if (changed) saveChat(shop, chat);
|
|
}
|
|
|
|
function listChats() {
|
|
if (!fs.existsSync(chatsDir)) return [];
|
|
return fs.readdirSync(chatsDir)
|
|
.filter(f => f.endsWith('.json'))
|
|
.map(f => {
|
|
try {
|
|
const data = JSON.parse(fs.readFileSync(path.join(chatsDir, f), 'utf8'));
|
|
const msgs = data.messages || [];
|
|
const last = msgs[msgs.length - 1] || null;
|
|
const unread = msgs.filter(m => m.from === 'customer' && !m.read).length;
|
|
return { shop: data.shop, lastMessage: last, unread, messageCount: msgs.length };
|
|
} catch { return null; }
|
|
})
|
|
.filter(Boolean)
|
|
.sort((a, b) => (b.lastMessage?.timestamp || '').localeCompare(a.lastMessage?.timestamp || ''));
|
|
}
|
|
|
|
module.exports = { addMessage, readChat, markRead, listChats };
|