diff --git a/src/index.tsx b/src/index.tsx index 44d18047..61bf5285 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -31,7 +31,7 @@ type FrameReturnType = { intents: JSX.Element } -const renderer = jsxRenderer( +const previewRenderer = jsxRenderer( ({ children }) => { return ( @@ -46,176 +46,181 @@ const renderer = jsxRenderer( { docType: true }, ) +const frameRenderer = jsxRenderer( + ({ context, intents }) => { + return ( + + + + + + + {parseIntents(intents)} + + + ) + }, + { docType: true }, +) + export class Framework extends Hono { frame( path: string, handler: (c: FrameContext) => FrameReturnType | Promise, ) { - this.get('/preview', renderer) - this.post('/preview', renderer) - - this.get('/preview/*', async (c) => { - const baseUrl = c.req.url.replace('/preview', '') - const response = await fetch(baseUrl) - const text = await response.text() - const frame = htmlToFrame(text) - return c.render( - <> - - , - ) - }) - - this.post('/preview', async (c) => { - const baseUrl = c.req.url.replace('/preview', '') - - const formData = await c.req.formData() - const buttonIndex = parseInt( - typeof formData.get('buttonIndex') === 'string' - ? (formData.get('buttonIndex') as string) - : '', - ) - const inputText = formData.get('inputText') - ? Buffer.from(formData.get('inputText') as string) - : undefined - - const privateKeyBytes = ed25519.utils.randomPrivateKey() - // const publicKeyBytes = await ed.getPublicKeyAsync(privateKeyBytes) - - // const key = bytesToHex(publicKeyBytes) - // const deadline = Math.floor(Date.now() / 1000) + 60 * 60 // now + hour - // - // const account = privateKeyToAccount(bytesToHex(privateKeyBytes)) - // const requestFid = 1 - - // const signature = await account.signTypedData({ - // domain: { - // name: 'Farcaster SignedKeyRequestValidator', - // version: '1', - // chainId: 10, - // verifyingContract: '0x00000000FC700472606ED4fA22623Acf62c60553', - // }, - // types: { - // SignedKeyRequest: [ - // { name: 'requestFid', type: 'uint256' }, - // { name: 'key', type: 'bytes' }, - // { name: 'deadline', type: 'uint256' }, - // ], - // }, - // primaryType: 'SignedKeyRequest', - // message: { - // requestFid: BigInt(requestFid), - // key, - // deadline: BigInt(deadline), - // }, - // }) - - // const response = await fetch( - // 'https://api.warpcast.com/v2/signed-key-requests', - // { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json', - // }, - // body: JSON.stringify({ - // deadline, - // key, - // requestFid, - // signature, - // }), - // }, - // ) - - const fid = 2 - const castId = { - fid, - hash: new Uint8Array( - Buffer.from('0000000000000000000000000000000000000000', 'hex'), - ), - } - const frameActionBody = FrameActionBody.create({ - url: Buffer.from(baseUrl), - buttonIndex, - castId, - inputText, + // Frame Routes + this.use(frameRenderer) + .get(async (c) => { + const { intents } = await handler(c) + return c.render(null, { context: c, intents }) }) - const frameActionMessage = await makeFrameAction( - frameActionBody, - { fid, network: 1 }, - new NobleEd25519Signer(privateKeyBytes), - ) - - const message = frameActionMessage._unsafeUnwrap() - const response = await fetch(baseUrl, { - method: 'POST', - body: JSON.stringify({ - untrustedData: { - buttonIndex, - castId: { - fid: castId.fid, - hash: `0x${bytesToHex(castId.hash)}`, - }, - fid, - inputText, - messageHash: `0x${bytesToHex(message.hash)}`, - network: 1, - timestamp: message.data.timestamp, - url: baseUrl, - }, - trustedData: { - messageBytes: Buffer.from( - Message.encode(message).finish(), - ).toString('hex'), - }, - }), + .post(async (c) => { + const context = await parsePostContext(c) + const { intents } = await handler(context) + return c.render(null, { context, intents }) }) - const text = await response.text() - // TODO: handle redirects - const frame = htmlToFrame(text) - - return c.render( - <> - - , - ) - }) - this.get(path, async (c) => { - const { intents } = await handler(c) - return c.render( - - - - - - - {parseIntents(intents)} - - , - ) - }) - - // TODO: don't slice - this.get(`${path.slice(1)}_og`, async (c) => { + // OG Image Route + this.get('image', async (c) => { const { image } = await handler(c) return new ImageResponse(image) }) - this.post(path, async (c) => { - const context = await parsePostContext(c) - const { intents } = await handler(context) - return c.render( - - - - - - - {parseIntents(intents)} - - , - ) - }) + // Frame Preview Routes + this.use('preview', previewRenderer) + .get(async (c) => { + const baseUrl = c.req.url.replace('/preview', '') + const response = await fetch(baseUrl) + const text = await response.text() + const frame = htmlToFrame(text) + return c.render( + <> + + , + ) + }) + .post(async (c) => { + const baseUrl = c.req.url.replace('/preview', '') + + const formData = await c.req.formData() + const buttonIndex = parseInt( + typeof formData.get('buttonIndex') === 'string' + ? (formData.get('buttonIndex') as string) + : '', + ) + const inputText = formData.get('inputText') + ? Buffer.from(formData.get('inputText') as string) + : undefined + + const privateKeyBytes = ed25519.utils.randomPrivateKey() + // const publicKeyBytes = await ed.getPublicKeyAsync(privateKeyBytes) + + // const key = bytesToHex(publicKeyBytes) + // const deadline = Math.floor(Date.now() / 1000) + 60 * 60 // now + hour + // + // const account = privateKeyToAccount(bytesToHex(privateKeyBytes)) + // const requestFid = 1 + + // const signature = await account.signTypedData({ + // domain: { + // name: 'Farcaster SignedKeyRequestValidator', + // version: '1', + // chainId: 10, + // verifyingContract: '0x00000000FC700472606ED4fA22623Acf62c60553', + // }, + // types: { + // SignedKeyRequest: [ + // { name: 'requestFid', type: 'uint256' }, + // { name: 'key', type: 'bytes' }, + // { name: 'deadline', type: 'uint256' }, + // ], + // }, + // primaryType: 'SignedKeyRequest', + // message: { + // requestFid: BigInt(requestFid), + // key, + // deadline: BigInt(deadline), + // }, + // }) + + // const response = await fetch( + // 'https://api.warpcast.com/v2/signed-key-requests', + // { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json', + // }, + // body: JSON.stringify({ + // deadline, + // key, + // requestFid, + // signature, + // }), + // }, + // ) + + const fid = 2 + const castId = { + fid, + hash: new Uint8Array( + Buffer.from('0000000000000000000000000000000000000000', 'hex'), + ), + } + const frameActionBody = FrameActionBody.create({ + url: Buffer.from(baseUrl), + buttonIndex, + castId, + inputText, + }) + const frameActionMessage = await makeFrameAction( + frameActionBody, + { fid, network: 1 }, + new NobleEd25519Signer(privateKeyBytes), + ) + + const message = frameActionMessage._unsafeUnwrap() + const response = await fetch(baseUrl, { + method: 'POST', + body: JSON.stringify({ + untrustedData: { + buttonIndex, + castId: { + fid: castId.fid, + hash: `0x${bytesToHex(castId.hash)}`, + }, + fid, + inputText, + messageHash: `0x${bytesToHex(message.hash)}`, + network: 1, + timestamp: message.data.timestamp, + url: baseUrl, + }, + trustedData: { + messageBytes: Buffer.from( + Message.encode(message).finish(), + ).toString('hex'), + }, + }), + }) + const text = await response.text() + // TODO: handle redirects + const frame = htmlToFrame(text) + + return c.render( + <> + + , + ) + }) + + // Package up the above routes into `path`. + this.route(path, this) } } @@ -374,6 +379,10 @@ function parseIntent(node: JSXNode, counter: Counter) { return Object.assign(intent, { props }) } +function parseUrl(path: string) { + return path.endsWith('/') ? path.slice(0, -1) : path +} + function htmlToFrame(html: string) { const window = new Window() window.document.write(html) diff --git a/src/register.d.ts b/src/register.d.ts new file mode 100644 index 00000000..6192f4e5 --- /dev/null +++ b/src/register.d.ts @@ -0,0 +1,10 @@ +import type { FrameContext } from './_lib/index.js' + +declare module 'hono' { + interface ContextRenderer { + ( + content: JSX.Element | null, + props?: { context: FrameContext; intents: JSX.Element }, + ): Response + } +}