import { SignalClient } from "./signal-crypto.js"; import { v4 as uuidv4 } from "uuid"; export class EncryptedTodoService { constructor() { this.users = new Map(); // userId -> SignalClient this.todos = new Map(); // todoId -> encrypted todo this.userTodos = new Map(); // userId -> Set of todoIds this.sharedSessions = new Map(); // userId+recipientId -> shared session } async createUser(userId) { if (this.users.has(userId)) { console.log(`User ${userId} already exists. Logging in.`); return { userId }; } const client = await SignalClient.create(userId); this.users.set(userId, client); this.userTodos.set(userId, new Set()); return { userId }; } getUser(userId) { const user = this.users.get(userId); if (!user) { throw new Error("User not found. Please create the user first."); } return user; } async addTodo(userId, todoText) { const client = this.getUser(userId); const todoId = uuidv4(); // Self-encrypt the todo const encryptedTodo = await client.encrypt(todoText); this.todos.set(todoId, { id: todoId, userId, encrypted: encryptedTodo, createdAt: new Date().toISOString(), originalText: todoText, // Store original for sharing (in a real app, this would be encrypted) }); this.userTodos.get(userId).add(todoId); console.log(`Todo added for user ${userId}: ${todoId}`); return todoId; } async getTodos(userId) { const client = this.getUser(userId); const todoIds = this.userTodos.get(userId) || new Set(); const todos = []; for (const todoId of todoIds) { const encryptedTodo = this.todos.get(todoId); if (encryptedTodo) { try { const decryptedText = await client.decrypt(encryptedTodo.encrypted); todos.push({ id: todoId, text: decryptedText, createdAt: encryptedTodo.createdAt, sharedBy: encryptedTodo.sharedBy, sharedWith: encryptedTodo.sharedWith || [], }); } catch (error) { console.error(`Failed to decrypt todo ${todoId}:`, error); } } } return todos.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); } async deleteTodo(userId, todoId) { const userTodos = this.userTodos.get(userId); if (!userTodos || !userTodos.has(todoId)) { throw new Error("Todo not found"); } userTodos.delete(todoId); // Don't delete from todos map if it's shared with others const todo = this.todos.get(todoId); if (todo && (!todo.sharedWith || todo.sharedWith.length === 0)) { this.todos.delete(todoId); } console.log(`Todo deleted: ${todoId}`); return true; } // Share a todo with another user async shareTodo(userId, todoId, recipientId) { // Validate users and todo const senderClient = this.getUser(userId); const recipientClient = this.getUser(recipientId); const todo = this.todos.get(todoId); if (!todo) { throw new Error("Todo not found"); } // Get the original text (in a real app, this would be re-encrypted for the recipient) const originalText = todo.originalText; if (!originalText) { throw new Error("Cannot share this todo"); } // Encrypt the todo for the recipient const encryptedForRecipient = await recipientClient.encrypt(originalText); // Create a new todo ID for the shared copy const sharedTodoId = uuidv4(); // Store the shared todo this.todos.set(sharedTodoId, { id: sharedTodoId, userId: recipientId, encrypted: encryptedForRecipient, createdAt: new Date().toISOString(), originalText: originalText, sharedBy: userId, }); // Add to recipient's todos this.userTodos.get(recipientId).add(sharedTodoId); // Track sharing in the original todo if (!todo.sharedWith) { todo.sharedWith = []; } todo.sharedWith.push(recipientId); console.log( `Todo ${todoId} shared from ${userId} to ${recipientId} as ${sharedTodoId}`, ); return sharedTodoId; } }