c.get('pubkey') -> await c.get('signer')?.getPublicKey()

This commit is contained in:
Alex Gleason 2024-05-14 11:57:03 -05:00
parent c5fbe69b80
commit c715827c81
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
14 changed files with 81 additions and 46 deletions

View File

@ -29,7 +29,7 @@ const createAccountSchema = z.object({
}); });
const createAccountController: AppController = async (c) => { const createAccountController: AppController = async (c) => {
const pubkey = c.get('pubkey')!; const pubkey = await c.get('signer')?.getPublicKey()!;
const result = createAccountSchema.safeParse(await c.req.json()); const result = createAccountSchema.safeParse(await c.req.json());
if (!result.success) { if (!result.success) {
@ -45,7 +45,7 @@ const createAccountController: AppController = async (c) => {
}; };
const verifyCredentialsController: AppController = async (c) => { const verifyCredentialsController: AppController = async (c) => {
const pubkey = c.get('pubkey')!; const pubkey = await c.get('signer')?.getPublicKey()!;
const event = await getAuthor(pubkey, { relations: ['author_stats'] }); const event = await getAuthor(pubkey, { relations: ['author_stats'] });
if (event) { if (event) {
@ -122,7 +122,7 @@ const accountSearchController: AppController = async (c) => {
}; };
const relationshipsController: AppController = async (c) => { const relationshipsController: AppController = async (c) => {
const pubkey = c.get('pubkey')!; const pubkey = await c.get('signer')?.getPublicKey()!;
const ids = z.array(z.string()).safeParse(c.req.queries('id[]')); const ids = z.array(z.string()).safeParse(c.req.queries('id[]'));
if (!ids.success) { if (!ids.success) {
@ -178,7 +178,11 @@ const accountStatusesController: AppController = async (c) => {
return events; return events;
}); });
const statuses = await Promise.all(events.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') }))); const viewerPubkey = await c.get('signer')?.getPublicKey();
const statuses = await Promise.all(
events.map((event) => renderStatus(event, { viewerPubkey })),
);
return paginated(c, events, statuses); return paginated(c, events, statuses);
}; };
@ -194,7 +198,7 @@ const updateCredentialsSchema = z.object({
}); });
const updateCredentialsController: AppController = async (c) => { const updateCredentialsController: AppController = async (c) => {
const pubkey = c.get('pubkey')!; const pubkey = await c.get('signer')?.getPublicKey()!;
const body = await parseBody(c.req.raw); const body = await parseBody(c.req.raw);
const result = updateCredentialsSchema.safeParse(body); const result = updateCredentialsSchema.safeParse(body);
@ -236,7 +240,7 @@ const updateCredentialsController: AppController = async (c) => {
/** https://docs.joinmastodon.org/methods/accounts/#follow */ /** https://docs.joinmastodon.org/methods/accounts/#follow */
const followController: AppController = async (c) => { const followController: AppController = async (c) => {
const sourcePubkey = c.get('pubkey')!; const sourcePubkey = await c.get('signer')?.getPublicKey()!;
const targetPubkey = c.req.param('pubkey'); const targetPubkey = c.req.param('pubkey');
await updateListEvent( await updateListEvent(
@ -253,7 +257,7 @@ const followController: AppController = async (c) => {
/** https://docs.joinmastodon.org/methods/accounts/#unfollow */ /** https://docs.joinmastodon.org/methods/accounts/#unfollow */
const unfollowController: AppController = async (c) => { const unfollowController: AppController = async (c) => {
const sourcePubkey = c.get('pubkey')!; const sourcePubkey = await c.get('signer')?.getPublicKey()!;
const targetPubkey = c.req.param('pubkey'); const targetPubkey = c.req.param('pubkey');
await updateListEvent( await updateListEvent(
@ -290,7 +294,7 @@ const unblockController: AppController = (c) => {
/** https://docs.joinmastodon.org/methods/accounts/#mute */ /** https://docs.joinmastodon.org/methods/accounts/#mute */
const muteController: AppController = async (c) => { const muteController: AppController = async (c) => {
const sourcePubkey = c.get('pubkey')!; const sourcePubkey = await c.get('signer')?.getPublicKey()!;
const targetPubkey = c.req.param('pubkey'); const targetPubkey = c.req.param('pubkey');
await updateListEvent( await updateListEvent(
@ -305,7 +309,7 @@ const muteController: AppController = async (c) => {
/** https://docs.joinmastodon.org/methods/accounts/#unmute */ /** https://docs.joinmastodon.org/methods/accounts/#unmute */
const unmuteController: AppController = async (c) => { const unmuteController: AppController = async (c) => {
const sourcePubkey = c.get('pubkey')!; const sourcePubkey = await c.get('signer')?.getPublicKey()!;
const targetPubkey = c.req.param('pubkey'); const targetPubkey = c.req.param('pubkey');
await updateListEvent( await updateListEvent(
@ -319,7 +323,7 @@ const unmuteController: AppController = async (c) => {
}; };
const favouritesController: AppController = async (c) => { const favouritesController: AppController = async (c) => {
const pubkey = c.get('pubkey')!; const pubkey = await c.get('signer')?.getPublicKey()!;
const params = paginationSchema.parse(c.req.query()); const params = paginationSchema.parse(c.req.query());
const { signal } = c.req.raw; const { signal } = c.req.raw;
@ -335,7 +339,11 @@ const favouritesController: AppController = async (c) => {
const events1 = await Storages.db.query([{ kinds: [1], ids }], { signal }) const events1 = await Storages.db.query([{ kinds: [1], ids }], { signal })
.then((events) => hydrateEvents({ events, storage: Storages.db, signal })); .then((events) => hydrateEvents({ events, storage: Storages.db, signal }));
const statuses = await Promise.all(events1.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') }))); const viewerPubkey = await c.get('signer')?.getPublicKey();
const statuses = await Promise.all(
events1.map((event) => renderStatus(event, { viewerPubkey })),
);
return paginated(c, events1, statuses); return paginated(c, events1, statuses);
}; };

View File

@ -5,7 +5,7 @@ import { renderStatuses } from '@/views.ts';
/** https://docs.joinmastodon.org/methods/bookmarks/#get */ /** https://docs.joinmastodon.org/methods/bookmarks/#get */
const bookmarksController: AppController = async (c) => { const bookmarksController: AppController = async (c) => {
const pubkey = c.get('pubkey')!; const pubkey = await c.get('signer')?.getPublicKey()!;
const { signal } = c.req.raw; const { signal } = c.req.raw;
const [event10003] = await Storages.db.query( const [event10003] = await Storages.db.query(

View File

@ -14,7 +14,7 @@ interface Marker {
} }
export const markersController: AppController = async (c) => { export const markersController: AppController = async (c) => {
const pubkey = c.get('pubkey')!; const pubkey = await c.get('signer')?.getPublicKey()!;
const timelines = c.req.queries('timeline[]') ?? []; const timelines = c.req.queries('timeline[]') ?? [];
const results = await kv.getMany<Marker[]>( const results = await kv.getMany<Marker[]>(
@ -37,7 +37,7 @@ const markerDataSchema = z.object({
}); });
export const updateMarkersController: AppController = async (c) => { export const updateMarkersController: AppController = async (c) => {
const pubkey = c.get('pubkey')!; const pubkey = await c.get('signer')?.getPublicKey()!;
const record = z.record(z.enum(['home', 'notifications']), markerDataSchema).parse(await parseBody(c.req.raw)); const record = z.record(z.enum(['home', 'notifications']), markerDataSchema).parse(await parseBody(c.req.raw));
const timelines = Object.keys(record) as Timeline[]; const timelines = Object.keys(record) as Timeline[];

View File

@ -14,7 +14,7 @@ const mediaBodySchema = z.object({
}); });
const mediaController: AppController = async (c) => { const mediaController: AppController = async (c) => {
const pubkey = c.get('pubkey')!; const pubkey = await c.get('signer')?.getPublicKey()!;
const result = mediaBodySchema.safeParse(await parseBody(c.req.raw)); const result = mediaBodySchema.safeParse(await parseBody(c.req.raw));
const { signal } = c.req.raw; const { signal } = c.req.raw;

View File

@ -5,7 +5,7 @@ import { renderAccounts } from '@/views.ts';
/** https://docs.joinmastodon.org/methods/mutes/#get */ /** https://docs.joinmastodon.org/methods/mutes/#get */
const mutesController: AppController = async (c) => { const mutesController: AppController = async (c) => {
const pubkey = c.get('pubkey')!; const pubkey = await c.get('signer')?.getPublicKey()!;
const { signal } = c.req.raw; const { signal } = c.req.raw;
const [event10000] = await Storages.db.query( const [event10000] = await Storages.db.query(

View File

@ -5,8 +5,8 @@ import { hydrateEvents } from '@/storages/hydrate.ts';
import { paginated, paginationSchema } from '@/utils/api.ts'; import { paginated, paginationSchema } from '@/utils/api.ts';
import { renderNotification } from '@/views/mastodon/notifications.ts'; import { renderNotification } from '@/views/mastodon/notifications.ts';
const notificationsController: AppController = (c) => { const notificationsController: AppController = async (c) => {
const pubkey = c.get('pubkey')!; const pubkey = await c.get('signer')?.getPublicKey()!;
const { since, until } = paginationSchema.parse(c.req.query()); const { since, until } = paginationSchema.parse(c.req.query());
return renderNotifications(c, [{ kinds: [1, 6, 7], '#p': [pubkey], since, until }]); return renderNotifications(c, [{ kinds: [1, 6, 7], '#p': [pubkey], since, until }]);
@ -14,7 +14,7 @@ const notificationsController: AppController = (c) => {
async function renderNotifications(c: AppContext, filters: NostrFilter[]) { async function renderNotifications(c: AppContext, filters: NostrFilter[]) {
const store = c.get('store'); const store = c.get('store');
const pubkey = c.get('pubkey')!; const pubkey = await c.get('signer')?.getPublicKey()!;
const { signal } = c.req.raw; const { signal } = c.req.raw;
const events = await store const events = await store

View File

@ -55,9 +55,15 @@ const reportController: AppController = async (c) => {
/** https://docs.joinmastodon.org/methods/admin/reports/#get */ /** https://docs.joinmastodon.org/methods/admin/reports/#get */
const adminReportsController: AppController = async (c) => { const adminReportsController: AppController = async (c) => {
const store = c.get('store'); const store = c.get('store');
const viewerPubkey = await c.get('signer')?.getPublicKey();
const reports = await store.query([{ kinds: [1984], '#P': [Conf.pubkey] }]) const reports = await store.query([{ kinds: [1984], '#P': [Conf.pubkey] }])
.then((events) => hydrateEvents({ storage: store, events: events, signal: c.req.raw.signal })) .then((events) => hydrateEvents({ storage: store, events: events, signal: c.req.raw.signal }))
.then((events) => Promise.all(events.map((event) => renderAdminReport(event, { viewerPubkey: c.get('pubkey') })))); .then((events) =>
Promise.all(
events.map((event) => renderAdminReport(event, { viewerPubkey })),
)
);
return c.json(reports); return c.json(reports);
}; };
@ -67,7 +73,7 @@ const adminReportController: AppController = async (c) => {
const eventId = c.req.param('id'); const eventId = c.req.param('id');
const { signal } = c.req.raw; const { signal } = c.req.raw;
const store = c.get('store'); const store = c.get('store');
const pubkey = c.get('pubkey'); const pubkey = await c.get('signer')?.getPublicKey();
const [event] = await store.query([{ const [event] = await store.query([{
kinds: [1984], kinds: [1984],
@ -89,7 +95,7 @@ const adminReportResolveController: AppController = async (c) => {
const eventId = c.req.param('id'); const eventId = c.req.param('id');
const { signal } = c.req.raw; const { signal } = c.req.raw;
const store = c.get('store'); const store = c.get('store');
const pubkey = c.get('pubkey'); const pubkey = await c.get('signer')?.getPublicKey();
const [event] = await store.query([{ const [event] = await store.query([{
kinds: [1984], kinds: [1984],

View File

@ -43,6 +43,7 @@ const searchController: AppController = async (c) => {
} }
const results = dedupeEvents(events); const results = dedupeEvents(events);
const viewerPubkey = await c.get('signer')?.getPublicKey();
const [accounts, statuses] = await Promise.all([ const [accounts, statuses] = await Promise.all([
Promise.all( Promise.all(
@ -54,7 +55,7 @@ const searchController: AppController = async (c) => {
Promise.all( Promise.all(
results results
.filter((event) => event.kind === 1) .filter((event) => event.kind === 1)
.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') })) .map((event) => renderStatus(event, { viewerPubkey }))
.filter(Boolean), .filter(Boolean),
), ),
]); ]);

View File

@ -47,7 +47,7 @@ const statusController: AppController = async (c) => {
}); });
if (event) { if (event) {
return c.json(await renderStatus(event, { viewerPubkey: c.get('pubkey') })); return c.json(await renderStatus(event, { viewerPubkey: await c.get('signer')?.getPublicKey() }));
} }
return c.json({ error: 'Event not found.' }, 404); return c.json({ error: 'Event not found.' }, 404);
@ -89,9 +89,11 @@ const createStatusController: AppController = async (c) => {
tags.push(['subject', data.spoiler_text]); tags.push(['subject', data.spoiler_text]);
} }
const viewerPubkey = await c.get('signer')?.getPublicKey();
if (data.media_ids?.length) { if (data.media_ids?.length) {
const media = await getUnattachedMediaByIds(data.media_ids) const media = await getUnattachedMediaByIds(data.media_ids)
.then((media) => media.filter(({ pubkey }) => pubkey === c.get('pubkey'))) .then((media) => media.filter(({ pubkey }) => pubkey === viewerPubkey))
.then((media) => media.map(({ url, data }) => ['media', url, data])); .then((media) => media.map(({ url, data }) => ['media', url, data]));
tags.push(...media); tags.push(...media);
@ -143,12 +145,12 @@ const createStatusController: AppController = async (c) => {
}); });
} }
return c.json(await renderStatus({ ...event, author }, { viewerPubkey: c.get('pubkey') })); return c.json(await renderStatus({ ...event, author }, { viewerPubkey: await c.get('signer')?.getPublicKey() }));
}; };
const deleteStatusController: AppController = async (c) => { const deleteStatusController: AppController = async (c) => {
const id = c.req.param('id'); const id = c.req.param('id');
const pubkey = c.get('pubkey'); const pubkey = await c.get('signer')?.getPublicKey();
const event = await getEvent(id, { signal: c.req.raw.signal }); const event = await getEvent(id, { signal: c.req.raw.signal });
@ -172,9 +174,12 @@ const deleteStatusController: AppController = async (c) => {
const contextController: AppController = async (c) => { const contextController: AppController = async (c) => {
const id = c.req.param('id'); const id = c.req.param('id');
const event = await getEvent(id, { kind: 1, relations: ['author', 'event_stats', 'author_stats'] }); const event = await getEvent(id, { kind: 1, relations: ['author', 'event_stats', 'author_stats'] });
const viewerPubkey = await c.get('signer')?.getPublicKey();
async function renderStatuses(events: NostrEvent[]) { async function renderStatuses(events: NostrEvent[]) {
const statuses = await Promise.all(events.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') }))); const statuses = await Promise.all(
events.map((event) => renderStatus(event, { viewerPubkey })),
);
return statuses.filter(Boolean); return statuses.filter(Boolean);
} }
@ -204,7 +209,7 @@ const favouriteController: AppController = async (c) => {
], ],
}, c); }, c);
const status = await renderStatus(target, { viewerPubkey: c.get('pubkey') }); const status = await renderStatus(target, { viewerPubkey: await c.get('signer')?.getPublicKey() });
if (status) { if (status) {
status.favourited = true; status.favourited = true;
@ -247,7 +252,7 @@ const reblogStatusController: AppController = async (c) => {
signal: signal, signal: signal,
}); });
const status = await renderReblog(reblogEvent, { viewerPubkey: c.get('pubkey') }); const status = await renderReblog(reblogEvent, { viewerPubkey: await c.get('signer')?.getPublicKey() });
return c.json(status); return c.json(status);
}; };
@ -255,7 +260,7 @@ const reblogStatusController: AppController = async (c) => {
/** https://docs.joinmastodon.org/methods/statuses/#unreblog */ /** https://docs.joinmastodon.org/methods/statuses/#unreblog */
const unreblogStatusController: AppController = async (c) => { const unreblogStatusController: AppController = async (c) => {
const eventId = c.req.param('id'); const eventId = c.req.param('id');
const pubkey = c.get('pubkey') as string; const pubkey = await c.get('signer')?.getPublicKey() as string;
const event = await getEvent(eventId, { const event = await getEvent(eventId, {
kind: 1, kind: 1,
@ -282,7 +287,7 @@ const rebloggedByController: AppController = (c) => {
/** https://docs.joinmastodon.org/methods/statuses/#bookmark */ /** https://docs.joinmastodon.org/methods/statuses/#bookmark */
const bookmarkController: AppController = async (c) => { const bookmarkController: AppController = async (c) => {
const pubkey = c.get('pubkey')!; const pubkey = await c.get('signer')?.getPublicKey()!;
const eventId = c.req.param('id'); const eventId = c.req.param('id');
const event = await getEvent(eventId, { const event = await getEvent(eventId, {
@ -309,7 +314,7 @@ const bookmarkController: AppController = async (c) => {
/** https://docs.joinmastodon.org/methods/statuses/#unbookmark */ /** https://docs.joinmastodon.org/methods/statuses/#unbookmark */
const unbookmarkController: AppController = async (c) => { const unbookmarkController: AppController = async (c) => {
const pubkey = c.get('pubkey')!; const pubkey = await c.get('signer')?.getPublicKey()!;
const eventId = c.req.param('id'); const eventId = c.req.param('id');
const event = await getEvent(eventId, { const event = await getEvent(eventId, {
@ -336,7 +341,7 @@ const unbookmarkController: AppController = async (c) => {
/** https://docs.joinmastodon.org/methods/statuses/#pin */ /** https://docs.joinmastodon.org/methods/statuses/#pin */
const pinController: AppController = async (c) => { const pinController: AppController = async (c) => {
const pubkey = c.get('pubkey')!; const pubkey = await c.get('signer')?.getPublicKey()!;
const eventId = c.req.param('id'); const eventId = c.req.param('id');
const event = await getEvent(eventId, { const event = await getEvent(eventId, {
@ -363,7 +368,7 @@ const pinController: AppController = async (c) => {
/** https://docs.joinmastodon.org/methods/statuses/#unpin */ /** https://docs.joinmastodon.org/methods/statuses/#unpin */
const unpinController: AppController = async (c) => { const unpinController: AppController = async (c) => {
const pubkey = c.get('pubkey')!; const pubkey = await c.get('signer')?.getPublicKey()!;
const eventId = c.req.param('id'); const eventId = c.req.param('id');
const { signal } = c.req.raw; const { signal } = c.req.raw;
@ -423,7 +428,7 @@ const zapController: AppController = async (c) => {
], ],
}, c); }, c);
const status = await renderStatus(target, { viewerPubkey: c.get('pubkey') }); const status = await renderStatus(target, { viewerPubkey: await c.get('signer')?.getPublicKey() });
status.zapped = true; status.zapped = true;
return c.json(status); return c.json(status);

View File

@ -11,7 +11,7 @@ import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts';
const homeTimelineController: AppController = async (c) => { const homeTimelineController: AppController = async (c) => {
const params = paginationSchema.parse(c.req.query()); const params = paginationSchema.parse(c.req.query());
const pubkey = c.get('pubkey')!; const pubkey = await c.get('signer')?.getPublicKey()!;
const authors = await getFeedPubkeys(pubkey); const authors = await getFeedPubkeys(pubkey);
return renderStatuses(c, [{ authors, kinds: [1, 6], ...params }]); return renderStatuses(c, [{ authors, kinds: [1, 6], ...params }]);
}; };
@ -61,11 +61,13 @@ async function renderStatuses(c: AppContext, filters: NostrFilter[]) {
return c.json([]); return c.json([]);
} }
const viewerPubkey = await c.get('signer')?.getPublicKey();
const statuses = (await Promise.all(events.map((event) => { const statuses = (await Promise.all(events.map((event) => {
if (event.kind === 6) { if (event.kind === 6) {
return renderReblog(event, { viewerPubkey: c.get('pubkey') }); return renderReblog(event, { viewerPubkey });
} }
return renderStatus(event, { viewerPubkey: c.get('pubkey') }); return renderStatus(event, { viewerPubkey });
}))).filter((boolean) => boolean); }))).filter((boolean) => boolean);
if (!statuses.length) { if (!statuses.length) {

View File

@ -8,7 +8,6 @@ import {
validateAuthEvent, validateAuthEvent,
} from '@/utils/nip98.ts'; } from '@/utils/nip98.ts';
import { localRequest } from '@/utils/api.ts'; import { localRequest } from '@/utils/api.ts';
import { APISigner } from '@/signers/APISigner.ts';
import { findUser, User } from '@/db/users.ts'; import { findUser, User } from '@/db/users.ts';
/** /**
@ -70,7 +69,7 @@ function withProof(
opts?: ParseAuthRequestOpts, opts?: ParseAuthRequestOpts,
): AppMiddleware { ): AppMiddleware {
return async (c, next) => { return async (c, next) => {
const pubkey = c.get('pubkey'); const pubkey = await c.get('signer')?.getPublicKey();
const proof = c.get('proof') || await obtainProof(c, opts); const proof = c.get('proof') || await obtainProof(c, opts);
// Prevent people from accidentally using the wrong account. This has no other security implications. // Prevent people from accidentally using the wrong account. This has no other security implications.
@ -90,9 +89,16 @@ function withProof(
/** Get the proof over Nostr Connect. */ /** Get the proof over Nostr Connect. */
async function obtainProof(c: AppContext, opts?: ParseAuthRequestOpts) { async function obtainProof(c: AppContext, opts?: ParseAuthRequestOpts) {
const signer = c.get('signer');
if (!signer) {
throw new HTTPException(401, {
res: c.json({ error: 'No way to sign Nostr event' }, 401),
});
}
const req = localRequest(c); const req = localRequest(c);
const reqEvent = await buildAuthEventTemplate(req, opts); const reqEvent = await buildAuthEventTemplate(req, opts);
const resEvent = await new APISigner(c).signEvent(reqEvent); const resEvent = await signer.signEvent(reqEvent);
const result = await validateAuthEvent(req, resEvent, opts); const result = await validateAuthEvent(req, resEvent, opts);
if (result.success) { if (result.success) {

View File

@ -4,7 +4,7 @@ import { Storages } from '@/storages.ts';
/** Store middleware. */ /** Store middleware. */
const storeMiddleware: AppMiddleware = async (c, next) => { const storeMiddleware: AppMiddleware = async (c, next) => {
const pubkey = c.get('pubkey'); const pubkey = await c.get('signer')?.getPublicKey();
if (pubkey) { if (pubkey) {
const store = new UserStore(pubkey, Storages.admin); const store = new UserStore(pubkey, Storages.admin);

View File

@ -10,7 +10,6 @@ import { type AppContext } from '@/app.ts';
import { Conf } from '@/config.ts'; import { Conf } from '@/config.ts';
import * as pipeline from '@/pipeline.ts'; import * as pipeline from '@/pipeline.ts';
import { AdminSigner } from '@/signers/AdminSigner.ts'; import { AdminSigner } from '@/signers/AdminSigner.ts';
import { APISigner } from '@/signers/APISigner.ts';
import { Storages } from '@/storages.ts'; import { Storages } from '@/storages.ts';
import { nostrNow } from '@/utils.ts'; import { nostrNow } from '@/utils.ts';
@ -21,7 +20,13 @@ type EventStub = TypeFest.SetOptional<EventTemplate, 'content' | 'created_at' |
/** Publish an event through the pipeline. */ /** Publish an event through the pipeline. */
async function createEvent(t: EventStub, c: AppContext): Promise<NostrEvent> { async function createEvent(t: EventStub, c: AppContext): Promise<NostrEvent> {
const signer = new APISigner(c); const signer = c.get('signer');
if (!signer) {
throw new HTTPException(401, {
res: c.json({ error: 'No way to sign Nostr event' }, 401),
});
}
const event = await signer.signEvent({ const event = await signer.signEvent({
content: '', content: '',

View File

@ -59,8 +59,10 @@ async function renderStatuses(c: AppContext, ids: string[], signal = AbortSignal
const sortedEvents = [...events].sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id)); const sortedEvents = [...events].sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id));
const viewerPubkey = await c.get('signer')?.getPublicKey();
const statuses = await Promise.all( const statuses = await Promise.all(
sortedEvents.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') })), sortedEvents.map((event) => renderStatus(event, { viewerPubkey })),
); );
// TODO: pagination with min_id and max_id based on the order of `ids`. // TODO: pagination with min_id and max_id based on the order of `ids`.