Skip to content

Commit

Permalink
wip: redirect
Browse files Browse the repository at this point in the history
  • Loading branch information
tmm committed Feb 13, 2024
1 parent 0bef1b2 commit 3166339
Show file tree
Hide file tree
Showing 5 changed files with 304 additions and 137 deletions.
6 changes: 4 additions & 2 deletions examples/_dev/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,10 @@ app.frame('/buttons', ({ buttonValue }) => {
</div>
),
intents: [
<Button value="apples">Apples</Button>,
<Button.Link href="https://www.google.com">Google</Button.Link>,
<Button action="post_redirect" value="apples">
Redirect
</Button>,
<Button.Link href="https://www.example.com">Link</Button.Link>,
<Button.Mint target="eip155:7777777:0x060f3edd18c47f59bd23d063bbeb9aa4a8fec6df">
Mint
</Button.Mint>,
Expand Down
3 changes: 3 additions & 0 deletions src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type ButtonProps = {

export type ButtonRootProps = ButtonProps & {
action?: 'post' | 'post_redirect'
target?: string | undefined
value?: string | undefined
}

Expand All @@ -15,6 +16,7 @@ export function ButtonRoot({
action = 'post',
children,
index = 0,
target,
value,
}: ButtonRootProps) {
return [
Expand All @@ -24,6 +26,7 @@ export function ButtonRoot({
data-value={value}
/>,
<meta property={`fc:frame:button:${index}:action`} content={action} />,
<meta property={`fc:frame:button:${index}:target`} content={target} />,
] as unknown as HtmlEscapedString
}

Expand Down
131 changes: 93 additions & 38 deletions src/dev/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,15 @@ function Frame(props: FrameProps) {
} = props
const hasIntents = Boolean(input || buttons?.length)
return (
<div class="w-full" style={{ maxWidth: '512px' }}>
<div
class="w-full"
style={{ maxWidth: '512px' }}
x-data={`{
baseUrl: '${baseUrl}',
inputText: '',
postUrl: '${postUrl}',
}`}
>
<div class="relative rounded-md relative w-full">
<Img {...{ hasIntents, imageAspectRatio, imageUrl, title }} />

Expand Down Expand Up @@ -137,11 +145,13 @@ function Input(props: InputProps) {
return (
<input
aria-label={text}
autocomplete="off"
class="bg-bg rounded-sm border px-3 py-2.5 text-sm leading-snug w-full"
style={{ paddingBottom: '0.5rem' }}
name={name}
placeholder={text}
autocomplete="off"
style={{ paddingBottom: '0.5rem' }}
type="text"
x-model="inputText"
/>
)
}
Expand All @@ -161,48 +171,94 @@ function Button(props: ButtonProps) {
{title}
</span>
)
const leavingAppContainerProps = {
class: 'relative',
'x-data': `{
index: '${index}',
open: false,
target: ${target ? `'${target}'` : undefined},
}`,
}
const leavingAppPrompt = (
<div
x-show="open"
class="flex flex-col gap-1.5 border bg-bg p-4 rounded-lg text-center"
style={{ position: 'absolute', marginTop: '4px', width: '20rem' }}
{...{
'@click.outside': 'open = false',
'@keyup.escape': 'open = false',
'x-trap.noscroll': 'open',
}}
>
<h1 class="font-bold text-base">Leaving Warpcast</h1>
<div class="text-fg2 text-sm font-mono">{target}</div>
<p class="text-base leading-snug">
If you connect your wallet and the site is malicious, you may lose
funds.
</p>
<div class="flex gap-1.5 mt-1">
<button
class="bg-bg border rounded-md w-full text-sm font-bold py-1"
type="button"
x-on:click="open = false"
>
<div style={{ marginTop: '1px' }}>Cancel</div>
</button>
<button
class="bg-er border-er rounded-md w-full text-sm text-white font-bold py-1"
target="_blank"
type="button"
x-on:click={`open = false; window.open(target, '_blank');`}
>
<div style={{ marginTop: '1px' }}>I Understand</div>
</button>
</div>
</div>
)

if (type === 'link')
return (
<div x-data="{ open: false }" class="relative">
<div {...leavingAppContainerProps}>
<button class={buttonClass} type="button" x-on:click="open = true">
<div style={{ marginTop: '2px' }}>{innerHtml}</div>
{type === 'link' && linkIcon}
<div style={{ marginTop: '2px' }}>{linkIcon}</div>
</button>

<div
x-show="open"
class="flex flex-col gap-1.5 border bg-bg p-4 rounded-lg text-center"
style={{ position: 'absolute', marginTop: '4px', width: '20rem' }}
{...{
'@click.outside': 'open = false',
'x-trap.noscroll': 'open',
}}
{leavingAppPrompt}
</div>
)

if (type === 'post_redirect')
return (
<div {...leavingAppContainerProps}>
<button
class={buttonClass}
type="button"
x-on:click={`
fetch(baseUrl + '/dev/redirect', {
method: 'POST',
body: JSON.stringify({
buttonIndex: index,
inputText,
postUrl: target ?? postUrl,
}),
headers: {
'Content-Type': 'application/json',
},
})
.then(res => {
console.log(res)
target = 'https://example.com'
open = true
})
.catch(error => console.log(error))
`}
>
<h1 class="font-bold text-base">Leaving Warpcast</h1>
<div class="text-fg2 text-sm font-mono">{target}</div>
<p class="text-base leading-snug">
If you connect your wallet and the site is malicious, you may lose
funds.
</p>
<div class="flex gap-1.5 mt-1">
<button
class="bg-bg border rounded-md w-full text-sm font-bold py-1"
type="button"
x-on:click="open = false"
>
<div style={{ marginTop: '1px' }}>Cancel</div>
</button>
<button
class="bg-er border-er rounded-md w-full text-sm text-white font-bold py-1"
target="_blank"
type="button"
x-on:click={`open = false; window.open('${target}', '_blank');`}
>
<div style={{ marginTop: '1px' }}>I Understand</div>
</button>
</div>
</div>
<div style={{ marginTop: '2px' }}>{innerHtml}</div>
<div style={{ marginTop: '2px' }}>{redirectIcon}</div>
</button>

{leavingAppPrompt}
</div>
)

Expand All @@ -216,7 +272,6 @@ function Button(props: ButtonProps) {
>
{type === 'mint' && mintIcon}
{innerHtml}
{type === 'post_redirect' && redirectIcon}
</button>
)
}
Expand Down
106 changes: 106 additions & 0 deletions src/dev/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { Window } from 'happy-dom'
import { inspectRoutes } from 'hono/dev'
import {
FrameActionBody,
NobleEd25519Signer,
makeFrameAction,
} from '@farcaster/core'
import { ed25519 } from '@noble/curves/ed25519'

import {
type FrameContext,
Expand Down Expand Up @@ -253,3 +259,103 @@ export function getRoutes(
}
return frameRoutes
}

export function getData(value: Record<string, string | File>) {
const buttonIndex = parseInt(value.buttonIndex as string)
if (buttonIndex < 1 || buttonIndex > 4) throw new Error('Invalid buttonIndex')

const postUrl = value.postUrl as string
if (!postUrl) throw new Error('Invalid postUrl')

// TODO: Sanitize input
const inputText = value.inputText as string | undefined

// TODO: Make dynamic
const fid = 2
const castId = {
fid,
hash: new Uint8Array(
Buffer.from('0000000000000000000000000000000000000000', 'hex'),
),
}

return { buttonIndex, castId, fid, inputText, postUrl }
}

export async function fetchFrameMessage({
baseUrl,
buttonIndex,
castId,
fid,
inputText,
}: {
baseUrl: string
buttonIndex: number
castId: {
fid: number
hash: Uint8Array
}
fid: number
inputText: 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 frameActionBody = FrameActionBody.create({
url: Buffer.from(baseUrl),
buttonIndex,
castId,
inputText: inputText ? Buffer.from(inputText) : undefined,
})
const frameActionMessage = await makeFrameAction(
frameActionBody,
{ fid, network: 1 },
new NobleEd25519Signer(privateKeyBytes),
)

return frameActionMessage._unsafeUnwrap()
}
Loading

0 comments on commit 3166339

Please sign in to comment.