From 764c3afdae3b47026fed025544dd69af437538cf Mon Sep 17 00:00:00 2001 From: awkweb Date: Wed, 7 Feb 2024 17:40:28 -0500 Subject: [PATCH] feat: preview (#3) Co-authored-by: jxom --- bun.lockb | Bin 185088 -> 195584 bytes example/src/index.tsx | 3 + src/index.tsx | 192 ++++++++++++++++++++++++++++++++++++++---- src/package.json | 5 +- 4 files changed, 181 insertions(+), 19 deletions(-) diff --git a/bun.lockb b/bun.lockb index d26d007e46a1248154fbf467885b45dab7bfb5fb..d162306371a683809f9e7f79d38e43ed920ed0b0 100755 GIT binary patch delta 10589 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 Ut6#-g<|4f8LRY { <> + ), } diff --git a/src/index.tsx b/src/index.tsx index f573e25f..9c83674e 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,6 +3,14 @@ import { type Context, Hono } from 'hono' import { ImageResponse } from 'hono-og' import { type JSXNode } from 'hono/jsx' import { jsxRenderer } from 'hono/jsx-renderer' +import * as ed from '@noble/ed25519' +import { bytesToHex } from 'viem/utils' +import { + NobleEd25519Signer, + makeFrameAction, + Message, + FrameActionBody, +} from '@farcaster/core' import { type Frame, @@ -30,34 +38,148 @@ type FrameReturnType = { intents: JSX.Element } +const renderer = jsxRenderer( + ({ children }) => { + return ( + + {children} + + ) + }, + { docType: true }, +) + export class Framework extends Hono { frame( path: string, handler: (c: FrameContext) => FrameReturnType | Promise, ) { - this.get( - '/preview', - jsxRenderer( - ({ children }) => { - return ( - - {children} - - ) - }, - { docType: true }, - ), - ) + 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 html = await response.text() - const frame = htmlToFrame(html) + const text = await response.text() + const frame = htmlToFrame(text) return c.render( -
- Farcaster frame -
, + <> + + , + ) + }) + + 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 = ed.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: bytesToHex(castId.hash), + }, + fid, + inputText, + messageHash: 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( + <> + + , ) }) @@ -103,6 +225,40 @@ export class Framework extends Hono { //////////////////////////////////////////////////////////////////////// // Components +type FramePreviewProps = { + baseUrl: string + frame: Frame +} + +function FramePreview({ baseUrl, frame }: FramePreviewProps) { + return ( +
+
+ +
+ {frame.title +
{new URL(baseUrl).host}
+
+ {/* TODO: Text input */} + {frame.buttons && ( +
+ {frame.buttons.map((button) => ( + + ))} +
+ )} +
+
+ ) +} + export type ButtonProps = { children: string } diff --git a/src/package.json b/src/package.json index c77d0c0e..57c140cc 100644 --- a/src/package.json +++ b/src/package.json @@ -13,7 +13,10 @@ "hono": "^3" }, "dependencies": { + "@farcaster/core": "^0.13.7", + "@noble/ed25519": "^2.0.0", "happy-dom": "^13.3.8", - "hono-og": "~0.0.2" + "hono-og": "~0.0.2", + "viem": "^2.7.6" } }