Skip to content

Commit

Permalink
feat: redirect
Browse files Browse the repository at this point in the history
  • Loading branch information
tmm committed Feb 14, 2024
1 parent 169ceb4 commit c034d3b
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 40 deletions.
4 changes: 2 additions & 2 deletions examples/_dev/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ app.frame('/buttons', ({ buttonValue }) => {
</div>
),
intents: [
<Button action="post_redirect" value="apples">
<Button.Redirect location="https://github.com/honojs/vite-plugins/tree/main/packages/dev-server">
Redirect
</Button>,
</Button.Redirect>,
<Button.Link href="https://www.example.com">Link</Button.Link>,
<Button.Mint target="eip155:7777777:0x060f3edd18c47f59bd23d063bbeb9aa4a8fec6df">
Mint
Expand Down
46 changes: 32 additions & 14 deletions src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ export type ButtonProps = {
}

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

ButtonRoot.__type = 'button'
export function ButtonRoot({
action = 'post',
children,
index = 0,
target,
Expand All @@ -23,11 +21,9 @@ export function ButtonRoot({
<meta
property={`fc:frame:button:${index}`}
content={children}
data-value={value}
{...(value ? { 'data-value': value } : {})}
/>,
action !== 'post' && (
<meta property={`fc:frame:button:${index}:action`} content={action} />
),
<meta property={`fc:frame:button:${index}:action`} content="post" />,
target && (
<meta property={`fc:frame:button:${index}:target`} content={target} />
),
Expand All @@ -41,11 +37,7 @@ export type ButtonLinkProps = ButtonProps & {
ButtonLink.__type = 'button'
export function ButtonLink({ children, index = 0, href }: ButtonLinkProps) {
return [
<meta
property={`fc:frame:button:${index}`}
content={children}
data-href={href}
/>,
<meta property={`fc:frame:button:${index}`} content={children} />,
<meta property={`fc:frame:button:${index}:action`} content="link" />,
<meta property={`fc:frame:button:${index}:target`} content={href} />,
] as unknown as HtmlEscapedString
Expand All @@ -57,14 +49,39 @@ export type ButtonMintProps = ButtonProps & {

ButtonMint.__type = 'button'
export function ButtonMint({ children, index = 0, target }: ButtonMintProps) {
return [
<meta property={`fc:frame:button:${index}`} content={children} />,
<meta property={`fc:frame:button:${index}:action`} content="mint" />,
<meta property={`fc:frame:button:${index}:target`} content={target} />,
] as unknown as HtmlEscapedString
}

export type ButtonRedirectProps = ButtonProps & {
location: string
target?: string | undefined
}

ButtonRedirect.__type = 'button'
export function ButtonRedirect({
children,
index = 0,
location,
target,
}: ButtonRedirectProps) {
return [
<meta
property={`fc:frame:button:${index}`}
content={children}
data-target={target}
data-type="redirect"
data-value={location}
/>,
<meta property={`fc:frame:button:${index}:action`} content="mint" />,
<meta property={`fc:frame:button:${index}:target`} content={target} />,
<meta
property={`fc:frame:button:${index}:action`}
content="post_redirect"
/>,
target && (
<meta property={`fc:frame:button:${index}:target`} content={target} />
),
] as unknown as HtmlEscapedString
}

Expand All @@ -84,5 +101,6 @@ export function ButtonReset({ children, index = 0 }: ButtonResetProps) {
export const Button = Object.assign(ButtonRoot, {
Link: ButtonLink,
Mint: ButtonMint,
Redirect: ButtonRedirect,
Reset: ButtonReset,
})
25 changes: 18 additions & 7 deletions src/dev/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ function Button(props: ButtonProps) {
index: '${index}',
open: false,
target: ${target ? `'${target}'` : undefined},
url: ${type === 'link' && target ? `'${target}'` : undefined},
}`,
}
const leavingAppPrompt = (
Expand All @@ -187,11 +188,11 @@ function Button(props: ButtonProps) {
{...{
'@click.outside': 'open = false',
'@keyup.escape': 'open = false',
'x-trap.noscroll': 'open',
'x-trap': 'open',
}}
>
<h1 class="font-bold text-base">Leaving Warpcast</h1>
<div class="text-fg2 text-sm font-mono">{target}</div>
<div class="line-clamp-2 text-fg2 text-sm font-mono" x-text="url" />
<p class="text-base leading-snug">
If you connect your wallet and the site is malicious, you may lose
funds.
Expand All @@ -208,7 +209,7 @@ function Button(props: ButtonProps) {
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');`}
x-on:click={`open = false; window.open(url, '_blank');`}
>
<div style={{ marginTop: '1px' }}>I Understand</div>
</button>
Expand All @@ -235,6 +236,7 @@ function Button(props: ButtonProps) {
class={buttonClass}
type="button"
x-on:click={`
if (open) return
fetch(baseUrl + '/dev/redirect', {
method: 'POST',
body: JSON.stringify({
Expand All @@ -246,12 +248,14 @@ function Button(props: ButtonProps) {
'Content-Type': 'application/json',
},
})
.then(res => {
console.log(res)
target = 'https://example.com'
.then(async (res) => {
const json = await res.json()
// TODO: show error
if (!json.success) return
url = json.redirectUrl
open = true
})
.catch(error => console.log(error))
.catch((error) => console.log(error))
`}
>
<div style={{ marginTop: '2px' }}>{innerHtml}</div>
Expand Down Expand Up @@ -709,6 +713,13 @@ export function Style() {
scrollbar-color: var(--br) transparent;
scrollbar-width: thin;
}
.line-clamp-2 {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
`
return <style dangerouslySetInnerHTML={{ __html: styles }} />
}
10 changes: 2 additions & 8 deletions src/dev/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ export function routes<
inputText,
})

console.log(postUrl)
const response = await fetch(postUrl, {
method: 'POST',
body: JSON.stringify({
Expand All @@ -175,13 +174,8 @@ export function routes<
}),
})

// TODO: Get redirect url
console.log({ response })

return c.json({
success: true,
redirectUrl: '/',
})
if (!response.redirected) return c.json({ success: false })
return c.json({ success: true, redirectUrl: response.url })
},
)
}
6 changes: 6 additions & 0 deletions src/farc-base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ export class FarcBase<
request: c.req,
})

if (context.status === 'redirect') {
const location = context.buttonValue
if (!location) throw new Error('location required to redirect')
return c.redirect(location, 302)
}

if (context.url !== parsePath(c.req.url))
return c.redirect(
`${context.url}?previousContext=${query.previousContext}`,
Expand Down
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ export type FrameContext<path extends string = string, state = unknown> = {
/**
* Status of the frame in the frame lifecycle.
* - `initial` - The frame has not yet been interacted with.
* - `redirect` - The frame interaction is a redirect (button of type `'post_redirect'`).
* - `response` - The frame has been interacted with (user presses button).
*/
status: 'initial' | 'response'
status: 'initial' | 'redirect' | 'response'
trustedData?: TrustedData | undefined
untrustedData?: UntrustedData | undefined
url: Context['req']['url']
Expand Down
12 changes: 7 additions & 5 deletions src/utils/getFrameContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ export async function getFrameContext<state>(
const { context, previousContext, request } = options
const { trustedData, untrustedData } = context || {}

const { buttonIndex, buttonValue, inputText, reset } = getIntentState(
// TODO: derive from untrusted data.
untrustedData,
previousContext?.intents || [],
)
const { buttonIndex, buttonValue, inputText, redirect, reset } =
getIntentState(
// TODO: derive from untrusted data.
untrustedData,
previousContext?.intents || [],
)

const status = (() => {
if (redirect) return 'redirect'
if (reset) return 'initial'
return context.status || 'initial'
})()
Expand Down
20 changes: 17 additions & 3 deletions src/utils/getIntentState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,29 @@ export function getIntentState(
intents: readonly JSXNode[] | null,
) {
const { buttonIndex, inputText } = frameData || {}
const state = { buttonIndex, buttonValue: undefined, inputText, reset: false }

const state = {
buttonIndex,
buttonValue: undefined,
inputText,
redirect: false,
reset: false,
}
if (!intents) return state

if (buttonIndex) {
const buttonIntents = intents.filter((intent) =>
intent?.props?.property?.match(/fc:frame:button:\d$/),
)
const intent = buttonIntents[buttonIndex - 1]
state.buttonValue = intent.props['data-value']
if (intent.props['data-type'] === 'reset') state.reset = true

const type = intent.props['data-type']
if (type === 'redirect') state.redirect = true
else if (type === 'reset') state.reset = true

const value = intent.props['data-value']
state.buttonValue = value
}

return state
}

0 comments on commit c034d3b

Please sign in to comment.