feat: include chatPublicKey in user profiles and the swarm user interface.
This commit is contained in:
@@ -22,6 +22,7 @@ export interface SwarmUserProfile {
|
|||||||
isBot?: boolean;
|
isBot?: boolean;
|
||||||
botOwnerHandle?: string; // Handle of the bot's owner (e.g., "user" or "user@domain")
|
botOwnerHandle?: string; // Handle of the bot's owner (e.g., "user" or "user@domain")
|
||||||
nodeDomain: string;
|
nodeDomain: string;
|
||||||
|
chatPublicKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SwarmUserPost {
|
export interface SwarmUserPost {
|
||||||
@@ -91,6 +92,7 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
|||||||
isBot: user.isBot || undefined,
|
isBot: user.isBot || undefined,
|
||||||
botOwnerHandle: user.isBot && user.botOwner ? user.botOwner.handle : undefined,
|
botOwnerHandle: user.isBot && user.botOwner ? user.botOwner.handle : undefined,
|
||||||
nodeDomain,
|
nodeDomain,
|
||||||
|
chatPublicKey: user.chatPublicKey || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get user's recent posts
|
// Get user's recent posts
|
||||||
@@ -120,7 +122,7 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
|||||||
|
|
||||||
// Fetch media for each post
|
// Fetch media for each post
|
||||||
const swarmPosts: SwarmUserPost[] = [];
|
const swarmPosts: SwarmUserPost[] = [];
|
||||||
|
|
||||||
for (const post of userPosts) {
|
for (const post of userPosts) {
|
||||||
const postMedia = await db
|
const postMedia = await db
|
||||||
.select({ url: media.url, mimeType: media.mimeType, altText: media.altText })
|
.select({ url: media.url, mimeType: media.mimeType, altText: media.altText })
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export async function GET(request: Request, context: RouteContext) {
|
|||||||
|
|
||||||
// If user exists but is a remote placeholder (handle contains @), fetch fresh data from remote
|
// If user exists but is a remote placeholder (handle contains @), fetch fresh data from remote
|
||||||
const isRemotePlaceholder = user && cleanHandle.includes('@');
|
const isRemotePlaceholder = user && cleanHandle.includes('@');
|
||||||
|
|
||||||
if (!user || isRemotePlaceholder) {
|
if (!user || isRemotePlaceholder) {
|
||||||
if (remoteHandle && remoteDomain) {
|
if (remoteHandle && remoteDomain) {
|
||||||
// Only fetch from swarm nodes
|
// Only fetch from swarm nodes
|
||||||
@@ -44,7 +44,7 @@ export async function GET(request: Request, context: RouteContext) {
|
|||||||
const profileData = await fetchSwarmUserProfile(remoteHandle, remoteDomain, 0);
|
const profileData = await fetchSwarmUserProfile(remoteHandle, remoteDomain, 0);
|
||||||
if (profileData?.profile) {
|
if (profileData?.profile) {
|
||||||
const profile = profileData.profile;
|
const profile = profileData.profile;
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
user: {
|
user: {
|
||||||
id: `swarm:${remoteDomain}:${profile.handle}`,
|
id: `swarm:${remoteDomain}:${profile.handle}`,
|
||||||
@@ -62,11 +62,12 @@ export async function GET(request: Request, context: RouteContext) {
|
|||||||
isSwarm: true,
|
isSwarm: true,
|
||||||
nodeDomain: remoteDomain,
|
nodeDomain: remoteDomain,
|
||||||
isBot: profile.isBot || false,
|
isBot: profile.isBot || false,
|
||||||
|
chatPublicKey: profile.chatPublicKey,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-swarm nodes are no longer supported
|
// Non-swarm nodes are no longer supported
|
||||||
return NextResponse.json({ error: 'User not found. Only Synapsis swarm nodes are supported.' }, { status: 404 });
|
return NextResponse.json({ error: 'User not found. Only Synapsis swarm nodes are supported.' }, { status: 404 });
|
||||||
}
|
}
|
||||||
@@ -97,7 +98,7 @@ export async function GET(request: Request, context: RouteContext) {
|
|||||||
publicKey: user.publicKey, // RSA key for signing
|
publicKey: user.publicKey, // RSA key for signing
|
||||||
chatPublicKey: user.chatPublicKey, // ECDH key for E2E chat
|
chatPublicKey: user.chatPublicKey, // ECDH key for E2E chat
|
||||||
};
|
};
|
||||||
|
|
||||||
// If this is a bot, include owner info
|
// If this is a bot, include owner info
|
||||||
if (user.isBot && user.botOwnerId) {
|
if (user.isBot && user.botOwnerId) {
|
||||||
const owner = await db.query.users.findFirst({
|
const owner = await db.query.users.findFirst({
|
||||||
@@ -112,7 +113,7 @@ export async function GET(request: Request, context: RouteContext) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.json({ user: userResponse });
|
return NextResponse.json({ user: userResponse });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Get user error:', error);
|
console.error('Get user error:', error);
|
||||||
|
|||||||
@@ -257,17 +257,17 @@ async function deliverSwarmInteraction(
|
|||||||
payload: unknown
|
payload: unknown
|
||||||
): Promise<SwarmInteractionResponse> {
|
): Promise<SwarmInteractionResponse> {
|
||||||
try {
|
try {
|
||||||
const baseUrl = targetDomain.startsWith('http')
|
const baseUrl = targetDomain.startsWith('http')
|
||||||
? targetDomain
|
? targetDomain
|
||||||
: targetDomain.startsWith('localhost') || targetDomain.startsWith('127.0.0.1')
|
: targetDomain.startsWith('localhost') || targetDomain.startsWith('127.0.0.1')
|
||||||
? `http://${targetDomain}`
|
? `http://${targetDomain}`
|
||||||
: `https://${targetDomain}`;
|
: `https://${targetDomain}`;
|
||||||
|
|
||||||
const url = `${baseUrl}${endpoint}`;
|
const url = `${baseUrl}${endpoint}`;
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeout = setTimeout(() => controller.abort(), 10000); // 10s timeout
|
const timeout = setTimeout(() => controller.abort(), 10000); // 10s timeout
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -277,9 +277,9 @@ async function deliverSwarmInteraction(
|
|||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text().catch(() => 'Unknown error');
|
const errorText = await response.text().catch(() => 'Unknown error');
|
||||||
return {
|
return {
|
||||||
@@ -287,7 +287,7 @@ async function deliverSwarmInteraction(
|
|||||||
error: `HTTP ${response.status}: ${errorText}`,
|
error: `HTTP ${response.status}: ${errorText}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -318,7 +318,9 @@ export interface SwarmUserProfile {
|
|||||||
postsCount: number;
|
postsCount: number;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
isBot?: boolean;
|
isBot?: boolean;
|
||||||
|
botOwnerHandle?: string; // Handle of the bot's owner (e.g., "user" or "user@domain")
|
||||||
nodeDomain: string;
|
nodeDomain: string;
|
||||||
|
chatPublicKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SwarmUserPost {
|
export interface SwarmUserPost {
|
||||||
@@ -357,23 +359,23 @@ export async function fetchSwarmUserProfile(
|
|||||||
: domain.startsWith('localhost') || domain.startsWith('127.0.0.1')
|
: domain.startsWith('localhost') || domain.startsWith('127.0.0.1')
|
||||||
? `http://${domain}`
|
? `http://${domain}`
|
||||||
: `https://${domain}`;
|
: `https://${domain}`;
|
||||||
|
|
||||||
const url = `${baseUrl}/api/swarm/users/${handle}?limit=${postsLimit}`;
|
const url = `${baseUrl}/api/swarm/users/${handle}?limit=${postsLimit}`;
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeout = setTimeout(() => controller.abort(), 10000);
|
const timeout = setTimeout(() => controller.abort(), 10000);
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
headers: { 'Accept': 'application/json' },
|
headers: { 'Accept': 'application/json' },
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await response.json();
|
return await response.json();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Swarm] Failed to fetch profile for ${handle}@${domain}:`, error);
|
console.error(`[Swarm] Failed to fetch profile for ${handle}@${domain}:`, error);
|
||||||
@@ -393,7 +395,7 @@ export async function cacheSwarmUserPosts(
|
|||||||
): Promise<{ cached: number; skipped: number }> {
|
): Promise<{ cached: number; skipped: number }> {
|
||||||
try {
|
try {
|
||||||
const profileData = await fetchSwarmUserProfile(handle, domain, limit);
|
const profileData = await fetchSwarmUserProfile(handle, domain, limit);
|
||||||
|
|
||||||
if (!profileData || !profileData.posts) {
|
if (!profileData || !profileData.posts) {
|
||||||
return { cached: 0, skipped: 0 };
|
return { cached: 0, skipped: 0 };
|
||||||
}
|
}
|
||||||
@@ -464,23 +466,23 @@ export async function fetchSwarmPost(
|
|||||||
: domain.startsWith('localhost') || domain.startsWith('127.0.0.1')
|
: domain.startsWith('localhost') || domain.startsWith('127.0.0.1')
|
||||||
? `http://${domain}`
|
? `http://${domain}`
|
||||||
: `https://${domain}`;
|
: `https://${domain}`;
|
||||||
|
|
||||||
const url = `${baseUrl}/api/swarm/posts/${postId}`;
|
const url = `${baseUrl}/api/swarm/posts/${postId}`;
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeout = setTimeout(() => controller.abort(), 10000);
|
const timeout = setTimeout(() => controller.abort(), 10000);
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
headers: { 'Accept': 'application/json' },
|
headers: { 'Accept': 'application/json' },
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await response.json();
|
return await response.json();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Swarm] Failed to fetch post ${postId} from ${domain}:`, error);
|
console.error(`[Swarm] Failed to fetch post ${postId} from ${domain}:`, error);
|
||||||
@@ -500,7 +502,7 @@ export function extractMentions(content: string): { handle: string; domain: stri
|
|||||||
// Match @handle or @handle@domain patterns
|
// Match @handle or @handle@domain patterns
|
||||||
const mentionRegex = /@([a-zA-Z0-9_]+)(?:@([a-zA-Z0-9.-]+))?/g;
|
const mentionRegex = /@([a-zA-Z0-9_]+)(?:@([a-zA-Z0-9.-]+))?/g;
|
||||||
const mentions: { handle: string; domain: string | null }[] = [];
|
const mentions: { handle: string; domain: string | null }[] = [];
|
||||||
|
|
||||||
let match;
|
let match;
|
||||||
while ((match = mentionRegex.exec(content)) !== null) {
|
while ((match = mentionRegex.exec(content)) !== null) {
|
||||||
mentions.push({
|
mentions.push({
|
||||||
@@ -508,7 +510,7 @@ export function extractMentions(content: string): { handle: string; domain: stri
|
|||||||
domain: match[2]?.toLowerCase() || null,
|
domain: match[2]?.toLowerCase() || null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return mentions;
|
return mentions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,15 +530,15 @@ export async function deliverSwarmMentions(
|
|||||||
const mentions = extractMentions(content);
|
const mentions = extractMentions(content);
|
||||||
let delivered = 0;
|
let delivered = 0;
|
||||||
let failed = 0;
|
let failed = 0;
|
||||||
|
|
||||||
for (const mention of mentions) {
|
for (const mention of mentions) {
|
||||||
// Skip local mentions (no domain)
|
// Skip local mentions (no domain)
|
||||||
if (!mention.domain) continue;
|
if (!mention.domain) continue;
|
||||||
|
|
||||||
// Check if it's a swarm node
|
// Check if it's a swarm node
|
||||||
const isSwarm = await isSwarmNode(mention.domain);
|
const isSwarm = await isSwarmNode(mention.domain);
|
||||||
if (!isSwarm) continue;
|
if (!isSwarm) continue;
|
||||||
|
|
||||||
// Deliver the mention
|
// Deliver the mention
|
||||||
const result = await deliverSwarmMention(mention.domain, {
|
const result = await deliverSwarmMention(mention.domain, {
|
||||||
mentionedHandle: mention.handle,
|
mentionedHandle: mention.handle,
|
||||||
@@ -551,14 +553,14 @@ export async function deliverSwarmMentions(
|
|||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
delivered++;
|
delivered++;
|
||||||
} else {
|
} else {
|
||||||
failed++;
|
failed++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { delivered, failed };
|
return { delivered, failed };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -619,7 +621,7 @@ export async function getSwarmFollowerDomains(userId: string): Promise<string[]>
|
|||||||
|
|
||||||
// Filter for swarm followers (actorUrl starts with swarm://)
|
// Filter for swarm followers (actorUrl starts with swarm://)
|
||||||
const swarmFollowers = followers.filter(f => f.actorUrl.startsWith('swarm://'));
|
const swarmFollowers = followers.filter(f => f.actorUrl.startsWith('swarm://'));
|
||||||
|
|
||||||
// Extract unique domains
|
// Extract unique domains
|
||||||
const domains = swarmFollowers.map(f => {
|
const domains = swarmFollowers.map(f => {
|
||||||
const match = f.actorUrl.match(/^swarm:\/\/([^\/]+)/);
|
const match = f.actorUrl.match(/^swarm:\/\/([^\/]+)/);
|
||||||
@@ -660,7 +662,7 @@ export async function deliverPostToSwarmFollowers(
|
|||||||
nodeDomain: string
|
nodeDomain: string
|
||||||
): Promise<{ delivered: number; failed: number }> {
|
): Promise<{ delivered: number; failed: number }> {
|
||||||
const swarmDomains = await getSwarmFollowerDomains(userId);
|
const swarmDomains = await getSwarmFollowerDomains(userId);
|
||||||
|
|
||||||
if (swarmDomains.length === 0) {
|
if (swarmDomains.length === 0) {
|
||||||
return { delivered: 0, failed: 0 };
|
return { delivered: 0, failed: 0 };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user