Enhance chat key lookup with remote and cache fallback
The GET /api/chat/keys endpoint now checks a remote identity cache and, if necessary, fetches keys from remote nodes or Swarm user profiles when a local key is not found. Results from remote lookups are cached for 24 hours to improve performance and reliability.
This commit is contained in:
+118
-11
@@ -1,8 +1,8 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { db } from '@/db';
|
import { db } from '@/db';
|
||||||
import { chatDeviceBundles } from '@/db/schema';
|
import { chatDeviceBundles, handleRegistry, remoteIdentityCache } from '@/db/schema';
|
||||||
import { requireAuth } from '@/lib/auth';
|
import { requireAuth } from '@/lib/auth';
|
||||||
import { eq, and } from 'drizzle-orm';
|
import { eq, and, gt } from 'drizzle-orm';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/chat/keys?did=<did>
|
* GET /api/chat/keys?did=<did>
|
||||||
@@ -23,7 +23,7 @@ export async function GET(request: NextRequest) {
|
|||||||
where: eq(chatDeviceBundles.did, did),
|
where: eq(chatDeviceBundles.did, did),
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('[Chat Keys GET] Found bundle:', bundle ? 'YES' : 'NO');
|
console.log('[Chat Keys GET] Found local bundle:', bundle ? 'YES' : 'NO');
|
||||||
if (bundle) {
|
if (bundle) {
|
||||||
console.log('[Chat Keys GET] Bundle data:', {
|
console.log('[Chat Keys GET] Bundle data:', {
|
||||||
userId: bundle.userId,
|
userId: bundle.userId,
|
||||||
@@ -31,16 +31,123 @@ export async function GET(request: NextRequest) {
|
|||||||
deviceId: bundle.deviceId,
|
deviceId: bundle.deviceId,
|
||||||
hasIdentityKey: !!bundle.identityKey,
|
hasIdentityKey: !!bundle.identityKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// For libsodium, we just need the public key
|
||||||
|
return NextResponse.json({
|
||||||
|
publicKey: bundle.identityKey,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bundle) {
|
// If not found locally, check remote identity cache
|
||||||
return NextResponse.json({ error: 'No keys found' }, { status: 404 });
|
const cached = await db.query.remoteIdentityCache.findFirst({
|
||||||
}
|
where: and(
|
||||||
|
eq(remoteIdentityCache.did, did),
|
||||||
// For libsodium, we just need the public key
|
gt(remoteIdentityCache.expiresAt, new Date())
|
||||||
return NextResponse.json({
|
),
|
||||||
publicKey: bundle.identityKey, // Reusing identityKey field for libsodium public key
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (cached) {
|
||||||
|
console.log('[Chat Keys GET] Found cached remote key');
|
||||||
|
return NextResponse.json({
|
||||||
|
publicKey: cached.publicKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not in cache, try to resolve DID to a node
|
||||||
|
console.log('[Chat Keys GET] resolving DID to node...');
|
||||||
|
const handleEntry = await db.query.handleRegistry.findFirst({
|
||||||
|
where: eq(handleRegistry.did, did),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (handleEntry) {
|
||||||
|
const { nodeDomain } = handleEntry;
|
||||||
|
console.log('[Chat Keys GET] Resolved to node:', nodeDomain);
|
||||||
|
|
||||||
|
// Fetch from remote node
|
||||||
|
const remoteUrl = `https://${nodeDomain}/api/chat/keys?did=${encodeURIComponent(did)}`;
|
||||||
|
console.log('[Chat Keys GET] Fetching from remote:', remoteUrl);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const remoteRes = await fetch(remoteUrl);
|
||||||
|
if (remoteRes.ok) {
|
||||||
|
const data = await remoteRes.json();
|
||||||
|
if (data.publicKey) {
|
||||||
|
console.log('[Chat Keys GET] Successfully fetched remote key');
|
||||||
|
|
||||||
|
// Cache it
|
||||||
|
await db.insert(remoteIdentityCache).values({
|
||||||
|
did: did,
|
||||||
|
publicKey: data.publicKey,
|
||||||
|
fetchedAt: new Date(),
|
||||||
|
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours
|
||||||
|
}).onConflictDoUpdate({
|
||||||
|
target: remoteIdentityCache.did,
|
||||||
|
set: {
|
||||||
|
publicKey: data.publicKey,
|
||||||
|
fetchedAt: new Date(),
|
||||||
|
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
publicKey: data.publicKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('[Chat Keys GET] Remote fetch failed:', remoteRes.status, remoteRes.statusText);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Chat Keys GET] Remote fetch error:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FALLBACK: Try fetching from Swarm User Profile (standard endpoint)
|
||||||
|
if (!cached) {
|
||||||
|
console.log('[Chat Keys GET] Trying fallback to Swarm Profile...');
|
||||||
|
const swarmUrl = `https://${nodeDomain}/api/swarm/users/${handleEntry.handle}`;
|
||||||
|
console.log('[Chat Keys GET] Fetching Swarm Profile:', swarmUrl);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const swarmRes = await fetch(swarmUrl);
|
||||||
|
if (swarmRes.ok) {
|
||||||
|
const data = await swarmRes.json();
|
||||||
|
const chatKey = data.profile?.chatPublicKey;
|
||||||
|
|
||||||
|
if (chatKey) {
|
||||||
|
console.log('[Chat Keys GET] Found key in Swarm Profile');
|
||||||
|
|
||||||
|
// Cache it
|
||||||
|
await db.insert(remoteIdentityCache).values({
|
||||||
|
did: did,
|
||||||
|
publicKey: chatKey,
|
||||||
|
fetchedAt: new Date(),
|
||||||
|
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours
|
||||||
|
}).onConflictDoUpdate({
|
||||||
|
target: remoteIdentityCache.did,
|
||||||
|
set: {
|
||||||
|
publicKey: chatKey,
|
||||||
|
fetchedAt: new Date(),
|
||||||
|
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
publicKey: chatKey,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn('[Chat Keys GET] Swarm Profile found but no chatPublicKey');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('[Chat Keys GET] Swarm Profile fetch failed:', swarmRes.status);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Chat Keys GET] Swarm Profile fetch error:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('[Chat Keys GET] DID not found in handle registry');
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({ error: 'No keys found' }, { status: 404 });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('[Chat Keys] Failed to fetch keys:', error);
|
console.error('[Chat Keys] Failed to fetch keys:', error);
|
||||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||||
@@ -58,7 +165,7 @@ export async function GET(request: NextRequest) {
|
|||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const user = await requireAuth();
|
const user = await requireAuth();
|
||||||
|
|
||||||
console.log('[Chat Keys POST] User:', user.handle, 'DID:', user.did, 'UserID:', user.id);
|
console.log('[Chat Keys POST] User:', user.handle, 'DID:', user.did, 'UserID:', user.id);
|
||||||
|
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
|
|||||||
Reference in New Issue
Block a user