From 45adfde29a2e8dc641e07071a8ecc08e39f41ad0 Mon Sep 17 00:00:00 2001 From: Vladyslav Dalechyn Date: Fri, 26 Apr 2024 15:50:33 +0300 Subject: [PATCH 01/22] feat: multi-step cast actions --- .changeset/purple-pants-eat.md | 8 + playground/src/castAction.tsx | 74 +++++- site/pages/concepts/cast-actions.mdx | 49 +++- .../concepts/multi-step-cast-actions.mdx | 233 ++++++++++++++++++ .../reference/frog-cast-action-context.mdx | 38 +++ site/vocs.config.tsx | 6 +- src/frog-base.tsx | 13 +- src/types/castAction.ts | 33 ++- src/types/context.ts | 19 +- src/utils/getCastActionContext.ts | 22 +- 10 files changed, 467 insertions(+), 28 deletions(-) create mode 100644 .changeset/purple-pants-eat.md create mode 100644 site/pages/concepts/multi-step-cast-actions.mdx diff --git a/.changeset/purple-pants-eat.md b/.changeset/purple-pants-eat.md new file mode 100644 index 00000000..ce9bcb27 --- /dev/null +++ b/.changeset/purple-pants-eat.md @@ -0,0 +1,8 @@ +--- +"frog": minor +--- + +Implemented multi-step cast actions. [See more](https://warpcast.notion.site/Frames-Multi-step-actions-f469054de8fb4ffc8b8e2649a41b6ad9?pvs=74). + +Breaking changes have affected `.castAction` handler definition and its response: +- `.castAction` handler response now requires a `"type": "message" | "frame"` to be specified. Shorthands `c.message(...)` and `c.frame(...)` were added for the ease of use. diff --git a/playground/src/castAction.tsx b/playground/src/castAction.tsx index 76cce0be..30d38376 100644 --- a/playground/src/castAction.tsx +++ b/playground/src/castAction.tsx @@ -1,7 +1,10 @@ -import { Frog } from 'frog' -import { Button } from 'frog' +import { Button, Frog, TextInput } from 'frog' -export const app = new Frog() +import { Box, Heading, vars } from './ui.js' + +export const app = new Frog({ + ui: { vars }, +}) .frame('/', (c) => c.res({ image: ( @@ -36,12 +39,17 @@ export const app = new Frog() ), intents: [ - Add, + + Message + , + + Frame + , ], }), ) .castAction( - '/action', + '/action-message', async (c) => { console.log( `Cast Action to ${JSON.stringify(c.actionData.castId)} from ${ @@ -49,11 +57,63 @@ export const app = new Frog() }`, ) if (Math.random() > 0.5) return c.error({ message: 'Action failed :(' }) - return c.res({ message: 'Action Succeeded' }) + return c.message({ message: 'Action Succeeded' }) }, { name: 'Log This!', icon: 'log', - description: 'This cast action will log something!', + description: 'This cast action will log something and return a message!', }, ) + .castAction( + '/action-frame', + async (c) => { + console.log( + `Cast Action to ${JSON.stringify(c.actionData.castId)} from ${ + c.actionData.fid + }`, + ) + if (Math.random() > 0.5) return c.error({ message: 'Action failed :(' }) + + return c.frame({ + action: '/action-frame-response', + }) + }, + { + name: 'Log This!', + icon: 'log', + description: 'This cast action will log something and invoke a frame!', + }, + ) + .frame('/action-frame-response', async (c) => { + const { buttonValue, inputText, status } = c + const fruit = inputText || buttonValue + return c.res({ + action: '/action', + image: ( + + + {status === 'response' + ? `Nice choice.${fruit ? ` ${fruit.toUpperCase()}!!` : ''}` + : 'Cast Action Frame Response 🐸'} + + + ), + intents: [ + , + , + , + , + status === 'response' && Reset, + ], + }) + }) diff --git a/site/pages/concepts/cast-actions.mdx b/site/pages/concepts/cast-actions.mdx index d94a3acc..7f0ec7b9 100644 --- a/site/pages/concepts/cast-actions.mdx +++ b/site/pages/concepts/cast-actions.mdx @@ -8,7 +8,7 @@ At a glance: 1. User installs Cast Action via specific deeplink or by clicking on `{:jsx}` element with a specified target `.castAction` route in a Frame. 2. When the user presses the Cast Action button in the App, the App will make a `POST` request to the `.castAction` route. -3. Frame performs any action and returns a response to the App. +3. Server performs any action and returns a response to the App which is shown as a toast. ## Walkthrough @@ -48,7 +48,7 @@ app.castAction( c.actionData.fid }`, ) - return c.res({ message: 'Action Succeeded' }) + return c.res({ type: 'message', message: 'Action Succeeded' }) }, { name: "Log This!", icon: "log" }) ) @@ -126,7 +126,7 @@ app.frame('/', (c) => { }) }) -app.castAction( +app.castAction( // [!code focus] '/log-this', // [!code focus] (c) => { // [!code focus] console.log( // [!code focus] @@ -134,7 +134,7 @@ app.castAction( c.actionData.fid // [!code focus] }`, // [!code focus] ) // [!code focus] - return c.res({ message: 'Action Succeeded' }) // [!code focus] + return c.res({ type: 'message', message: 'Action Succeeded' }) // [!code focus] }, // [!code focus] { name: "Log This!", icon: "log" }) // [!code focus] ) // [!code focus] @@ -145,8 +145,47 @@ A breakdown of the `/log-this` route handler: - `c.actionData` is never nullable and is always defined since Cast Actions always do `POST` request. - We are responding with a `c.res` response and specifying a `message` that will appear in the success toast. +### 3. Bonus: Shorthand `c.message` + +In order not to add property `"type": "message"` to your `c.res(...)`, you can use a shorthand `c.message(...)`. + +```tsx twoslash [src/index.tsx] +// @noErrors +app.castAction( + '/log-this', + (c) => { + console.log( + `Cast Action to ${JSON.stringify(c.actionData.castId)} from ${ + c.actionData.fid + }`, + ) + return c.message({ message: 'Action Succeeded' }) // [!code focus] + }, + { name: "Log This!", icon: "log" }) +) +``` + +### 4. Bonus: Returning an error + +You can return an error response for a Client to render an error toast by using `c.error`. + +```tsx twoslash [src/index.tsx] +// @noErrors +app.castAction( + '/log-this', + (c) => { + console.log( + `Cast Action to ${JSON.stringify(c.actionData.castId)} from ${ + c.actionData.fid + }`, + ) + return c.error({ message: 'Action Failed' }) // [!code focus] + }, + { name: "Log This!", icon: "log" }) +) +``` -### 3. Bonus: Learn the API +### 5. Bonus: Learn the API You can learn more about the transaction APIs here: diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx new file mode 100644 index 00000000..e1125cc9 --- /dev/null +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -0,0 +1,233 @@ +# Mutli-step Cast Actions + +Multi-step Cast Actions are similar to [Cast Actions](/concepts/cast-actions) with a difference that they return a frame, instead of showing a message. (see the [spec](https://warpcast.notion.site/Frames-Multi-step-actions-f469054de8fb4ffc8b8e2649a41b6ad9)). + +## Overview + +At a glance: + +1. User installs Cast Action via specific deeplink or by clicking on `{:jsx}` element with a specified target `.castAction` route in a Frame. +2. When the user presses the Cast Action button in the App, the App will make a `POST` request to the `.castAction` route. +3. Server performs any action and returns a response to the App, which is shown as an interactible Frame dialog. + +## Walkthrough + +Here is a trivial example on how to expose a multi-step action with a frame. We will break it down below. + +:::code-group + +```tsx twoslash [src/index.tsx] +// @noErrors +/** @jsxImportSource hono/jsx */ +// ---cut--- +import { Button, Frog, TextInput, parseEther } from 'frog' +import { abi } from './abi' + +export const app = new Frog() + +app.frame('/', (c) => { + return c.res({ + image: ( +
+ Add "Hello world!" Action +
+ ), + intents: [ + + Add + , + ] + }) +}) + +app.castAction( + '/hello-world', + (c) => { + return c.res({ type: 'frame', action: '/hello-world-frame' }) + }, + { name: "Hello world!", icon: "smiley" }) +) + +app.frame('/hello-world-frame', (c) => { + return c.res({ + image: ( +
+ Hello world! +
+ ) + }) +}) +``` + +::: + +::::steps + +### 1. Render Frame & Add Action Intent + +In the example above, we are rendering Add Action intent: + +`action` property is used to set the path to the cast action route. + +```tsx twoslash [src/index.tsx] +// @noErrors +/** @jsxImportSource hono/jsx */ +import { Button, Frog, parseEther } from 'frog' +import { abi } from './abi' + +export const app = new Frog() +// ---cut--- +app.frame('/', (c) => { + return c.res({ + image: ( +
+ Add "Hello world!" Action +
+ ), + intents: [ + + Add + , + ] + }) +}) + +// ... +``` + + +### 2. Handle `/hello-world` Requests + +Without a route handler to handle the Action request, the Multi-step Cast Action will be meaningless. + +To specify the name and icon for your action, the next properties are used in the action handler definition: +1. `name` property is used to set the name of the action. It must be less than 30 characters +2. `icon` property is used to associate your Multi-step Cast Action with one of the Octicons. You can see the supported list [here](https://warpcast.notion.site/Spec-Farcaster-Actions-84d5a85d479a43139ea883f6823d8caa). +3. (optional) `description` property is used to describe your action, up to 80 characters. +4. (optional) `aboutUrl` property is used to show an "About" link when installing an action. + +Let's define a `/hello-world` route to handle the the Multi-step Cast Action: + +```tsx twoslash [src/index.tsx] +// @noErrors +/** @jsxImportSource hono/jsx */ +import { Button, Frog, parseEther } from 'frog' +import { abi } from './abi' + +export const app = new Frog() +// ---cut--- +app.frame('/', (c) => { + return c.res({ + image: ( +
+ Add "Hello world!" Action +
+ ), + intents: [ + + Add + , + ] + }) +}) + +app.castAction( // [!code focus] + '/hello-world', // [!code focus] + (c) => { // [!code focus] + console.log( // [!code focus] + `Cast Action to ${JSON.stringify(c.actionData.castId)} from ${ // [!code focus] + c.actionData.fid // [!code focus] + }`, // [!code focus] + ) // [!code focus] + return c.res({ type: 'frame', action: '/hello-world-frame' }) // [!code focus] + }, // [!code focus] + { name: "Hello world!", icon: "smiley" }) // [!code focus] +) // [!code focus] +``` + +A breakdown of the `/hello-world` route handler: + +- `c.actionData` is never nullable and is always defined since Multi-step Cast Actions always do `POST` request. +- We are responding with a `c.res` response and specifying a `message` that will appear in the success toast. + +### 3. Defining a frame handler +Since in the previous step we send a response with `"type": "frame"` and have specified `"action": "/hello-world-frame"`, Client won't be able to resolve it since we have not defined it yet. + +So let's define one! + +```tsx twoslash [src/index.tsx] +// @noErrors +/** @jsxImportSource hono/jsx */ +// ---cut--- +import { Button, Frog, TextInput, parseEther } from 'frog' +import { abi } from './abi' + +export const app = new Frog() + +app.frame('/', (c) => { + return c.res({ + image: ( +
+ Add "Hello world!" Action +
+ ), + intents: [ + + Add + , + ] + }) +}) + +app.castAction( + '/hello-world', + (c) => { + return c.res({ type: 'frame', action: '/hello-world-frame' }) + }, + { name: "Hello world!", icon: "smiley" }) +) + +app.frame('/hello-world-frame', (c) => { // [!code focus] + return c.res({ // [!code focus] + image: ( // [!code focus] +
// [!code focus] + Hello world! // [!code focus] +
// [!code focus] + ) // [!code focus] + }) // [!code focus] +}) // [!code focus] +``` + +Now we're all set! You can use existing Frame API to connect multiple frames together to have a multi-step experience. (see the [Connecting Frames (Actions)](/concepts/actions)). + +### 4. Bonus: Shorthand `c.frame` + +In order not to add property `"type": "frame"` to your `c.res(...)`, you can use a shorthand `c.frame(...)`. + +```tsx twoslash [src/index.tsx] +// @noErrors +app.castAction( + '/hello-world', + (c) => { + console.log( + `Cast Action to ${JSON.stringify(c.actionData.castId)} from ${ + c.actionData.fid + }`, + ) + return c.frame({ action: '/hello-world-frame' }) // [!code focus] + }, + { name: "Hello world!", icon: "smiley" }) +) +``` + +### 4. Bonus: Learn the API + +You can learn more about the transaction APIs here: + +- [`Frog.castAction` Reference](/reference/frog-cast-action) +- [`Frog.castAction` Context Reference](/reference/frog-cast-action-context) +- [`Frog.castAction` Response Reference](/reference/frog-cast-action-response) + +:::: diff --git a/site/pages/reference/frog-cast-action-context.mdx b/site/pages/reference/frog-cast-action-context.mdx index a73a1038..401d1ce0 100644 --- a/site/pages/reference/frog-cast-action-context.mdx +++ b/site/pages/reference/frog-cast-action-context.mdx @@ -57,6 +57,44 @@ app.castAction('/', (c) => { }) ``` +## message + +- **Type**: `(response: CastActionMessageResponse) => CastActionResponse` + +Shorthand for [`c.res(...)`](/reference/frog-cast-action-context#res) response with `"type": "message"` + +```tsx twoslash +// @noErrors +/** @jsxImportSource frog/jsx */ +// ---cut--- +import { Button, Frog } from 'frog' + +export const app = new Frog() + +app.castAction('/', (c) => { + return c.message({/* ... */}) // [!code focus] +}) +``` + +## frame + +- **Type**: `(response: CastActionFrameResponse) => CastActionResponse` + +Shorthand for [`c.res(...)`](/reference/frog-cast-action-context#res) response with `"type": "frame"` + +```tsx twoslash +// @noErrors +/** @jsxImportSource frog/jsx */ +// ---cut--- +import { Button, Frog } from 'frog' + +export const app = new Frog() + +app.castAction('/', (c) => { + return c.frame({/* ... */}) // [!code focus] +}) +``` + ## req - **Type**: `Request` diff --git a/site/vocs.config.tsx b/site/vocs.config.tsx index 86922e74..0c766a2e 100644 --- a/site/vocs.config.tsx +++ b/site/vocs.config.tsx @@ -4,7 +4,7 @@ import { getFrameMetadata } from '../src/utils/getFrameMetadata.js' export default defineConfig({ banner: - '💅 Introducing [FrogUI](/ui) – systematic & type-safe UI primitives for frame images.', + '👣 Introducing [Multi-step Cast Actions](/concepts/multi-step-cast-actions).', description: 'Framework for Farcaster Frames', iconUrl: '/icon.png', async head({ path }) { @@ -309,6 +309,10 @@ export default defineConfig({ text: 'Cast Actions', link: '/concepts/cast-actions', }, + { + text: 'Multi-step Cast Actions', + link: '/concepts/multi-step-cast-actions', + }, ], }, { diff --git a/src/frog-base.tsx b/src/frog-base.tsx index 81b2084c..ebc88971 100644 --- a/src/frog-base.tsx +++ b/src/frog-base.tsx @@ -356,13 +356,16 @@ export class FrogBase< return c.json({ message: response.error.message }) } - const { headers = this.headers, message } = response.data + if (response.data.type === 'frame') { + return c.json({ + frameUrl: baseUrl + parsePath(response.data.action), + type: 'frame', + }) + } - // Set response headers provided by consumer. - for (const [key, value] of Object.entries(headers ?? {})) - c.header(key, value) + const { message } = response.data - return c.json({ message }) + return c.json({ message, type: 'message' }) }) return this diff --git a/src/types/castAction.ts b/src/types/castAction.ts index bc523d7c..d54ac8f7 100644 --- a/src/types/castAction.ts +++ b/src/types/castAction.ts @@ -1,10 +1,19 @@ import type { TypedResponse } from './response.js' -export type CastActionResponse = { +export type CastActionFrameResponse = { /** - * HTTP response headers. + * Action path to the frame + * + * @example '/my-frame' */ - headers?: Record | undefined + action: string +} + +export type CastActionFrameResponseFn = ( + response: CastActionFrameResponse, +) => TypedResponse + +export type CastActionMessageResponse = { /** * Message to show in the toast. * @@ -13,6 +22,24 @@ export type CastActionResponse = { message: string } +export type CastActionMessageResponseFn = ( + response: CastActionMessageResponse, +) => TypedResponse + +export type CastActionResponse = + | ({ + /** + * Type of the response + */ + type: 'message' + } & CastActionMessageResponse) + | ({ + /** + * Type of the response + */ + type: 'frame' + } & CastActionFrameResponse) + export type CastActionResponseFn = ( response: CastActionResponse, ) => TypedResponse diff --git a/src/types/context.ts b/src/types/context.ts index a00688e3..646db98b 100644 --- a/src/types/context.ts +++ b/src/types/context.ts @@ -1,5 +1,10 @@ import type { Context as Context_hono, Input } from 'hono' -import type { CastActionData, CastActionResponseFn } from './castAction.js' +import type { + CastActionData, + CastActionFrameResponseFn, + CastActionMessageResponseFn, + CastActionResponseFn, +} from './castAction.js' import type { Env } from './env.js' import type { FrameButtonValue, FrameData, FrameResponseFn } from './frame.js' import type { BaseErrorResponseFn } from './response.js' @@ -36,6 +41,18 @@ export type CastActionContext< env: Context_hono['env'] /** Error response that includes message and statusCode. */ error: BaseErrorResponseFn + /** + * Frame response for building ephemeral frames. + * + * @see https://warpcast.notion.site/Frames-Multi-step-actions-f469054de8fb4ffc8b8e2649a41b6ad9?pvs=74 + */ + frame: CastActionFrameResponseFn + /** + * Message response for building single-step cast actions. + * + * @see https://warpcast.notion.site/Frames-Cast-Actions-v1-84d5a85d479a43139ea883f6823d8caa + */ + message: CastActionMessageResponseFn /** * Hono request object. * diff --git a/src/utils/getCastActionContext.ts b/src/utils/getCastActionContext.ts index 4a2f17e9..9bff9775 100644 --- a/src/utils/getCastActionContext.ts +++ b/src/utils/getCastActionContext.ts @@ -33,12 +33,6 @@ export function getCastActionContext< return { context: { - env, - error: (data) => ({ - error: data, - format: 'castAction', - status: 'error', - }), actionData: { buttonIndex: 1, castId: frameData.castId, @@ -48,6 +42,22 @@ export function getCastActionContext< timestamp: frameData.timestamp, url: frameData.url, }, + env, + error: (data) => ({ + error: data, + format: 'castAction', + status: 'error', + }), + frame: (data) => ({ + data: { action: data.action, type: 'frame' }, + format: 'castAction', + status: 'success', + }), + message: (data) => ({ + data: { message: data.message, type: 'message' }, + format: 'castAction', + status: 'success', + }), req, res: (data) => ({ data, From e0fc422f35676078a06c62dc1984e54b86a1091d Mon Sep 17 00:00:00 2001 From: hotwater Date: Sat, 27 Apr 2024 02:21:28 +0300 Subject: [PATCH 02/22] feat: support absolute frame urls --- src/frog-base.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/frog-base.tsx b/src/frog-base.tsx index ebc88971..5bb1da5d 100644 --- a/src/frog-base.tsx +++ b/src/frog-base.tsx @@ -357,8 +357,11 @@ export class FrogBase< } if (response.data.type === 'frame') { + const action = response.data.action return c.json({ - frameUrl: baseUrl + parsePath(response.data.action), + frameUrl: action.startsWith('http') + ? action + : baseUrl + parsePath(action), type: 'frame', }) } From c52111a89784cd586521ec15bd5791c8ee9f647f Mon Sep 17 00:00:00 2001 From: Vladyslav Dalechyn Date: Mon, 29 Apr 2024 10:10:33 +0300 Subject: [PATCH 03/22] docs Co-authored-by: jxom --- site/pages/concepts/multi-step-cast-actions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx index e1125cc9..7fa11dbb 100644 --- a/site/pages/concepts/multi-step-cast-actions.mdx +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -1,4 +1,4 @@ -# Mutli-step Cast Actions +# Multi-step Cast Actions Multi-step Cast Actions are similar to [Cast Actions](/concepts/cast-actions) with a difference that they return a frame, instead of showing a message. (see the [spec](https://warpcast.notion.site/Frames-Multi-step-actions-f469054de8fb4ffc8b8e2649a41b6ad9)). From 4ff6ca74c99a1a4fe3d450e780fe32dc39c42c8d Mon Sep 17 00:00:00 2001 From: Vladyslav Dalechyn Date: Mon, 29 Apr 2024 10:10:54 +0300 Subject: [PATCH 04/22] docs Co-authored-by: jxom --- site/pages/concepts/multi-step-cast-actions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx index 7fa11dbb..95c4a56e 100644 --- a/site/pages/concepts/multi-step-cast-actions.mdx +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -1,6 +1,6 @@ # Multi-step Cast Actions -Multi-step Cast Actions are similar to [Cast Actions](/concepts/cast-actions) with a difference that they return a frame, instead of showing a message. (see the [spec](https://warpcast.notion.site/Frames-Multi-step-actions-f469054de8fb4ffc8b8e2649a41b6ad9)). +Multi-step Cast Actions are similar to [Cast Actions](/concepts/cast-actions) with a difference that they display a frame, instead of displaying a message. See the [spec](https://warpcast.notion.site/Frames-Multi-step-actions-f469054de8fb4ffc8b8e2649a41b6ad9). ## Overview From 217bc3a9836226bd7fe239504f47836768fbdbaf Mon Sep 17 00:00:00 2001 From: Vladyslav Dalechyn Date: Mon, 29 Apr 2024 10:11:22 +0300 Subject: [PATCH 05/22] docs Co-authored-by: jxom --- site/pages/concepts/multi-step-cast-actions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx index 95c4a56e..9d9177c8 100644 --- a/site/pages/concepts/multi-step-cast-actions.mdx +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -65,7 +65,7 @@ app.frame('/hello-world-frame', (c) => { ### 1. Render Frame & Add Action Intent -In the example above, we are rendering Add Action intent: +In the example below, we are rendering an "add cast action" intent. `action` property is used to set the path to the cast action route. From 8572e9a90acd4791518f26ef90a4b5a2b46c6e4b Mon Sep 17 00:00:00 2001 From: Vladyslav Dalechyn Date: Mon, 29 Apr 2024 10:11:38 +0300 Subject: [PATCH 06/22] docs Co-authored-by: jxom --- site/pages/concepts/multi-step-cast-actions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx index 9d9177c8..92e1269e 100644 --- a/site/pages/concepts/multi-step-cast-actions.mdx +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -67,7 +67,7 @@ app.frame('/hello-world-frame', (c) => { In the example below, we are rendering an "add cast action" intent. -`action` property is used to set the path to the cast action route. +The `action` property is used to set the path to the cast action route. ```tsx twoslash [src/index.tsx] // @noErrors From 71df61993d3b1ae24eba2a6402309299d8b87966 Mon Sep 17 00:00:00 2001 From: Vladyslav Dalechyn Date: Mon, 29 Apr 2024 10:11:56 +0300 Subject: [PATCH 07/22] docs multi-step-cast-actions.mdx Co-authored-by: jxom --- site/pages/concepts/multi-step-cast-actions.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx index 92e1269e..b26edb7f 100644 --- a/site/pages/concepts/multi-step-cast-actions.mdx +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -91,8 +91,6 @@ app.frame('/', (c) => { ] }) }) - -// ... ``` From 8a751d3de61a026ca1923695f5ba6db4e76c43a7 Mon Sep 17 00:00:00 2001 From: Vladyslav Dalechyn Date: Mon, 29 Apr 2024 10:12:18 +0300 Subject: [PATCH 08/22] docs Co-authored-by: jxom --- site/pages/concepts/multi-step-cast-actions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx index b26edb7f..2311b21e 100644 --- a/site/pages/concepts/multi-step-cast-actions.mdx +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -96,7 +96,7 @@ app.frame('/', (c) => { ### 2. Handle `/hello-world` Requests -Without a route handler to handle the Action request, the Multi-step Cast Action will be meaningless. +Next, we will add a route handler to handle the Cast Action request. To specify the name and icon for your action, the next properties are used in the action handler definition: 1. `name` property is used to set the name of the action. It must be less than 30 characters From d86d5c1a51b3b4031537c0f71d494ef371d4c028 Mon Sep 17 00:00:00 2001 From: Vladyslav Dalechyn Date: Mon, 29 Apr 2024 10:12:37 +0300 Subject: [PATCH 09/22] docs Co-authored-by: jxom --- site/pages/concepts/multi-step-cast-actions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx index 2311b21e..150c22f6 100644 --- a/site/pages/concepts/multi-step-cast-actions.mdx +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -98,7 +98,7 @@ app.frame('/', (c) => { Next, we will add a route handler to handle the Cast Action request. -To specify the name and icon for your action, the next properties are used in the action handler definition: +The following properties can be used in the handler options object: 1. `name` property is used to set the name of the action. It must be less than 30 characters 2. `icon` property is used to associate your Multi-step Cast Action with one of the Octicons. You can see the supported list [here](https://warpcast.notion.site/Spec-Farcaster-Actions-84d5a85d479a43139ea883f6823d8caa). 3. (optional) `description` property is used to describe your action, up to 80 characters. From 19dad7fc6266b3490eca9e29afbffe488df45efb Mon Sep 17 00:00:00 2001 From: Vladyslav Dalechyn Date: Mon, 29 Apr 2024 10:13:28 +0300 Subject: [PATCH 10/22] docs Co-authored-by: jxom --- site/pages/concepts/multi-step-cast-actions.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx index 150c22f6..b01c608a 100644 --- a/site/pages/concepts/multi-step-cast-actions.mdx +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -99,10 +99,10 @@ app.frame('/', (c) => { Next, we will add a route handler to handle the Cast Action request. The following properties can be used in the handler options object: -1. `name` property is used to set the name of the action. It must be less than 30 characters -2. `icon` property is used to associate your Multi-step Cast Action with one of the Octicons. You can see the supported list [here](https://warpcast.notion.site/Spec-Farcaster-Actions-84d5a85d479a43139ea883f6823d8caa). -3. (optional) `description` property is used to describe your action, up to 80 characters. -4. (optional) `aboutUrl` property is used to show an "About" link when installing an action. +- `name`: Used to set the name of the action. It must be less than 30 characters +- `icon`: Used to associate your Multi-step Cast Action with one of the Octicons. You can see the supported list [here](https://warpcast.notion.site/Spec-Farcaster-Actions-84d5a85d479a43139ea883f6823d8caa). +- `description` (optional): Used to describe your action, up to 80 characters. +- `aboutUrl` (optional): Used to show an "About" link when installing an action. Let's define a `/hello-world` route to handle the the Multi-step Cast Action: From b0366a51e4ba1f02a8aa7b4c848e5538d80937be Mon Sep 17 00:00:00 2001 From: Vladyslav Dalechyn Date: Mon, 29 Apr 2024 10:13:49 +0300 Subject: [PATCH 11/22] docs Co-authored-by: jxom --- site/pages/concepts/multi-step-cast-actions.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx index b01c608a..36206c4c 100644 --- a/site/pages/concepts/multi-step-cast-actions.mdx +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -147,7 +147,6 @@ app.castAction( // [!code focus] A breakdown of the `/hello-world` route handler: -- `c.actionData` is never nullable and is always defined since Multi-step Cast Actions always do `POST` request. - We are responding with a `c.res` response and specifying a `message` that will appear in the success toast. ### 3. Defining a frame handler From e3e0a51ef8656f391b313b630ed2d89f8147e686 Mon Sep 17 00:00:00 2001 From: Vladyslav Dalechyn Date: Mon, 29 Apr 2024 10:14:24 +0300 Subject: [PATCH 12/22] docs Co-authored-by: jxom --- site/pages/concepts/multi-step-cast-actions.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx index 36206c4c..4f26fa71 100644 --- a/site/pages/concepts/multi-step-cast-actions.mdx +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -150,7 +150,8 @@ A breakdown of the `/hello-world` route handler: - We are responding with a `c.res` response and specifying a `message` that will appear in the success toast. ### 3. Defining a frame handler -Since in the previous step we send a response with `"type": "frame"` and have specified `"action": "/hello-world-frame"`, Client won't be able to resolve it since we have not defined it yet. + +We will need to define a frame handler that will handle the request to the `action` path provided in the previous step: So let's define one! From 60c3ea76f2e40c2ef29a60313b0356e9446b0a47 Mon Sep 17 00:00:00 2001 From: Vladyslav Dalechyn Date: Mon, 29 Apr 2024 10:14:34 +0300 Subject: [PATCH 13/22] docs Co-authored-by: jxom --- site/pages/concepts/multi-step-cast-actions.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx index 4f26fa71..bf04ffc4 100644 --- a/site/pages/concepts/multi-step-cast-actions.mdx +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -153,8 +153,6 @@ A breakdown of the `/hello-world` route handler: We will need to define a frame handler that will handle the request to the `action` path provided in the previous step: -So let's define one! - ```tsx twoslash [src/index.tsx] // @noErrors /** @jsxImportSource hono/jsx */ From bb8bf3305af20315011847dec55323c8292a7223 Mon Sep 17 00:00:00 2001 From: Vladyslav Dalechyn Date: Mon, 29 Apr 2024 10:15:35 +0300 Subject: [PATCH 14/22] docs Co-authored-by: jxom --- site/pages/concepts/multi-step-cast-actions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx index bf04ffc4..cdd74c0a 100644 --- a/site/pages/concepts/multi-step-cast-actions.mdx +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -196,7 +196,7 @@ app.frame('/hello-world-frame', (c) => { // [!code focus] }) // [!code focus] ``` -Now we're all set! You can use existing Frame API to connect multiple frames together to have a multi-step experience. (see the [Connecting Frames (Actions)](/concepts/actions)). +Now we're all set! You can use existing Frame API to connect multiple frames together to have a multi-step experience. See [Connecting Frames (Actions)](/concepts/actions). ### 4. Bonus: Shorthand `c.frame` From 626a154ae894ea5e5a8ccebda746f9a8caec09d2 Mon Sep 17 00:00:00 2001 From: Vladyslav Dalechyn Date: Mon, 29 Apr 2024 10:15:54 +0300 Subject: [PATCH 15/22] docs Co-authored-by: jxom --- site/pages/concepts/multi-step-cast-actions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx index cdd74c0a..1a601485 100644 --- a/site/pages/concepts/multi-step-cast-actions.mdx +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -220,7 +220,7 @@ app.castAction( ### 4. Bonus: Learn the API -You can learn more about the transaction APIs here: +You can learn more about the Cast Action APIs here: - [`Frog.castAction` Reference](/reference/frog-cast-action) - [`Frog.castAction` Context Reference](/reference/frog-cast-action-context) From 7fa9a4a6fbc2af3016bc479f13642d49ec93291b Mon Sep 17 00:00:00 2001 From: Vladyslav Dalechyn Date: Mon, 29 Apr 2024 10:16:09 +0300 Subject: [PATCH 16/22] docs Co-authored-by: jxom --- site/pages/concepts/multi-step-cast-actions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx index 1a601485..e3d661a6 100644 --- a/site/pages/concepts/multi-step-cast-actions.mdx +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -200,7 +200,7 @@ Now we're all set! You can use existing Frame API to connect multiple frames tog ### 4. Bonus: Shorthand `c.frame` -In order not to add property `"type": "frame"` to your `c.res(...)`, you can use a shorthand `c.frame(...)`. +Instead of `c.res({ type: 'frame' })`, you can use a shorthand `c.frame(...)`. ```tsx twoslash [src/index.tsx] // @noErrors From 314626c8a95d0f5f96835cc78c73e2e8d98939b2 Mon Sep 17 00:00:00 2001 From: hotwater Date: Mon, 29 Apr 2024 12:31:23 +0300 Subject: [PATCH 17/22] refactor: requested changes --- site/pages/concepts/cast-actions.mdx | 3 +-- src/frog-base.tsx | 8 ++++---- src/types/castAction.ts | 4 ++-- src/utils/getCastActionContext.ts | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/site/pages/concepts/cast-actions.mdx b/site/pages/concepts/cast-actions.mdx index 7f0ec7b9..80d04400 100644 --- a/site/pages/concepts/cast-actions.mdx +++ b/site/pages/concepts/cast-actions.mdx @@ -142,12 +142,11 @@ app.castAction( // [!code focus] A breakdown of the `/log-this` route handler: -- `c.actionData` is never nullable and is always defined since Cast Actions always do `POST` request. - We are responding with a `c.res` response and specifying a `message` that will appear in the success toast. ### 3. Bonus: Shorthand `c.message` -In order not to add property `"type": "message"` to your `c.res(...)`, you can use a shorthand `c.message(...)`. +Instead of `c.res({ type: 'message' })`, you can use a shorthand `c.message(...)`. ```tsx twoslash [src/index.tsx] // @noErrors diff --git a/src/frog-base.tsx b/src/frog-base.tsx index 5bb1da5d..74294d3f 100644 --- a/src/frog-base.tsx +++ b/src/frog-base.tsx @@ -357,11 +357,11 @@ export class FrogBase< } if (response.data.type === 'frame') { - const action = response.data.action + const framePath = response.data.path return c.json({ - frameUrl: action.startsWith('http') - ? action - : baseUrl + parsePath(action), + frameUrl: framePath.startsWith('http') + ? framePath + : baseUrl + parsePath(framePath), type: 'frame', }) } diff --git a/src/types/castAction.ts b/src/types/castAction.ts index d54ac8f7..8d652ef9 100644 --- a/src/types/castAction.ts +++ b/src/types/castAction.ts @@ -2,11 +2,11 @@ import type { TypedResponse } from './response.js' export type CastActionFrameResponse = { /** - * Action path to the frame + * Path to the frame. * * @example '/my-frame' */ - action: string + path: string } export type CastActionFrameResponseFn = ( diff --git a/src/utils/getCastActionContext.ts b/src/utils/getCastActionContext.ts index 9bff9775..c37aaf08 100644 --- a/src/utils/getCastActionContext.ts +++ b/src/utils/getCastActionContext.ts @@ -49,7 +49,7 @@ export function getCastActionContext< status: 'error', }), frame: (data) => ({ - data: { action: data.action, type: 'frame' }, + data: { path: data.path, type: 'frame' }, format: 'castAction', status: 'success', }), From f6ef76de0723a935d3163c2e88541c812ecbf835 Mon Sep 17 00:00:00 2001 From: hotwater Date: Mon, 29 Apr 2024 13:14:02 +0300 Subject: [PATCH 18/22] fix: playground --- playground/src/castAction.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/src/castAction.tsx b/playground/src/castAction.tsx index 30d38376..395e3681 100644 --- a/playground/src/castAction.tsx +++ b/playground/src/castAction.tsx @@ -76,7 +76,7 @@ export const app = new Frog({ if (Math.random() > 0.5) return c.error({ message: 'Action failed :(' }) return c.frame({ - action: '/action-frame-response', + path: '/action-frame-response', }) }, { From b4a80e5852cd616e3f6ce02ec7b0ad2d92e26d80 Mon Sep 17 00:00:00 2001 From: jxom Date: Tue, 30 Apr 2024 10:09:11 +1000 Subject: [PATCH 19/22] Update site/pages/concepts/multi-step-cast-actions.mdx --- site/pages/concepts/multi-step-cast-actions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx index e3d661a6..eb448002 100644 --- a/site/pages/concepts/multi-step-cast-actions.mdx +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -43,7 +43,7 @@ app.frame('/', (c) => { app.castAction( '/hello-world', (c) => { - return c.res({ type: 'frame', action: '/hello-world-frame' }) + return c.res({ type: 'frame', path: '/hello-world-frame' }) }, { name: "Hello world!", icon: "smiley" }) ) From 0db9486a42e12d7061876073aa6a1de3e1ce6542 Mon Sep 17 00:00:00 2001 From: jxom Date: Tue, 30 Apr 2024 10:09:17 +1000 Subject: [PATCH 20/22] Update site/pages/concepts/multi-step-cast-actions.mdx --- site/pages/concepts/multi-step-cast-actions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx index eb448002..d4dfdf24 100644 --- a/site/pages/concepts/multi-step-cast-actions.mdx +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -180,7 +180,7 @@ app.frame('/', (c) => { app.castAction( '/hello-world', (c) => { - return c.res({ type: 'frame', action: '/hello-world-frame' }) + return c.res({ type: 'frame', path: '/hello-world-frame' }) }, { name: "Hello world!", icon: "smiley" }) ) From 50712c474919f86fdd2fab64303e5e088022485e Mon Sep 17 00:00:00 2001 From: jxom Date: Tue, 30 Apr 2024 10:09:22 +1000 Subject: [PATCH 21/22] Update site/pages/concepts/multi-step-cast-actions.mdx --- site/pages/concepts/multi-step-cast-actions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx index d4dfdf24..9f6030d3 100644 --- a/site/pages/concepts/multi-step-cast-actions.mdx +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -139,7 +139,7 @@ app.castAction( // [!code focus] c.actionData.fid // [!code focus] }`, // [!code focus] ) // [!code focus] - return c.res({ type: 'frame', action: '/hello-world-frame' }) // [!code focus] + return c.res({ type: 'frame', path: '/hello-world-frame' }) // [!code focus] }, // [!code focus] { name: "Hello world!", icon: "smiley" }) // [!code focus] ) // [!code focus] From 1e09f5fc2dafcde3e6f9df4bc7e3d42e6e831ffa Mon Sep 17 00:00:00 2001 From: jxom Date: Tue, 30 Apr 2024 10:09:27 +1000 Subject: [PATCH 22/22] Update site/pages/concepts/multi-step-cast-actions.mdx --- site/pages/concepts/multi-step-cast-actions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/pages/concepts/multi-step-cast-actions.mdx b/site/pages/concepts/multi-step-cast-actions.mdx index 9f6030d3..eefc4951 100644 --- a/site/pages/concepts/multi-step-cast-actions.mdx +++ b/site/pages/concepts/multi-step-cast-actions.mdx @@ -212,7 +212,7 @@ app.castAction( c.actionData.fid }`, ) - return c.frame({ action: '/hello-world-frame' }) // [!code focus] + return c.frame({ path: '/hello-world-frame' }) // [!code focus] }, { name: "Hello world!", icon: "smiley" }) )