Revamp chat UI and add V2 encrypted messaging support
Major update to chat page with new conversation list, message thread view, and support for V2 end-to-end encrypted messaging using DIDs. Adds chat lock state, message sending, conversation deletion, and improved polling. Updates user API and types to include DID and chatPublicKey. Fixes race conditions in feed loading, adds message button to user profile, and improves crypto and identity hooks for better locked/unlocked state detection.
This commit is contained in:
+19
-10
@@ -162,12 +162,17 @@ export async function encrypt(
|
||||
? new TextEncoder().encode(plaintext)
|
||||
: plaintext;
|
||||
|
||||
const algorithm: any = {
|
||||
name: 'AES-GCM',
|
||||
iv: iv
|
||||
};
|
||||
|
||||
if (associatedData) {
|
||||
algorithm.additionalData = associatedData;
|
||||
}
|
||||
|
||||
const encrypted = await cryptoSubtle.encrypt(
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
iv: iv,
|
||||
additionalData: associatedData
|
||||
},
|
||||
algorithm,
|
||||
key,
|
||||
data
|
||||
);
|
||||
@@ -196,12 +201,16 @@ export async function decrypt(
|
||||
const iv = base64ToArrayBuffer(ivBase64);
|
||||
|
||||
try {
|
||||
const algorithm: any = {
|
||||
name: 'AES-GCM',
|
||||
iv: iv
|
||||
};
|
||||
if (associatedData) {
|
||||
algorithm.additionalData = associatedData;
|
||||
}
|
||||
|
||||
const decrypted = await cryptoSubtle.decrypt(
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
iv: iv,
|
||||
additionalData: associatedData
|
||||
},
|
||||
algorithm,
|
||||
key,
|
||||
ciphertext
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
'use client';
|
||||
|
||||
import { useState, useCallback, useRef } from 'react';
|
||||
import { useState, useCallback, useRef, useEffect } from 'react';
|
||||
import {
|
||||
unlockChatStorage,
|
||||
loadDeviceKeys,
|
||||
@@ -40,6 +40,44 @@ export function useChatEncryption() {
|
||||
// Session Cache (In-Memory)
|
||||
const sessionsRef = useRef<Map<string, RatchetState>>(new Map());
|
||||
|
||||
const [isLocked, setIsLocked] = useState(false);
|
||||
|
||||
// Auto-detect if storage is unlocked (by AuthContext)
|
||||
useEffect(() => {
|
||||
if (isReady) {
|
||||
setIsLocked(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const check = async () => {
|
||||
const unlocked = isStorageUnlocked();
|
||||
setIsLocked(!unlocked);
|
||||
|
||||
if (unlocked) {
|
||||
try {
|
||||
// If storage is open, verify keys exist
|
||||
// If so, we are ready.
|
||||
// If not, we might need ensureReady to generate, but usually they exist.
|
||||
const keys = await loadDeviceKeys();
|
||||
if (keys) {
|
||||
setIsReady(true);
|
||||
setStatus('ready');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Auto-ready check failed", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
check(); // Checks immediately
|
||||
const interval = setInterval(check, 1000); // And polls
|
||||
return () => clearInterval(interval);
|
||||
}, [isReady]);
|
||||
|
||||
// ... (ensureReady, sendMessage, decryptMessage) ...
|
||||
|
||||
|
||||
|
||||
const ensureReady = useCallback(async (password: string, userId: string) => {
|
||||
setStatus('initializing');
|
||||
try {
|
||||
@@ -279,6 +317,7 @@ export function useChatEncryption() {
|
||||
|
||||
return {
|
||||
isReady,
|
||||
isLocked,
|
||||
status,
|
||||
ensureReady,
|
||||
sendMessage,
|
||||
|
||||
@@ -29,9 +29,18 @@ export function useUserIdentity() {
|
||||
const [isUnlocked, setIsUnlocked] = useState(false);
|
||||
|
||||
// Check status on mount / updates
|
||||
// Check status on mount / updates and poll for changes in singleton
|
||||
useEffect(() => {
|
||||
const hasKey = !!keyStore.getPrivateKey();
|
||||
setIsUnlocked(hasKey);
|
||||
const check = () => {
|
||||
const hasKey = !!keyStore.getPrivateKey();
|
||||
setIsUnlocked(hasKey);
|
||||
// We could also try to recover identity data if it's missing but key exists?
|
||||
// But identity data usually comes from initializeIdentity
|
||||
};
|
||||
|
||||
check();
|
||||
const interval = setInterval(check, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,8 @@ export interface User {
|
||||
isBot?: boolean;
|
||||
isSwarm?: boolean; // Whether this user is from a Synapsis swarm node
|
||||
nodeDomain?: string | null; // Domain of the node this user is from (for swarm users)
|
||||
did?: string;
|
||||
chatPublicKey?: string;
|
||||
botOwner?: {
|
||||
id: string;
|
||||
handle: string;
|
||||
|
||||
Reference in New Issue
Block a user