Refactor chat system to remove E2EE and simplify messaging

Removed all E2EE chat endpoints, crypto logic, and related API routes, transitioning chat to plain text storage and transport. Updated chat send/receive endpoints to use signed actions and enforce DM privacy settings. Cleaned up Swarm chat inbox, key management, and related debug endpoints. Updated user profile to support DM privacy, improved search for handle queries, and refactored conversation/message logic to support the new model. Migrated bot settings and privacy settings to new locations.
This commit is contained in:
Christopher
2026-01-28 13:05:34 -08:00
parent b52b3f9804
commit ceae76d58f
44 changed files with 1945 additions and 3058 deletions
@@ -54,7 +54,6 @@ export async function GET(request: NextRequest) {
handle: participant2Handle,
displayName: participant2Handle,
avatarUrl: null as string | null,
chatPublicKey: null as string | null,
};
// Try to get cached user info
@@ -67,20 +66,22 @@ export async function GET(request: NextRequest) {
handle: cachedUser.handle,
displayName: cachedUser.displayName || cachedUser.handle,
avatarUrl: cachedUser.avatarUrl,
chatPublicKey: cachedUser.chatPublicKey, // ECDH key for E2E chat
};
}
return {
...conv,
participant2: participant2Info,
participant2: {
...participant2Info,
isBot: cachedUser?.isBot || false,
},
unreadCount: Number(unreadCount[0]?.count || 0),
};
})
);
return NextResponse.json({
conversations: conversationsWithUnread,
conversations: conversationsWithUnread.filter(c => !c.participant2.isBot),
});
} catch (error) {
console.error('List conversations error:', error);
-217
View File
@@ -1,217 +0,0 @@
/**
* Swarm Chat Inbox
*
* POST: Receives chat messages from other swarm nodes
*/
import { NextRequest, NextResponse } from 'next/server';
import { db, users, chatConversations, chatMessages } from '@/db';
import { eq, and } from 'drizzle-orm';
import { z } from 'zod';
import type { SwarmChatMessagePayload } from '@/lib/swarm/chat-types';
const chatMessageSchema = z.object({
messageId: z.string(),
senderHandle: z.string(),
senderDisplayName: z.string().optional(),
senderAvatarUrl: z.string().optional(),
senderNodeDomain: z.string(),
recipientHandle: z.string(),
encryptedContent: z.string(),
timestamp: z.string(),
signature: z.string().optional(),
});
export async function POST(request: NextRequest) {
try {
if (!db) {
return NextResponse.json({ error: 'Database not available' }, { status: 503 });
}
const body = await request.json();
console.log('[Swarm Inbox] Received body keys:', Object.keys(body), 'action:', body.action);
// Check if this is a V2 encrypted envelope (has 'action' and 'data' fields)
// MUST check BEFORE Zod validation to avoid validation errors
if (body.action === 'chat.deliver' && body.data) {
// V2 E2EE Message - store in chatInbox
const { recipientDid, recipientDeviceId, ciphertext } = body.data;
if (!recipientDid || !ciphertext) {
return NextResponse.json({ error: 'Invalid V2 payload' }, { status: 400 });
}
// Find recipient by DID
const recipient = await db.query.users.findFirst({
where: eq(users.did, recipientDid)
});
if (!recipient) {
return NextResponse.json({ error: 'Recipient not found' }, { status: 404 });
}
// Import schemas
const { chatInbox, chatConversations, chatMessages } = await import('@/db/schema');
// Store in V2 inbox
await db.insert(chatInbox).values({
senderDid: body.did, // From signed envelope
recipientDid,
recipientDeviceId: recipientDeviceId || null,
envelope: JSON.stringify(body),
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
});
// Also create conversation and message for recipient UI
// Extract sender info from envelope
const senderHandle = body.handle || 'unknown';
const senderNodeDomain = body.data?.senderNodeDomain || 'unknown';
const senderFullHandle = senderNodeDomain !== 'unknown'
? `${senderHandle}@${senderNodeDomain}`
: senderHandle;
console.log('[Swarm Chat V2] Sender info:', { senderHandle, senderNodeDomain, senderFullHandle });
// Get or create conversation for recipient
let conversation = await db.query.chatConversations.findFirst({
where: and(
eq(chatConversations.participant1Id, recipient.id),
eq(chatConversations.participant2Handle, senderFullHandle)
)
});
if (!conversation) {
const [newConv] = await db.insert(chatConversations).values({
participant1Id: recipient.id,
participant2Handle: senderFullHandle,
lastMessageAt: new Date(),
lastMessagePreview: '[Encrypted message]'
}).returning();
conversation = newConv;
console.log(`[Swarm Chat V2] Created conversation for recipient:`, conversation.id);
} else {
// Update last message time
await db.update(chatConversations)
.set({
lastMessageAt: new Date(),
lastMessagePreview: '[Encrypted message]'
})
.where(eq(chatConversations.id, conversation.id));
}
// Store message reference so it appears in UI
// Store full envelope data as JSON so we can decrypt later
const envelopeData = {
did: body.did,
handle: body.handle,
ciphertext: body.data.ciphertext
};
const messageId = crypto.randomUUID();
await db.insert(chatMessages).values({
conversationId: conversation.id,
senderHandle: senderFullHandle,
senderDisplayName: null, // Unknown until decrypted
senderAvatarUrl: null,
senderNodeDomain: senderNodeDomain !== 'unknown' ? senderNodeDomain : null,
encryptedContent: JSON.stringify(envelopeData), // Full envelope for decryption
senderChatPublicKey: null,
swarmMessageId: `swarm:v2:${messageId}`,
deliveredAt: new Date(),
readAt: null,
});
console.log(`[Swarm Chat V2] Received encrypted message for ${recipientDid}, conversation:`, conversation.id);
return NextResponse.json({
success: true,
message: 'V2 message received',
version: 2
});
}
// V1 Legacy Message Format - validate with Zod
const data = chatMessageSchema.parse(body) as SwarmChatMessagePayload;
// Find the recipient (local user)
const recipient = await db.query.users.findFirst({
where: eq(users.handle, data.recipientHandle.toLowerCase()),
});
if (!recipient) {
return NextResponse.json({ error: 'Recipient not found' }, { status: 404 });
}
if (recipient.isSuspended) {
return NextResponse.json({ error: 'Recipient not available' }, { status: 404 });
}
// Check if message already exists (prevent duplicates)
const swarmMessageId = `swarm:${data.senderNodeDomain}:${data.messageId}`;
const existingMessage = await db.query.chatMessages.findFirst({
where: eq(chatMessages.swarmMessageId, swarmMessageId),
});
if (existingMessage) {
return NextResponse.json({
success: true,
message: 'Message already received',
});
}
// Get or create conversation
const senderFullHandle = `${data.senderHandle}@${data.senderNodeDomain}`;
let conversation = await db.query.chatConversations.findFirst({
where: and(
eq(chatConversations.participant1Id, recipient.id),
eq(chatConversations.participant2Handle, senderFullHandle)
),
});
if (!conversation) {
const [newConversation] = await db.insert(chatConversations).values({
participant1Id: recipient.id,
participant2Handle: senderFullHandle,
lastMessageAt: new Date(data.timestamp),
lastMessagePreview: '[Encrypted message]',
}).returning();
conversation = newConversation;
}
// Store the message
const [newMessage] = await db.insert(chatMessages).values({
conversationId: conversation.id,
senderHandle: senderFullHandle,
senderDisplayName: data.senderDisplayName,
senderAvatarUrl: data.senderAvatarUrl,
senderNodeDomain: data.senderNodeDomain,
encryptedContent: data.encryptedContent,
swarmMessageId,
deliveredAt: new Date(),
createdAt: new Date(data.timestamp),
}).returning();
// Update conversation last message
await db.update(chatConversations)
.set({
lastMessageAt: new Date(data.timestamp),
lastMessagePreview: '[Encrypted message]',
updatedAt: new Date(),
})
.where(eq(chatConversations.id, conversation.id));
console.log(`[Swarm Chat] Received message from ${senderFullHandle} to ${data.recipientHandle}`);
return NextResponse.json({
success: true,
message: 'Message received',
messageId: newMessage.id,
});
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({ error: 'Invalid payload', details: error.issues }, { status: 400 });
}
console.error('Swarm chat inbox error:', error);
return NextResponse.json({ error: 'Failed to receive message' }, { status: 500 });
}
}
+5 -88
View File
@@ -9,7 +9,7 @@ import { NextRequest, NextResponse } from 'next/server';
import { db, chatConversations, chatMessages, users } from '@/db';
import { eq, desc, and, lt, isNull } from 'drizzle-orm';
import { getSession } from '@/lib/auth';
import { decryptMessage } from '@/lib/swarm/chat-crypto';
export async function GET(request: NextRequest) {
try {
@@ -43,15 +43,6 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: 'Conversation not found' }, { status: 404 });
}
// Get user's private key for decryption
const user = await db.query.users.findFirst({
where: eq(users.id, session.user.id),
});
if (!user?.privateKeyEncrypted) {
return NextResponse.json({ error: 'Cannot decrypt messages' }, { status: 500 });
}
// Build query with cursor-based pagination
const baseCondition = eq(chatMessages.conversationId, conversationId);
const whereCondition = cursor
@@ -65,90 +56,16 @@ export async function GET(request: NextRequest) {
limit,
});
// Get recipient info for sent messages
const recipientHandle = conversation.participant2Handle;
let recipientPublicKey: string | null = null;
console.log('[Messages API] Fetching recipient key for:', recipientHandle);
// Check if this is a remote user (has @domain)
const isRemote = recipientHandle.includes('@');
if (isRemote) {
// Remote user - fetch from their node
const [handle, domain] = recipientHandle.split('@');
try {
const protocol = domain.includes('localhost') ? 'http' : 'https';
const response = await fetch(`${protocol}://${domain}/api/users/${handle}`);
if (response.ok) {
const data = await response.json();
recipientPublicKey = data.user?.chatPublicKey || null;
console.log('[Messages API] Fetched remote recipient key:', !!recipientPublicKey);
}
} catch (error) {
console.error('[Messages API] Failed to fetch remote recipient key:', error);
}
} else {
// Local user
const recipientUser = await db.query.users.findFirst({
where: eq(users.handle, recipientHandle),
});
recipientPublicKey = recipientUser?.chatPublicKey || null;
}
console.log('[Messages API] Recipient public key found:', !!recipientPublicKey);
// Get sender DID for received messages
const senderDids = new Map<string, string>();
for (const msg of messages) {
const messagesMapped = messages.map((msg) => {
const isSentByMe = msg.senderHandle === session.user.handle;
if (!isSentByMe && !senderDids.has(msg.senderHandle)) {
// Try to get DID for this sender
try {
const isRemote = msg.senderHandle.includes('@');
if (isRemote) {
const [handle, domain] = msg.senderHandle.split('@');
const protocol = domain.includes('localhost') ? 'http' : 'https';
const response = await fetch(`${protocol}://${domain}/api/users/${handle}`);
if (response.ok) {
const data = await response.json();
if (data.user?.did) {
senderDids.set(msg.senderHandle, data.user.did);
}
}
} else {
const senderUser = await db.query.users.findFirst({
where: eq(users.handle, msg.senderHandle),
});
if (senderUser?.did) {
senderDids.set(msg.senderHandle, senderUser.did);
}
}
} catch (e) {
console.error('[Messages API] Failed to resolve sender DID:', e);
}
}
}
const messagesWithDecryption = messages.map((msg) => {
const isSentByMe = msg.senderHandle === session.user.handle;
const senderPubKey = isSentByMe ? recipientPublicKey : msg.senderChatPublicKey;
console.log('[Messages API] Message:', msg.id, 'isSentByMe:', isSentByMe, 'senderPubKey:', !!senderPubKey, 'msgSenderChatPubKey:', !!msg.senderChatPublicKey);
return {
id: msg.id,
senderHandle: msg.senderHandle,
senderDisplayName: msg.senderDisplayName,
senderAvatarUrl: msg.senderAvatarUrl,
senderDid: isSentByMe ? undefined : senderDids.get(msg.senderHandle), // Add DID for received messages
// For decryption:
// - Sent messages: need recipient's public key
// - Received messages: need sender's public key
senderPublicKey: senderPubKey,
isE2E: !!msg.senderChatPublicKey || (isSentByMe && !!recipientPublicKey),
encryptedContent: msg.encryptedContent, // This is now the full envelope JSON
senderDid: msg.senderDid,
content: msg.content,
deliveredAt: msg.deliveredAt,
readAt: msg.readAt,
createdAt: msg.createdAt,
@@ -157,7 +74,7 @@ export async function GET(request: NextRequest) {
});
return NextResponse.json({
messages: messagesWithDecryption.reverse(), // Oldest first for display
messages: messagesMapped.reverse(), // Oldest first for display
nextCursor: messages.length === limit ? messages[messages.length - 1].createdAt.toISOString() : null,
});
} catch (error) {
-346
View File
@@ -1,346 +0,0 @@
/**
* Swarm Chat Send
*
* POST: Send a chat message to another user (local or remote)
*/
import { NextRequest, NextResponse } from 'next/server';
import { db, users, chatConversations, chatMessages } from '@/db';
import { eq, and } from 'drizzle-orm';
import { z } from 'zod';
import { getSession } from '@/lib/auth';
import { encryptMessage } from '@/lib/swarm/chat-crypto';
import type { SwarmChatMessagePayload } from '@/lib/swarm/chat-types';
const sendMessageSchema = z.object({
recipientHandle: z.string(),
// For E2E encryption: client sends pre-encrypted content
encryptedContent: z.string().optional(),
senderPublicKey: z.string().optional(), // ECDH public key for decryption
// Legacy: server-side encryption (will be removed)
content: z.string().min(1).max(5000).optional(),
}).refine(data => data.encryptedContent || data.content, {
message: 'Either encryptedContent or content is required',
});
export async function POST(request: NextRequest) {
console.log('[Chat Send] Starting request processing');
try {
if (!db) {
console.error('[Chat Send] Database connection missing');
return NextResponse.json({ error: 'Database not available' }, { status: 503 });
}
const session = await getSession();
if (!session?.user) {
console.warn('[Chat Send] Unauthorized attempt');
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
console.log('[Chat Send] User authenticated:', session.user.id);
let body;
try {
body = await request.json();
} catch (e) {
console.error('[Chat Send] Failed to parse JSON body');
return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 });
}
const parseResult = sendMessageSchema.safeParse(body);
if (!parseResult.success) {
console.error('[Chat Send] Schema validation failed:', parseResult.error);
return NextResponse.json({ error: 'Invalid input', details: parseResult.error.issues }, { status: 400 });
}
const data = parseResult.data;
console.log('[Chat Send] Input validated. Recipient:', data.recipientHandle, 'Has senderPublicKey:', !!data.senderPublicKey);
// Get sender info
const sender = await db.query.users.findFirst({
where: eq(users.id, session.user.id),
});
if (!sender) {
console.error('[Chat Send] Sender not found in DB:', session.user.id);
return NextResponse.json({ error: 'Sender not found' }, { status: 404 });
}
console.log('[Chat Send] Sender retrieved:', sender.handle);
// Parse recipient handle (could be local or remote)
// Strip leading @ if present, then normalize
const recipientHandle = data.recipientHandle.toLowerCase().replace(/^@/, '');
const isRemote = recipientHandle.includes('@');
let recipientUser: typeof users.$inferSelect | undefined;
let recipientPublicKey: string;
let recipientNodeDomain: string | null = null;
if (isRemote) {
// Remote user - need to fetch their public key
// Format: handle@domain (e.g., matterbator@batorbros.bond)
const atIndex = recipientHandle.indexOf('@');
const handle = recipientHandle.substring(0, atIndex);
const domain = recipientHandle.substring(atIndex + 1);
recipientNodeDomain = domain;
console.log('[Chat Send] Processing remote recipient:', handle, '@', domain);
// Try to find cached remote user
recipientUser = await db.query.users.findFirst({
where: eq(users.handle, recipientHandle),
});
// Check if we have a valid public key (not a placeholder)
const hasValidPublicKey = recipientUser?.publicKey?.startsWith('-----BEGIN');
if (!recipientUser || !hasValidPublicKey) {
// Fetch from remote node to get the real public key
try {
console.log('[Chat Send] Fetching remote user from node:', domain);
const protocol = domain.includes('localhost') ? 'http' : 'https';
const response = await fetch(`${protocol}://${domain}/api/users/${handle}`);
if (!response.ok) {
console.error('[Chat Send] Remote user fetch failed. Status:', response.status);
return NextResponse.json({ error: 'Recipient not found' }, { status: 404 });
}
const remoteUserData = await response.json();
const userData = remoteUserData.user || remoteUserData;
recipientPublicKey = userData.publicKey;
if (!recipientPublicKey || !recipientPublicKey.startsWith('-----BEGIN')) {
console.error('[Chat Send] Remote user has no valid public key');
return NextResponse.json({ error: 'Recipient does not support encrypted chat' }, { status: 400 });
}
if (recipientUser) {
// Update existing cached user with real public key
await db.update(users)
.set({ publicKey: recipientPublicKey })
.where(eq(users.id, recipientUser.id));
console.log('[Chat Send] Updated cached user with real public key');
} else {
// Cache the remote user
const [newUser] = await db.insert(users).values({
did: userData.did || `did:swarm:${domain}:${handle}`,
handle: recipientHandle,
displayName: userData.displayName,
avatarUrl: userData.avatarUrl,
publicKey: recipientPublicKey,
}).returning();
recipientUser = newUser;
console.log('[Chat Send] Remote user cached');
}
} catch (error) {
console.error('[Chat Send] Failed to fetch remote user:', error);
return NextResponse.json({ error: 'Failed to reach recipient node' }, { status: 503 });
}
} else {
recipientPublicKey = recipientUser.publicKey;
console.log('[Chat Send] Remote user found in cache with valid key');
}
} else {
// Local user
console.log('[Chat Send] Processing local recipient');
recipientUser = await db.query.users.findFirst({
where: eq(users.handle, recipientHandle),
});
if (!recipientUser) {
console.warn('[Chat Send] Local recipient not found:', recipientHandle);
return NextResponse.json({ error: 'Recipient not found' }, { status: 404 });
}
if (recipientUser.isSuspended) {
console.warn('[Chat Send] Local recipient suspended:', recipientHandle);
return NextResponse.json({ error: 'Recipient not available' }, { status: 404 });
}
recipientPublicKey = recipientUser.publicKey;
}
// Handle encryption - either client-side (E2E) or server-side (legacy)
let encryptedContent: string;
let senderEncryptedContent: string | null = null;
if (data.encryptedContent) {
// E2E mode: client already encrypted the message
// Server cannot read the content - this is true E2E encryption
console.log('[Chat Send] Using client-side E2E encryption');
encryptedContent = data.encryptedContent;
// Store sender's public key so recipient can decrypt
// Note: senderEncryptedContent not needed in E2E mode - client stores locally
} else if (data.content) {
// Legacy mode: server-side encryption (for backwards compatibility)
console.log('[Chat Send] Using server-side encryption (legacy)');
try {
encryptedContent = encryptMessage(data.content, recipientPublicKey);
senderEncryptedContent = encryptMessage(data.content, sender.publicKey);
} catch (encError) {
console.error('[Chat Send] Encryption failed:', encError);
return NextResponse.json({ error: 'Encryption failed' }, { status: 500 });
}
} else {
return NextResponse.json({ error: 'No message content provided' }, { status: 400 });
}
// Get or create conversation
let conversation = await db.query.chatConversations.findFirst({
where: and(
eq(chatConversations.participant1Id, sender.id),
eq(chatConversations.participant2Handle, recipientHandle)
),
});
if (!conversation) {
console.log('[Chat Send] Creating new conversation');
const [newConversation] = await db.insert(chatConversations).values({
participant1Id: sender.id,
participant2Handle: recipientHandle,
lastMessageAt: new Date(),
lastMessagePreview: 'New message',
}).returning();
conversation = newConversation;
}
// Store the message locally
const messageId = crypto.randomUUID();
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost';
const swarmMessageId = `swarm:${nodeDomain}:${messageId}`;
console.log('[Chat Send] Inserting message into DB, senderPublicKey from client:', !!data.senderPublicKey, 'from DB:', !!sender.chatPublicKey);
const [newMessage] = await db.insert(chatMessages).values({
conversationId: conversation.id,
senderHandle: sender.handle,
senderDisplayName: sender.displayName,
senderAvatarUrl: sender.avatarUrl,
senderNodeDomain: null, // Local sender
encryptedContent,
senderEncryptedContent,
// Use client-provided key first, fall back to database
senderChatPublicKey: data.senderPublicKey || sender.chatPublicKey,
swarmMessageId,
deliveredAt: isRemote ? null : new Date(), // Delivered immediately if local
readAt: null,
}).returning();
// Update conversation
await db.update(chatConversations)
.set({
lastMessageAt: new Date(),
lastMessagePreview: 'New message',
updatedAt: new Date(),
})
.where(eq(chatConversations.id, conversation.id));
// For LOCAL recipients, create/update their conversation too
if (!isRemote && recipientUser) {
try {
console.log('[Chat Send] Creating reciprocal conversation for local recipient:', recipientUser.handle);
// Check if recipient has a conversation with sender
let recipientConversation = await db.query.chatConversations.findFirst({
where: and(
eq(chatConversations.participant1Id, recipientUser.id),
eq(chatConversations.participant2Handle, sender.handle)
),
});
if (!recipientConversation) {
// Create conversation for recipient
const [newRecipientConv] = await db.insert(chatConversations).values({
participant1Id: recipientUser.id,
participant2Handle: sender.handle,
lastMessageAt: new Date(),
lastMessagePreview: 'New message',
}).returning();
recipientConversation = newRecipientConv;
console.log('[Chat Send] Created new conversation for recipient');
} else {
// Update existing conversation
await db.update(chatConversations)
.set({
lastMessageAt: new Date(),
lastMessagePreview: 'New message',
updatedAt: new Date(),
})
.where(eq(chatConversations.id, recipientConversation.id));
console.log('[Chat Send] Updated existing conversation for recipient');
}
// Insert message into recipient's conversation
await db.insert(chatMessages).values({
conversationId: recipientConversation.id,
senderHandle: sender.handle,
senderDisplayName: sender.displayName,
senderAvatarUrl: sender.avatarUrl,
senderNodeDomain: null,
encryptedContent,
senderEncryptedContent,
senderChatPublicKey: data.senderPublicKey || sender.chatPublicKey,
swarmMessageId: `${swarmMessageId}-recipient`, // Make it unique for recipient's copy
deliveredAt: new Date(),
readAt: null,
});
console.log('[Chat Send] Reciprocal message created for local recipient');
} catch (recipError) {
console.error('[Chat Send] Failed to create reciprocal conversation:', recipError);
// Don't fail the whole request - sender's message was still saved
}
}
// If remote, send to their node
if (isRemote && recipientNodeDomain) {
// ... (remote logic remains similar but add logs)
console.log('[Chat Send] Dispatching to remote node:', recipientNodeDomain);
// ... existing remote send logic ...
// For brevity in this tool call, I'm keeping the original logic mostly intact but wrapped/logged.
// Re-implementing the block:
try {
const payload: SwarmChatMessagePayload = {
messageId,
senderHandle: sender.handle,
senderDisplayName: sender.displayName || undefined,
senderAvatarUrl: sender.avatarUrl || undefined,
senderNodeDomain: nodeDomain,
recipientHandle: recipientHandle.split('@')[0],
encryptedContent,
timestamp: new Date().toISOString(),
};
const protocol = recipientNodeDomain.includes('localhost') ? 'http' : 'https';
const response = await fetch(`${protocol}://${recipientNodeDomain}/api/swarm/chat/inbox`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (response.ok) {
// Mark as delivered
await db.update(chatMessages)
.set({ deliveredAt: new Date() })
.where(eq(chatMessages.id, newMessage.id));
console.log('[Chat Send] Remote delivery confirmed');
} else {
console.warn('[Chat Send] Remote delivery failed. Status:', response.status);
}
} catch (error) {
console.error('[Chat Send] Failed to send message to remote node:', error);
// Message is still stored locally, will show as undelivered
}
}
console.log('[Chat Send] Success');
return NextResponse.json({
success: true,
message: newMessage,
});
} catch (error) {
if (error instanceof z.ZodError) {
// Should be caught by safeParse above, but just in case
return NextResponse.json({ error: 'Invalid input', details: error.issues }, { status: 400 });
}
console.error('[Chat Send] Unhandled error:', error);
return NextResponse.json({ error: 'Failed to send message' }, { status: 500 });
}
}
+2 -2
View File
@@ -22,7 +22,7 @@ export interface SwarmUserProfile {
isBot?: boolean;
botOwnerHandle?: string; // Handle of the bot's owner (e.g., "user" or "user@domain")
nodeDomain: string;
chatPublicKey?: string;
publicKey?: string; // Signing key for verifying actions
did?: string;
}
@@ -93,7 +93,7 @@ export async function GET(request: NextRequest, context: RouteContext) {
isBot: user.isBot || undefined,
botOwnerHandle: user.isBot && user.botOwner ? user.botOwner.handle : undefined,
nodeDomain,
chatPublicKey: user.chatPublicKey || undefined,
publicKey: user.publicKey, // Expose signing key
did: user.did || undefined,
};