146 lines
4.1 KiB
JavaScript
146 lines
4.1 KiB
JavaScript
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;
|
|
}
|
|
}
|