Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/components/CreatorMarketplace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ const CreatorMarketplace: React.FC<CreatorMarketplaceProps> = ({
src={match.creator.avatarUrl}
alt={match.creator.name}
className="h-10 w-10 rounded-full object-cover"
loading="lazy"
decoding="async"
/>
) : (
<div className="h-10 w-10 rounded-full bg-indigo-100 flex items-center justify-center text-indigo-700 font-medium">
Expand Down
2 changes: 2 additions & 0 deletions src/components/PresenceBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ function Avatar({ presence, isCurrentUser }: AvatarProps) {
src={presence.avatarUrl}
alt={presence.displayName}
className="w-full h-full rounded-full object-cover"
loading="lazy"
decoding="async"
/>
) : (
initials
Expand Down
52 changes: 39 additions & 13 deletions src/lib/stellar/horizonServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,45 @@ export async function fetchInvoicesForAddress(
const startId = cursor ? parseInt(cursor, 10) + 1 : 1;

const results: Invoice[] = [];
const maxId = startId + limit * 10;
// Fetch ids in parallel batches instead of one-by-one sequential awaits:
// each `getInvoice` is an independent RPC/cache call, so awaiting them
// serially in a for-loop (the previous behavior) multiplies round-trip
// latency by the number of ids scanned. Batching with Promise.all lets
// the cache/RPC calls for a batch fly concurrently.
const BATCH_SIZE = 10;
let reachedEnd = false;

for (let id = startId; results.length < limit; id++) {
if (id > startId + limit * 10) break;
for (let batchStart = startId; results.length < limit && batchStart <= maxId && !reachedEnd; batchStart += BATCH_SIZE) {
const batchIds = Array.from(
{ length: Math.min(BATCH_SIZE, maxId - batchStart + 1) },
(_, i) => batchStart + i
);

const batchResults = await Promise.all(
batchIds.map(async (id) => {
try {
// Cached: the dashboard/infinite-scroll list re-scans the same id
// range on every page load and poll, so this collapses repeat RPC
// calls for invoices that haven't changed in the last few seconds.
const inv = await blockchainCache.getOrFetch("invoice", String(id), () =>
splitClient.getInvoice(String(id))
);
return { id, inv, ended: false };
} catch {
// splitClient throws when the invoice id does not exist — end of list.
return { id, inv: null, ended: true };
}
})
);

for (const { inv, ended } of batchResults) {
if (ended) {
reachedEnd = true;
break;
}
if (!inv) continue;

try {
// Cached: the dashboard/infinite-scroll list re-scans the same id
// range on every page load and poll, so this collapses repeat RPC
// calls for invoices that haven't changed in the last few seconds.
const inv = await blockchainCache.getOrFetch("invoice", String(id), () =>
splitClient.getInvoice(String(id))
);
const mine =
inv.creator === publicKey || inv.recipients.some((r) => r.address === publicKey);

Expand All @@ -60,12 +88,10 @@ export async function fetchInvoicesForAddress(
results.push(inv);
}
}
} catch {
// splitClient throws when the invoice id does not exist — end of list.
return { invoices: results, nextCursor: null };
if (results.length >= limit) break;
}
}

const nextCursor = results.length === limit ? String(results[results.length - 1].id) : null;
return { invoices: results, nextCursor };
return { invoices: results.slice(0, limit), nextCursor };
}
Loading