refactor: remove legacy chat service, migrate chat-widget to Directus conversations
This commit is contained in:
@@ -1,217 +0,0 @@
|
||||
/**
|
||||
* Chat Service - Handles message storage and retrieval
|
||||
* Uses LocalStorage as mock backend until Directus is ready
|
||||
*/
|
||||
|
||||
import { cryptoService } from './crypto.js'
|
||||
|
||||
const CHATS_STORAGE_KEY = 'dgray_chats'
|
||||
const MESSAGES_STORAGE_KEY = 'dgray_messages'
|
||||
|
||||
class ChatService {
|
||||
constructor() {
|
||||
this.subscribers = new Set()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a chat between current user and another user
|
||||
* @param {string} recipientId - The other user's ID
|
||||
* @param {string} recipientPublicKey - The other user's public key
|
||||
* @param {string} listingId - The listing this chat is about
|
||||
* @returns {object} - Chat object
|
||||
*/
|
||||
getOrCreateChat(recipientId, recipientPublicKey, listingId) {
|
||||
const chats = this.getAllChats()
|
||||
const myPublicKey = cryptoService.getPublicKey()
|
||||
|
||||
// Find existing chat for this listing + recipient
|
||||
let chat = chats.find(c =>
|
||||
c.listingId === listingId &&
|
||||
c.recipientId === recipientId
|
||||
)
|
||||
|
||||
if (!chat) {
|
||||
chat = {
|
||||
id: this.generateId(),
|
||||
listingId,
|
||||
recipientId,
|
||||
recipientPublicKey,
|
||||
myPublicKey,
|
||||
createdAt: new Date().toISOString(),
|
||||
lastMessageAt: null
|
||||
}
|
||||
chats.push(chat)
|
||||
this.saveChats(chats)
|
||||
}
|
||||
|
||||
return chat
|
||||
}
|
||||
|
||||
getAllChats() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(CHATS_STORAGE_KEY) || '[]')
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
saveChats(chats) {
|
||||
localStorage.setItem(CHATS_STORAGE_KEY, JSON.stringify(chats))
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an encrypted message
|
||||
* @param {string} chatId - Chat ID
|
||||
* @param {string} recipientPublicKey - Recipient's public key
|
||||
* @param {string} plainText - Message content
|
||||
* @returns {object} - The saved message
|
||||
*/
|
||||
async sendMessage(chatId, recipientPublicKey, plainText) {
|
||||
await cryptoService.ready
|
||||
|
||||
const { nonce, ciphertext } = cryptoService.encrypt(plainText, recipientPublicKey)
|
||||
|
||||
const message = {
|
||||
id: this.generateId(),
|
||||
chatId,
|
||||
senderPublicKey: cryptoService.getPublicKey(),
|
||||
nonce,
|
||||
ciphertext,
|
||||
timestamp: new Date().toISOString(),
|
||||
// Store plain text for own messages (we can't decrypt our own box messages)
|
||||
_plainText: plainText
|
||||
}
|
||||
|
||||
const messages = this.getAllMessages()
|
||||
messages.push(message)
|
||||
this.saveMessages(messages)
|
||||
|
||||
// Update chat's lastMessageAt
|
||||
const chats = this.getAllChats()
|
||||
const chat = chats.find(c => c.id === chatId)
|
||||
if (chat) {
|
||||
chat.lastMessageAt = message.timestamp
|
||||
this.saveChats(chats)
|
||||
}
|
||||
|
||||
this.notifySubscribers()
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all messages for a chat, decrypted
|
||||
* @param {string} chatId - Chat ID
|
||||
* @param {string} otherPublicKey - The other party's public key
|
||||
* @returns {Array} - Decrypted messages
|
||||
*/
|
||||
async getMessages(chatId, otherPublicKey) {
|
||||
await cryptoService.ready
|
||||
|
||||
const messages = this.getAllMessages().filter(m => m.chatId === chatId)
|
||||
const myPublicKey = cryptoService.getPublicKey()
|
||||
|
||||
return messages.map(msg => {
|
||||
const isOwn = msg.senderPublicKey === myPublicKey
|
||||
|
||||
let text
|
||||
if (isOwn) {
|
||||
// Use stored plain text for own messages
|
||||
text = msg._plainText || '[Encrypted]'
|
||||
} else {
|
||||
// Decrypt messages from others
|
||||
text = cryptoService.decrypt(msg.ciphertext, msg.nonce, msg.senderPublicKey)
|
||||
if (!text) text = '[Decryption failed]'
|
||||
}
|
||||
|
||||
return {
|
||||
id: msg.id,
|
||||
text,
|
||||
isOwn,
|
||||
timestamp: msg.timestamp
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getAllMessages() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(MESSAGES_STORAGE_KEY) || '[]')
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
saveMessages(messages) {
|
||||
localStorage.setItem(MESSAGES_STORAGE_KEY, JSON.stringify(messages))
|
||||
}
|
||||
|
||||
generateId() {
|
||||
return 'msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
|
||||
}
|
||||
|
||||
subscribe(callback) {
|
||||
this.subscribers.add(callback)
|
||||
return () => this.subscribers.delete(callback)
|
||||
}
|
||||
|
||||
notifySubscribers() {
|
||||
this.subscribers.forEach(cb => cb())
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate receiving a message (for demo purposes)
|
||||
* In production, this would come from Directus/WebSocket
|
||||
*/
|
||||
async simulateIncomingMessage(chatId, senderPublicKey, plainText) {
|
||||
await cryptoService.ready
|
||||
|
||||
const myPublicKey = cryptoService.getPublicKey()
|
||||
const { nonce, ciphertext } = this.encryptForRecipient(plainText, myPublicKey, senderPublicKey)
|
||||
|
||||
const message = {
|
||||
id: this.generateId(),
|
||||
chatId,
|
||||
senderPublicKey,
|
||||
nonce,
|
||||
ciphertext,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
|
||||
const messages = this.getAllMessages()
|
||||
messages.push(message)
|
||||
this.saveMessages(messages)
|
||||
|
||||
this.notifySubscribers()
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
// Helper for simulation - encrypt as if from another user
|
||||
encryptForRecipient(message, recipientPublicKey, senderSecretKey) {
|
||||
// This is a simplified simulation - in reality the sender would have their own keypair
|
||||
const nacl = window.nacl
|
||||
const naclUtil = window.nacl.util
|
||||
|
||||
const nonce = nacl.randomBytes(nacl.box.nonceLength)
|
||||
const messageUint8 = naclUtil.decodeUTF8(message)
|
||||
|
||||
// For simulation, we create a temporary keypair for the "sender"
|
||||
const senderKeyPair = nacl.box.keyPair.fromSecretKey(
|
||||
naclUtil.decodeBase64(senderSecretKey || naclUtil.encodeBase64(nacl.randomBytes(32)))
|
||||
)
|
||||
|
||||
const encrypted = nacl.box(
|
||||
messageUint8,
|
||||
nonce,
|
||||
naclUtil.decodeBase64(recipientPublicKey),
|
||||
senderKeyPair.secretKey
|
||||
)
|
||||
|
||||
return {
|
||||
nonce: naclUtil.encodeBase64(nonce),
|
||||
ciphertext: naclUtil.encodeBase64(encrypted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const chatService = new ChatService()
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { directus } from './directus.js'
|
||||
import { cryptoService } from './crypto.js'
|
||||
import { authService } from './auth.js'
|
||||
|
||||
class ConversationsService {
|
||||
constructor() {
|
||||
|
||||
Reference in New Issue
Block a user