Skip to content

Commit

Permalink
feat: preview
Browse files Browse the repository at this point in the history
  • Loading branch information
tmm committed Feb 7, 2024
1 parent 1ec43a9 commit eecde11
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 43 deletions.
Binary file modified bun.lockb
Binary file not shown.
52 changes: 28 additions & 24 deletions example/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,60 @@
/** @jsxImportSource hono/jsx */
/** @jsxFrag */

import { Button, Framework } from "@wevm/framework";
import { Button, Framework } from '@wevm/framework'

const app = new Framework();
const app = new Framework()

app.frame("/", ({ untrustedData }) => {
const { buttonIndex } = untrustedData || {};
app.frame('/', ({ untrustedData }) => {
const { buttonIndex } = untrustedData || {}
console.log('buttonIndex', buttonIndex)
return {
image: (
<div
style={{
backgroundColor: "black",
backgroundSize: "150px 150px",
height: "100%",
width: "100%",
display: "flex",
textAlign: "center",
alignItems: "center",
justifyContent: "center",
flexDirection: "column",
flexWrap: "nowrap",
backgroundColor: 'black',
backgroundSize: '150px 150px',
height: '100%',
width: '100%',
display: 'flex',
textAlign: 'center',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
flexWrap: 'nowrap',
}}
>
<div
style={{
fontSize: 60,
fontStyle: "normal",
letterSpacing: "-0.025em",
color: "white",
fontStyle: 'normal',
letterSpacing: '-0.025em',
color: 'white',
marginTop: 30,
padding: "0 120px",
padding: '0 120px',
lineHeight: 1.4,
whiteSpace: "pre-wrap",
whiteSpace: 'pre-wrap',
}}
>
{typeof buttonIndex === "number"
{typeof buttonIndex === 'number'
? `Button Index: ${buttonIndex}`
: "Welcome!"}
: 'Welcome!'}
</div>
</div>
),
intents: (
<>
<Button>Apples</Button>
<Button>Oranges</Button>
<Button>{`Last index: ${
buttonIndex ? buttonIndex.toString() : 'none'
}`}</Button>
</>
),
};
});
}
})

export default {
port: 3001,
fetch: app.fetch,
};
}
192 changes: 174 additions & 18 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import { ImageResponse } from 'hono-og'
import { type JSXNode } from 'hono/jsx'
import { Window } from 'happy-dom'
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,
Expand Down Expand Up @@ -30,34 +38,148 @@ type FrameReturnType = {
intents: JSX.Element
}

const renderer = jsxRenderer(
({ children }) => {
return (
<html lang="en">
<body>{children}</body>
</html>
)
},
{ docType: true },
)

export class Framework extends Hono {
frame(
path: string,
handler: (c: FrameContext) => FrameReturnType | Promise<FrameReturnType>,
) {
this.get(
'/preview',
jsxRenderer(
({ children }) => {
return (
<html lang="en">
<body>{children}</body>
</html>
)
},
{ 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(
<div>
<img alt="Farcaster frame" src={frame.imageUrl} />
</div>,
<>
<FramePreview baseUrl={baseUrl} frame={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(
<>
<FramePreview baseUrl={baseUrl} frame={frame} />
</>,
)
})

Expand Down Expand Up @@ -103,6 +225,40 @@ export class Framework extends Hono {
////////////////////////////////////////////////////////////////////////
// Components

type FramePreviewProps = {
baseUrl: string
frame: Frame
}

function FramePreview({ baseUrl, frame }: FramePreviewProps) {
return (
<div>
<form action="/preview" method="post">
<input type="hidden" name="action" value={frame.postUrl} />
<div>
<img alt={frame.title ?? 'Farcaster frame'} src={frame.imageUrl} />
<div>{new URL(baseUrl).host}</div>
</div>
{/* TODO: Text input */}
{frame.buttons && (
<div>
{frame.buttons.map((button) => (
<button
key={button.index}
type="submit"
name="buttonIndex"
value={button.index}
>
{button.title}
</button>
))}
</div>
)}
</form>
</div>
)
}

export type ButtonProps = {
children: string
}
Expand Down
5 changes: 4 additions & 1 deletion src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}

0 comments on commit eecde11

Please sign in to comment.