ditto/src/sign.ts

72 lines
2.2 KiB
TypeScript
Raw Normal View History

2023-04-30 02:48:22 +00:00
import { type AppContext } from '@/app.ts';
2023-08-17 02:53:51 +00:00
import { type Event, type EventTemplate, getEventHash, getPublicKey, getSignature, HTTPException, z } from '@/deps.ts';
2023-08-12 16:28:16 +00:00
import { signedEventSchema } from '@/schemas/nostr.ts';
2023-05-21 04:47:31 +00:00
import { ws } from '@/stream.ts';
2023-04-30 02:48:22 +00:00
2023-05-14 00:46:47 +00:00
/** Get signing WebSocket from app context. */
function getSignStream(c: AppContext): WebSocket | undefined {
2023-05-21 00:39:05 +00:00
const pubkey = c.get('pubkey');
const session = c.get('session');
if (pubkey && session) {
const [socket] = ws.getSockets(`nostr:${pubkey}:${session}`);
return socket;
}
2023-05-14 00:46:47 +00:00
}
const nostrStreamingEventSchema = z.object({
type: z.literal('nostr.sign'),
2023-07-08 23:41:11 +00:00
data: signedEventSchema,
});
2023-05-14 01:16:44 +00:00
/**
* Sign Nostr event using the app context.
*
* - If a secret key is provided, it will be used to sign the event.
* - If a signing WebSocket is provided, it will be used to sign the event.
*/
2023-08-17 02:53:51 +00:00
async function signEvent<K extends number = number>(event: EventTemplate<K>, c: AppContext): Promise<Event<K>> {
const seckey = c.get('seckey');
2023-05-14 00:46:47 +00:00
const stream = getSignStream(c);
2023-05-14 01:16:44 +00:00
if (!seckey && stream) {
2023-05-14 00:46:47 +00:00
try {
2023-08-17 02:53:51 +00:00
return await new Promise<Event<K>>((resolve, reject) => {
2023-05-21 00:39:05 +00:00
const handleMessage = (e: MessageEvent) => {
try {
const { data: event } = nostrStreamingEventSchema.parse(JSON.parse(e.data));
2023-05-21 00:39:05 +00:00
stream.removeEventListener('message', handleMessage);
2023-08-17 02:53:51 +00:00
resolve(event as Event<K>);
} catch (_e) {
//
2023-05-14 00:46:47 +00:00
}
2023-05-21 00:39:05 +00:00
};
stream.addEventListener('message', handleMessage);
2023-05-14 00:46:47 +00:00
stream.send(JSON.stringify({ event: 'nostr.sign', payload: JSON.stringify(event) }));
2023-05-21 00:39:05 +00:00
setTimeout(() => {
stream.removeEventListener('message', handleMessage);
reject();
}, 60000);
2023-05-14 00:46:47 +00:00
});
} catch (_e) {
2023-05-14 02:25:43 +00:00
throw new HTTPException(408, {
res: c.json({ id: 'ditto.timeout', error: 'Signing timeout' }, 408),
2023-05-14 00:46:47 +00:00
});
}
}
if (!seckey) {
throw new HTTPException(400, {
res: c.json({ id: 'ditto.private_key', error: 'No private key' }, 400),
});
}
2023-04-30 02:48:22 +00:00
(event as Event<K>).pubkey = getPublicKey(seckey);
(event as Event<K>).id = getEventHash(event as Event<K>);
(event as Event<K>).sig = getSignature(event as Event<K>, seckey);
2023-08-17 02:53:51 +00:00
return event as Event<K>;
2023-04-30 02:48:22 +00:00
}
export { signEvent };