From fdea64267fabae0062c00139d5bea695cc9964a1 Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Mon, 25 Mar 2024 16:45:34 +0200 Subject: [PATCH 01/18] feat: createFrames params --- docs/pages/reference/core/createFrames.mdx | 48 +++++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/docs/pages/reference/core/createFrames.mdx b/docs/pages/reference/core/createFrames.mdx index f7b4f66ad..d38f9065c 100644 --- a/docs/pages/reference/core/createFrames.mdx +++ b/docs/pages/reference/core/createFrames.mdx @@ -17,6 +17,51 @@ const handleRequest = frames(async (ctx) => { The function passed to `frames` will be called with the context of a frame action and should return a `FrameDefinition`. +## Parameters + +`createFrames` accepts an optional options object with the following properties: + +### `basePath` + +- Type: `string` + +A string that specifies the base path for all relative URLs in the frame definition. It defaults to `/`. + +### `initialState` + +- Type: generic + +A JSON serializable value that is used if no state is provided in the message or you are on the initial frame. + +### `middleware` + +Type: `FramesMiddleware` + +An array of middleware functions that are called before the frame handler and allows you to inject additional context into the `ctx` parameter passed to each frame handler call. + +Each middleware should return a promise that resolves to the next middleware, or a [Web API `Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), or a `FrameDefinition`. + +#### Example + +```tsx [./app/frames/route.tsx] +const frames = createFrames({ + middleware: [ + async (ctx, next) => { + console.log("Before frame handler"); + const result = await next({name: "Alice"}); + console.log("After frame handler"); + return result; + }, + ], +}); + +const handler = frames(async (ctx) => { + return { + image: {ctx.name}, // Outputs an image with the text "Alice" + }; +}); +``` + ## FrameDefinition `FrameDefinition` is an object that describes a frame. It has the following properties: @@ -84,8 +129,7 @@ const handleRequest = frames(async (ctx) => { aspectRatio: "1:1", }, buttons: [], - headers: { - // [!code focus] + headers: {// [!code focus] // Max cache age in seconds // [!code focus] "Cache-Control": "max-age=0", // [!code focus] }, // [!code focus] From 82c41d83b8b597c463fa98b40e45fe8b5cff6ca5 Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Mon, 25 Mar 2024 16:50:54 +0200 Subject: [PATCH 02/18] fix: clean up example filenames, add steps --- docs/pages/guides/create-frame.mdx | 34 ++++++++++-------- docs/pages/guides/display-frames.mdx | 44 ++++++++++++----------- docs/pages/index.mdx | 23 +++++------- docs/pages/reference/core/Button.mdx | 3 +- docs/pages/reference/core/next/index.mdx | 6 ++-- docs/pages/reference/core/remix/index.mdx | 6 ++-- 6 files changed, 57 insertions(+), 59 deletions(-) diff --git a/docs/pages/guides/create-frame.mdx b/docs/pages/guides/create-frame.mdx index 0528d582d..cc6403853 100644 --- a/docs/pages/guides/create-frame.mdx +++ b/docs/pages/guides/create-frame.mdx @@ -1,5 +1,5 @@ --- -title: "Guide: Display Frames in your app" +title: "Guide: Create your first Frame" description: "Frames.js is the react based framework for making frames. Debugger included." --- @@ -8,21 +8,26 @@ description: "Frames.js is the react based framework for making frames. Debugger This guide shows you how to add frames rendering to your next.js + tailwind app using frames.js. ## Steps +::::steps +### Create a new repo -1. Create a new repo +Create a new Next.js app -`npx create-next-app@latest my-project --ts --eslint --tailwind --app` +```sh +npx create-next-app@latest my-project --ts --eslint --tailwind --app +cd my-project +``` -`cd my-project` +Add `frames.js` to your project -`yarn add frames.js` +```sh +yarn add frames.js +``` -`yarn install` -2. Create your Frames app +### Create your Frames app -```tsx filename="// ./app/frames/route.tsx" -// ./app/frames/route.tsx +```tsx [./app/frames/route.tsx] /* eslint-disable react/jsx-key */ import { createFrames, Button } from "frames.js/next"; @@ -51,10 +56,9 @@ export const GET = handleRequest; export const POST = handleRequest; ``` -3. If you have an existing page, render Frames in your metadata +### If you have an existing page, render Frames in your metadata -```tsx filename="// ./app/page.tsx" -// ./app/page.tsx +```tsx [./app/page.tsx] import { fetchMetadata } from "frames.js/next"; export async function generateMetadata() { @@ -72,6 +76,8 @@ export default function Page() { } ``` -4. Run `yarn run dev` +### Run `yarn run dev` + +### Done! 🎉 -5. Done! 🎉 +:::: diff --git a/docs/pages/guides/display-frames.mdx b/docs/pages/guides/display-frames.mdx index 6506f5617..298bd9fdd 100644 --- a/docs/pages/guides/display-frames.mdx +++ b/docs/pages/guides/display-frames.mdx @@ -9,26 +9,32 @@ This guide shows you how to add frames rendering to your next.js + tailwind app ## Steps -1. Create a new repo +::::steps -`npx create-next-app@latest my-project --ts --eslint --tailwind --app` +### Create a new repo -`cd my-project` -`yarn add @frames.js/render` +Create a new Next.js app -`yarn install` +```sh +npx create-next-app@latest my-project --ts --eslint --tailwind --app +cd my-project +``` + +Add `@frames.js/render` to your project -2. Add proxies for routing frame requests via your backend for privacy + preventing CORS issues -```tsx filename="// ./app/frames/route.tsx" -// ./app/frames/route.tsx +```sh +yarn add @frames.js/render +``` + +### Add proxies for routing frame requests via your backend for privacy + preventing CORS issues +```tsx [./app/frames/route.tsx] export { GET, POST } from "@frames.js/render/next"; ``` -3. Add the renderer to your page +### Add the renderer to your page -```tsx filename="// ./app/page.tsx" -// ./app/page.tsx +```tsx [./app/page.tsx] "use client"; import { FrameUI, @@ -81,10 +87,9 @@ export default function Page() { ``` -4. In order for the styles to work, your project should have tailwind set up as well as the tailwind.config.js rule +### In order for the styles to work, your project should have tailwind set up as well as the tailwind.config.js rule -```tsx filename="// tailwind.config.js" -// tailwind.config.js +```tsx [tailwind.config.js] const config = { // ... content: [ @@ -97,10 +102,9 @@ const config = { } ``` -5. Allow images from any domain +### Allow images from any domain -```tsx filename="// next.config.js" -// next.config.js +```tsx [next.config.js] const nextConfig = { images: { remotePatterns: [ @@ -113,10 +117,10 @@ const nextConfig = { }; ``` -6. Run `yarn run dev` - -7. Done! 🎉 +### Run `yarn run dev` +### Done! 🎉 +:::: ### Optional diff --git a/docs/pages/index.mdx b/docs/pages/index.mdx index d91135701..d4ce3b591 100644 --- a/docs/pages/index.mdx +++ b/docs/pages/index.mdx @@ -65,9 +65,7 @@ pnpm create frames yarn add frames.js ``` -```tsx filename="// ./app/page.tsx" -// ./app/page.tsx - +```tsx [./app/page.tsx] import { fetchMetadata } from "frames.js/next"; export async function generateMetadata() { @@ -89,8 +87,7 @@ export default function Home(props) { } ``` -```ts filename="./app/frames/route.tsx" -// ./app/frames/route.tsx +```ts [./app/frames/route.tsx] /* eslint-disable react/jsx-key */ import { createFrames, Button } from 'frames.js/next'; @@ -126,14 +123,12 @@ Check out the following places for more Frames-related content: Or use the [hosted Frames debugger](https://debugger.framesjs.org/?url=https%3A%2F%2Fframesjs.org). Running locally has the benefits of it working with natively with localhost. -## Prefer to not use JSX? - -### Use frames.js in Next.js using helper functions +## Prefer to not use JSX? -```tsx filename="./app/page.tsx" -// page that renders a frame -// ./app/page.tsx +### frames.js in Next.js using helper functions +```tsx [./app/page.tsx] +// Page that returns a frame import { Frame, getFrameFlattened } from "frames.js"; import type { Metadata } from "next"; @@ -164,10 +159,8 @@ export const metadata: Metadata = { }; ``` -```ts filename="app/frames/route.ts" -// handle frame actions -// ./app/frames/route.ts - +```ts [./app/frames/route.ts] +// Route that handles frame actions import { getFrameHtml, validateFrameMessage } from "frames.js"; import { NextRequest } from "next/server"; diff --git a/docs/pages/reference/core/Button.mdx b/docs/pages/reference/core/Button.mdx index 38daa078a..7fad906b4 100644 --- a/docs/pages/reference/core/Button.mdx +++ b/docs/pages/reference/core/Button.mdx @@ -114,8 +114,7 @@ type EthSendTransactionParams { If the transaction is successful, the frame will send a POST request to the URL specified in `post_url` or the frame `post_url`. This can be handled by the frame to show a success message or redirect the user to a different page. -```tsx -// /route.tsx +```tsx [route.tsx] import { Button } from "frames.js/next"; import { createFrames } from "frames.js/next"; diff --git a/docs/pages/reference/core/next/index.mdx b/docs/pages/reference/core/next/index.mdx index 12a719491..1324c6a91 100644 --- a/docs/pages/reference/core/next/index.mdx +++ b/docs/pages/reference/core/next/index.mdx @@ -8,8 +8,7 @@ Frames.js can be easily integrated with [Next.js](https://nextjs.org) applicatio Frames handler is responsible for rendering your Frames and also reacts to user interactions with buttons. -```tsx -// app/frames/route.tsx +```tsx [./app/frames/route.tsx] /* eslint-disable react/jsx-key */ import { createFrames, Button } from "frames.js/next"; @@ -29,8 +28,7 @@ export const POST = handleRequest; In order to render the initial frame of your Frames app on some of your pages, you need to render frame metadata. That can be achieved using [`generateMetadata() API`](https://nextjs.org/docs/app/api-reference/functions/generate-metadata). -```tsx -// app/page.tsx +```tsx [./app/page.tsx] import { fetchMetadata } from "frames.js/next"; export async function generateMetadata() { diff --git a/docs/pages/reference/core/remix/index.mdx b/docs/pages/reference/core/remix/index.mdx index 5649e9e0a..2282f209a 100644 --- a/docs/pages/reference/core/remix/index.mdx +++ b/docs/pages/reference/core/remix/index.mdx @@ -8,8 +8,7 @@ Frames.js can be easily integrated with [Remix](https://remix.run) applications. Frames handler is responsible for rendering your Frames and also reacts to user interactions with buttons. -```tsx -// app/routes/frames.tsx +```tsx [./app/routes/frames.tsx] /* eslint-disable react/jsx-key */ import { createFrames, Button } from "frames.js/remix"; @@ -27,8 +26,7 @@ export const loader = handleRequest; ### Render initial frame on your existing page -```tsx -// app/routes/_index.tsx +```tsx [./app/routes/_index.tsx] import { fetchMetadata } from "frames.js/remix"; export async function loader({ request }) { From 166b785443f1c7d953f42f3a330ebac8f5dc10ee Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Mon, 25 Mar 2024 17:04:13 +0200 Subject: [PATCH 03/18] fix: frames.js for apps, @frames.js/render imports in docs --- .../guides/{ => apps}/display-frames.mdx | 0 docs/pages/reference/render/frame-ui.mdx | 8 ++-- .../reference/render/next/frame-image.mdx | 8 ++-- docs/pages/reference/render/next/get.mdx | 4 +- docs/pages/reference/render/next/post.mdx | 4 +- docs/pages/reference/render/types.mdx | 6 +-- docs/pages/reference/render/use-frame.mdx | 23 ++++----- docs/vocs.config.tsx | 48 +++++++++++-------- 8 files changed, 53 insertions(+), 48 deletions(-) rename docs/pages/guides/{ => apps}/display-frames.mdx (100%) diff --git a/docs/pages/guides/display-frames.mdx b/docs/pages/guides/apps/display-frames.mdx similarity index 100% rename from docs/pages/guides/display-frames.mdx rename to docs/pages/guides/apps/display-frames.mdx diff --git a/docs/pages/reference/render/frame-ui.mdx b/docs/pages/reference/render/frame-ui.mdx index eb062c277..3611f3807 100644 --- a/docs/pages/reference/render/frame-ui.mdx +++ b/docs/pages/reference/render/frame-ui.mdx @@ -3,9 +3,9 @@ ## Usage ```tsx [frames.js/render/types.tsx] -import { FrameUI, fallbackFrameContext } from 'frames.js/render'; -import { FrameImageNext } from "frames.js/render/next"; -import { useFrame } from "frames.js/render/use-frame"; +import { FrameUI, fallbackFrameContext } from "@frames.js/render'; +import { FrameImageNext } from "@frames.js/render/next"; +import { useFrame } from "@frames.js/render/use-frame"; export const Page(){ @@ -35,4 +35,4 @@ const config = { "./node_modules/frames.js/dist/render/*.{ts,tsx,js,css}", "./node_modules/frames.js/dist/**/*.{ts,tsx,js,css}", ] -``` \ No newline at end of file +``` diff --git a/docs/pages/reference/render/next/frame-image.mdx b/docs/pages/reference/render/next/frame-image.mdx index 10141f77e..278daa50f 100644 --- a/docs/pages/reference/render/next/frame-image.mdx +++ b/docs/pages/reference/render/next/frame-image.mdx @@ -3,9 +3,9 @@ ## Usage ```tsx [frames.js/render/types.tsx] -import { FrameUI, fallbackFrameContext } from 'frames.js/render'; -import { useFrame } from 'frames.js/render/use-frame'; -import { FrameImageNext } from "frames.js/render/next"; +import { FrameUI, fallbackFrameContext } from "@frames.js/render'; +import { useFrame } from "@frames.js/render/use-frame'; +import { FrameImageNext } from "@frames.js/render/next"; export const Page(){ // ... @@ -18,4 +18,4 @@ export const Page(){ ); } -``` \ No newline at end of file +``` diff --git a/docs/pages/reference/render/next/get.mdx b/docs/pages/reference/render/next/get.mdx index bbf8b888d..b6dabd9fd 100644 --- a/docs/pages/reference/render/next/get.mdx +++ b/docs/pages/reference/render/next/get.mdx @@ -3,5 +3,5 @@ ## Usage ```tsx [./frames/route.tsx] -export { GET, POST } from 'frames.js/render/next'; -``` \ No newline at end of file +export { GET, POST } from "@frames.js/render/next'; +``` diff --git a/docs/pages/reference/render/next/post.mdx b/docs/pages/reference/render/next/post.mdx index 9c36324b2..d372a38fb 100644 --- a/docs/pages/reference/render/next/post.mdx +++ b/docs/pages/reference/render/next/post.mdx @@ -3,5 +3,5 @@ ## Usage ```tsx [./frames/route.tsx] -export { GET, POST } from 'frames.js/render/next'; -``` \ No newline at end of file +export { GET, POST } from "@frames.js/render/next'; +``` diff --git a/docs/pages/reference/render/types.mdx b/docs/pages/reference/render/types.mdx index bcb838fe1..14b3668f3 100644 --- a/docs/pages/reference/render/types.mdx +++ b/docs/pages/reference/render/types.mdx @@ -3,7 +3,7 @@ ## Example usage ```tsx -import { SignerStateInstance } from "frames.js/render"; +import { SignerStateInstance } from "@frames.js/render"; ``` ## Reference @@ -142,6 +142,4 @@ export type FrameTheme = Partial>; export interface FrameActionBodyPayload {} export type FrameContext = FarcasterFrameContext; - - -``` \ No newline at end of file +``` diff --git a/docs/pages/reference/render/use-frame.mdx b/docs/pages/reference/render/use-frame.mdx index 1e37aaee4..299b47d86 100644 --- a/docs/pages/reference/render/use-frame.mdx +++ b/docs/pages/reference/render/use-frame.mdx @@ -18,22 +18,21 @@ type Props = { onMint?: (t: onMintArgs) => void; /** the context of this frame, used for generating Frame Action payloads */ frameContext: FrameContext; -} +}; ``` ## Usage ```tsx [frames.js/render/types.tsx] "use client"; -import { - FrameUI, - fallbackFrameContext, - FrameContext, -} from "frames.js/render"; -import { FrameImageNext } from "frames.js/render/next"; +import { FrameUI, fallbackFrameContext, FrameContext } from "@frames.js/render"; +import { FrameImageNext } from "@frames.js/render/next"; import { FrameButton } from "frames.js"; -import { useFrame } from "frames.js/render/use-frame"; -import { mockFarcasterSigner, createFrameActionMessageWithSignerKey } from "frames.js/render/farcaster"; +import { useFrame } from "@frames.js/render/use-frame"; +import { + mockFarcasterSigner, + createFrameActionMessageWithSignerKey, +} from "@frames.js/render/farcaster"; export default function Page() { const frameState = useFrame({ @@ -54,7 +53,9 @@ export default function Page() { // Implement me alert("A frame button was pressed without a signer."); }, - signFrameAction: () => { alert('implement me.') }, + signFrameAction: () => { + alert("implement me."); + }, }, }); @@ -64,4 +65,4 @@ export default function Page() { } ``` -[Full example](/guides/display-frames.mdx) \ No newline at end of file +[Full example](/guides/apps/display-frames.mdx) diff --git a/docs/vocs.config.tsx b/docs/vocs.config.tsx index 000647e80..da3cf2784 100644 --- a/docs/vocs.config.tsx +++ b/docs/vocs.config.tsx @@ -50,16 +50,22 @@ const sidebar = [ text: "Transactions", link: "/guides/transactions", }, - { - text: "Display Frames", - link: "/guides/display-frames", - }, { text: "Open Frames", link: "/guides/open-frames", }, ], }, + { + text: "Frames.js for Apps", + collapsed: false, + items: [ + { + text: "Display Frames", + link: "/guides/apps/display-frames", + }, + ], + }, { text: "Write your frame with", collapsed: false, @@ -232,7 +238,7 @@ const sidebar = [ ], }, { - text: "frames.js/render", + text: "@frames.js/render", collapsed: true, items: [ { @@ -247,23 +253,23 @@ const sidebar = [ text: "FrameUI", link: "/reference/render/frame-ui", }, - ], - }, - { - text: "frames.js/render/next", - collapsed: true, - items: [ - { - text: "FrameImage", - link: "/reference/render/next/frame-image", - }, { - text: "POST", - link: "/reference/render/next/POST", - }, - { - text: "GET", - link: "/reference/render/next/GET", + text: "Next.js", + collapsed: true, + items: [ + { + text: "FrameImage", + link: "/reference/render/next/frame-image", + }, + { + text: "POST", + link: "/reference/render/next/POST", + }, + { + text: "GET", + link: "/reference/render/next/GET", + }, + ], }, ], }, From e0753e9f10acbf407754d676a88e3776f33ff168 Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Mon, 25 Mar 2024 17:29:09 +0200 Subject: [PATCH 04/18] feat: context reference --- .changeset/new-emus-love.md | 8 +++ docs/pages/reference/core/createFrames.mdx | 68 ++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 .changeset/new-emus-love.md diff --git a/.changeset/new-emus-love.md b/.changeset/new-emus-love.md new file mode 100644 index 000000000..e0434dd52 --- /dev/null +++ b/.changeset/new-emus-love.md @@ -0,0 +1,8 @@ +--- +"docs": patch +--- + +- Adds default context reference to `createFrames` reference +- "Frames.js for apps" section +- Clean up example filenames, add steps +- `createFrames` params diff --git a/docs/pages/reference/core/createFrames.mdx b/docs/pages/reference/core/createFrames.mdx index d38f9065c..32a9870d2 100644 --- a/docs/pages/reference/core/createFrames.mdx +++ b/docs/pages/reference/core/createFrames.mdx @@ -146,3 +146,71 @@ handleRequest(new Request("/")).then((res) => { return res.text(); }); ``` + +## Context + +Core middleware is included and executed by default and gives you access to the following default context in your frame handlers: + +### `basePath` + +- Type: `string` + +Specifies the base path for all relative URLs in the frame definition. + +### `initialState` + +- Type: generic + +A JSON serializable value that is used if no state is provided in the message or you are on the initial frame. + +### `request` + +- Type: [Web API `Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) + +The request object that was passed to the request handler. + +### `url` + +- Type: [Web API `URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) + +The URL object that was parsed from the request. + +### `searchParams` + +- Type: `Record` + +The search params in the URL as an object. If there are no search params, it will be an empty object. + +### `pressedButton` + +- Type: `undefined | { action: "post" | "post_redirect"; index: 1 | 2 | 3 | 4 }` + +The button that was clicked on the previous frame. + +### `buttonIndex` + +- Type: `number` + +The index of the button that was clicked on the previous frame. + +### `message` + +- Type: `FrameMessage` + +The frame message that was parsed from the request body. + +### `clientProtocol` + +- Type: `ClientProtocolId` + +The client protocol that was used to send the frame message. + +### `state` + +- Type: `JsonValue` + +The state extracted from the frame message. If you are on the initial frame (no button pressed), the value is the `initialState` value passed to `createFrames`. If you are on a frame with a button pressed, the value is the state from the previous frame. + + + + From d65714fb36d8295ef616c4e83fb215e9280ce43b Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Mon, 25 Mar 2024 21:54:40 +0200 Subject: [PATCH 05/18] feat: add note about per-route middleware --- docs/pages/reference/core/createFrames.mdx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/pages/reference/core/createFrames.mdx b/docs/pages/reference/core/createFrames.mdx index 32a9870d2..6b30573d9 100644 --- a/docs/pages/reference/core/createFrames.mdx +++ b/docs/pages/reference/core/createFrames.mdx @@ -147,6 +147,24 @@ handleRequest(new Request("/")).then((res) => { }); ``` +### Per-route middleware + +You can also pass middleware to the `handleRequest` function to be executed only for that specific route. + +```tsx +const handleRequest = frames(async (ctx) => { + return { + image: Test, + }; +}, { + middleware: [ + farcasterHubContext({ hubHttpUrl: process.env.HUB_HTTP_URL }), + ] +}); +``` + +This will only execute the `farcasterHubContext` middleware for the route that `handleRequest` is called with. + ## Context Core middleware is included and executed by default and gives you access to the following default context in your frame handlers: From 856428a507956d14477ffd84cf0d6a44cd9ca57c Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:51:18 +0200 Subject: [PATCH 06/18] feat: troubleshooting --- docs/pages/troubleshooting.mdx | 139 +++++++++++++++++++++++++++++++++ docs/vocs.config.tsx | 4 + 2 files changed, 143 insertions(+) create mode 100644 docs/pages/troubleshooting.mdx diff --git a/docs/pages/troubleshooting.mdx b/docs/pages/troubleshooting.mdx new file mode 100644 index 000000000..bb6479f4d --- /dev/null +++ b/docs/pages/troubleshooting.mdx @@ -0,0 +1,139 @@ +# Troubleshooting + +## Image not rendering + +### Image too large + +If the image is not rendering, it may be too large. + +- If you are including a full size external `` element, consider passing the external image as a string instead: + +```tsx +return { + image: "https://example.com/image.jpg", +}; +``` + +- Try resizing it to a smaller size via the `imageOptions` of the returned `FrameDefinition`. + +```tsx +return { + image:
...
, + imageOptions: { + width: 100, + height: 100, + }, +}; +``` + +### Image wrong format + +If the image is not rendering, it may be in the wrong format. SVG images are typically not supported on mobile. + +## Initial frame not loading + +Ensure that the `fetchMetadata` URL is correct and that it is inside the `other` folder for Next.js. + +### `VERCEL_URL` environment variable + +If you are using Vercel, the `VERCEL_URL` environment variable is not a fully qualified URL and may cause issues with `fetchMetadata`. You will have to prepend the protocol `VERCEL_URL`. + +```tsx +export async function generateMetadata() { + const frameMetadata = await fetchMetadata( + new URL( + "/frames", + process.env.VERCEL_URL + ? `https://${process.env.VERCEL_URL}` + : "http://localhost:3000" + ) + ); + + return { + title: "My page", + other: { + ...frameMetadata, + }, + }; +} +``` + +### Vercel authentication + +When deploying to Vercel, your site will not automatically be accessible to the public. You will need to disable Vercel Authentication under `Settings > Deployment Protection > Vercel Authentication` + +## Import type errors + +If you are getting type errors when importing `frames.js`, you may need to change the `moduleResolution` in your `tsconfig.json` from `node` to `nodenext`. + +```json +{ + "compilerOptions": { + "moduleResolution": "nodenext" + } +} +``` + +## Unable to access frame message on initial frame + +The initial frame is accessed via a GET request and does not have access to frame message and hence user data. + +## Type error: Route ... does not match the required types of a Next.js Route + +If you are getting this error you are exporting something other than a Next.js route from a `route.tsx` or `page.tsx` file. + +We recommend creating a new file for your `frames` app and importing it in the routes that use it. + +### Example + +Frames app file + +```tsx [frames.ts] +import { createFrames } from "frames.js/next"; + +export const frames = createFrames({ + basePath: "/frames", +}); +``` + +Initial page route + +```tsx [route.tsx] +import { frames } from "./frames"; + +export const GET = frames(async (ctx) => { + // ctx.message is not available in the initial frame + return { + image:
...
, + buttons: [ + , + ], + }; +}); +``` + +Frame action handler route + +```tsx [my-route/route.tsx] +import { frames } from "../frames"; + +export const POST = frames(async (ctx) => { + // Do something with ctx.message + // ... + + return { + image:
...
, + buttons: [ + // ... + ], + }; +}); +``` + +## Combining old and new SDKs + +You cannot use the ``, `` and `` components from the old SDK (`frames.js/next/server`) with the new SDK (`frames.js/next`). + +The new SDK uses a [`FrameDefinition`](/reference/core/createFrames#framedefinition) object to define a frame. diff --git a/docs/vocs.config.tsx b/docs/vocs.config.tsx index da3cf2784..e3eccd0ec 100644 --- a/docs/vocs.config.tsx +++ b/docs/vocs.config.tsx @@ -102,6 +102,10 @@ const sidebar = [ }, ], }, + { + text: "Troubleshooting", + link: "/troubleshooting", + }, { text: "Reference", // link: "/reference", From b2e96c0051fde191f04d92c0d1d2569498f46da4 Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:51:50 +0200 Subject: [PATCH 07/18] fix: FrameDefinition types --- docs/pages/reference/core/createFrames.mdx | 83 +++++++++++++--------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/docs/pages/reference/core/createFrames.mdx b/docs/pages/reference/core/createFrames.mdx index 6b30573d9..c30ea287d 100644 --- a/docs/pages/reference/core/createFrames.mdx +++ b/docs/pages/reference/core/createFrames.mdx @@ -66,43 +66,58 @@ const handler = frames(async (ctx) => { `FrameDefinition` is an object that describes a frame. It has the following properties: +### `image` + +- Type: `React.ReactElement | string` + +The image to be rendered in the frame. If a string is provided, it must be a valid URL. + +### `imageOptions` + +- Type: `{ aspectRatio?: "1.91:1" | "1:1" } & ConstructorParameters[1]` + +Options for the image. The `aspectRatio` property can be set to `"1.91:1"` or `"1:1"`. + +### `buttons` + +- Type: 1, 2, 3, or 4 `FrameButtonElement` elements + +An array of buttons to be rendered in the frame. The buttons are rendered in the order they are provided. + +#### Example + ```tsx -/** - * Frame definition, this is rendered by the frames - */ -export type FrameDefinition = { - /** - * If string then it must be a valid URL - */ - image: React.ReactElement | string; - imageOptions?: { - /** - * @default '1.91:1' - */ - aspectRatio?: "1.91:1" | "1:1"; - } & ConstructorParameters[1]; - buttons?: - | [] - | [FrameButtonElement] - | [FrameButtonElement, FrameButtonElement] - | [FrameButtonElement, FrameButtonElement, FrameButtonElement] - | [ - FrameButtonElement, - FrameButtonElement, - FrameButtonElement, - FrameButtonElement, - ]; - /** - * Label for text input, if no value is provided the input is not rendered - */ - textInput?: string; - /** - * Global app state that will be available on next frame - */ - state?: JsonValue; -} & ResponseInit; +import { Button } from "frames.js/next"; + +const handleRequest = frames(async (ctx) => { + return { + image: Test, + buttons: [ + , + , + ], + }; +}); ``` +### `textInput` + +- Type: `string` + +Label for text input. If no value is provided, the input is not rendered. + +### `state` + +- Type: `JsonValue` + +Global app state that will be available on the next frame. + +### `headers` + +- Type: `HeadersInit` + +Custom headers to be included in the response. + The `ResponseInit` properties allow you to specify custom headers such as `Cache-Control`. ### Cache-Control From e9acd3a7fbcefd3c43b8e57aee12c118cd93f860 Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:08:10 +0200 Subject: [PATCH 08/18] fix: troubleshooting file name --- docs/pages/troubleshooting.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/troubleshooting.mdx b/docs/pages/troubleshooting.mdx index bb6479f4d..53333f68a 100644 --- a/docs/pages/troubleshooting.mdx +++ b/docs/pages/troubleshooting.mdx @@ -66,7 +66,7 @@ When deploying to Vercel, your site will not automatically be accessible to the If you are getting type errors when importing `frames.js`, you may need to change the `moduleResolution` in your `tsconfig.json` from `node` to `nodenext`. -```json +```json [tsconfig.json] { "compilerOptions": { "moduleResolution": "nodenext" From fa4e870b2b607d04af55faa9286be67ae5ab9e94 Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Tue, 26 Mar 2024 17:24:15 +0200 Subject: [PATCH 09/18] feat: middleware docs, minor improvements --- docs/pages/guides/create-frame.mdx | 11 +- docs/pages/guides/middleware.mdx | 134 +++++++++++++++------ docs/pages/reference/core/Button.mdx | 2 +- docs/pages/reference/core/createFrames.mdx | 42 ++++--- docs/pages/troubleshooting.mdx | 2 +- 5 files changed, 139 insertions(+), 52 deletions(-) diff --git a/docs/pages/guides/create-frame.mdx b/docs/pages/guides/create-frame.mdx index cc6403853..f6e9fbae4 100644 --- a/docs/pages/guides/create-frame.mdx +++ b/docs/pages/guides/create-frame.mdx @@ -27,11 +27,18 @@ yarn add frames.js ### Create your Frames app +```tsx [./app/frames/frames.ts] +import { createFrames } from "frames.js/next"; + +export const frames = createFrames(); +``` + +### Create a route ```tsx [./app/frames/route.tsx] /* eslint-disable react/jsx-key */ -import { createFrames, Button } from "frames.js/next"; +import { Button } from "frames.js/next"; +import { frames } from "./frames"; -const frames = createFrames(); const handleRequest = frames(async (ctx) => { return { image: ( diff --git a/docs/pages/guides/middleware.mdx b/docs/pages/guides/middleware.mdx index d3e0de689..11f253e50 100644 --- a/docs/pages/guides/middleware.mdx +++ b/docs/pages/guides/middleware.mdx @@ -7,47 +7,113 @@ description: "" Frames.js uses middleware to extend the functionality of Frames, bringing in data from API providers, verifying frame actions and adding Open Frames support. -You can use middleware for all your frames by passing in middleware via the `middleware` Option. +You can use middleware for all your frames by passing in middleware via the `middleware` option in `createFrames` or you can specify per-route middleware. -```tsx -import { farcasterHubContext, openframes } from "frames.js/middleware"; -import { createFrames } from "frames.js/next"; -import { getXmtpFrameMessage, isXmtpFrameActionPayload } from "frames.js/xmtp"; +## Using middleware -const DEFAULT_DEBUGGER_URL = - process.env.DEBUGGER_URL ?? "http://localhost:3010/"; +Include the middleware in your `createFrames` call: -export const DEFAULT_DEBUGGER_HUB_URL = - process.env.NODE_ENV === "development" - ? new URL("/hub", DEFAULT_DEBUGGER_URL).toString() - : undefined; +```tsx [frames.ts] +import { farcasterHubContext } from "frames.js/middleware"; +import { createFrames } from "frames.js/next"; const frames = createFrames({ basePath: "/", initialState: { pageIndex: 0, }, - middleware: [ - farcasterHubContext({ - hubHttpUrl: DEFAULT_DEBUGGER_HUB_URL, - }), - openframes({ - clientProtocol: { - id: "xmtp", - version: "2024-02-09", - }, - handler: { - isValidPayload: (body: JSON) => isXmtpFrameActionPayload(body), - getFrameMessage: async (body: JSON) => { - if (!isXmtpFrameActionPayload(body)) { - return undefined; - } - const result = await getXmtpFrameMessage(body); - - return { ...result }; - }, - }, - }), - ], + middleware: [farcasterHubContext()], +}); +``` + +```tsx [frames/username/route.tsx] +import { frames } from "./frames"; + +export const POST = frames(async (ctx) => { + // The added context from the middleware will be available on `ctx` here + if (!ctx.message.isValid) { + throw new Error("Invalid message"); + } + + return { + image: ( +
The user's username is {ctx.message.requesterUserData.username}
+ ), + }; +}); +``` + +### Per-route middleware + +You can also specify middleware per-route that will only be applied to that route: + +```tsx [frames/username/route.tsx] +import { farcasterHubContext } from "frames.js/middleware"; + +export const POST = frames( + async (ctx) => { + // The added context from the middleware will be available on `ctx` here + if (!ctx.message.isValid) { + throw new Error("Invalid message"); + } + + return { + image: ( +
+ The user's username is {ctx.message.requesterUserData.username} +
+ ), + }; + }, + { + middleware: [farcasterHubContext()], + } +); +``` + +## Defining your own middleware + +You can define your own middleware by creating a function that returns a promise that resolves to the next middleware, or a [Web API `Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), or a `FrameDefinition`. + +Middleware can modify the context or return a response that will terminate the request early. + +### Adding context + +:::code-group + +```tsx [frames.ts] +import { createFrames, types } from "frames.js/next"; + +const myMiddleware: types.FramesMiddleware< + any, + { foo: string } +> = async (ctx, next) => { + return next({ foo: "bar" }); +}; + +export const frames = createFrames({ + basePath: "/", + initialState: { + pageIndex: 0, + }, + // Add the middleware + middleware: [myMiddleware], }); -``` \ No newline at end of file +``` + +```tsx [frames/route.tsx] +import { Button } from "frames.js/next"; +import { frames } from "./frames"; + +const handler = frames(async (ctx) => { + return { + // Use the additional contect + image:
foo: ${ctx.bar}
, + }; +}); + +export const GET = handler; +export const POST = handler; +``` + +::: diff --git a/docs/pages/reference/core/Button.mdx b/docs/pages/reference/core/Button.mdx index 7fad906b4..919aefccf 100644 --- a/docs/pages/reference/core/Button.mdx +++ b/docs/pages/reference/core/Button.mdx @@ -123,7 +123,7 @@ const export frames = createFrames() export const GET = frames(async (ctx) => { return { image: ( -
+
Execute transaction
), diff --git a/docs/pages/reference/core/createFrames.mdx b/docs/pages/reference/core/createFrames.mdx index c30ea287d..9cbdde563 100644 --- a/docs/pages/reference/core/createFrames.mdx +++ b/docs/pages/reference/core/createFrames.mdx @@ -35,12 +35,29 @@ A JSON serializable value that is used if no state is provided in the message or ### `middleware` +See the [Middleware guide](/guides/middleware) for more information. + Type: `FramesMiddleware` An array of middleware functions that are called before the frame handler and allows you to inject additional context into the `ctx` parameter passed to each frame handler call. Each middleware should return a promise that resolves to the next middleware, or a [Web API `Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), or a `FrameDefinition`. +#### Types + +For strong type support in the handler, the middleware should be typed as `FramesMiddleware`. + +```tsx +import { createFrames, types } from "frames.js/next"; + +const myMiddleware: types.FramesMiddleware< + any, + { foo?: string } +> = async (ctx, next) => { + return next({ foo: "bar" }); +}; +``` + #### Example ```tsx [./app/frames/route.tsx] @@ -48,7 +65,7 @@ const frames = createFrames({ middleware: [ async (ctx, next) => { console.log("Before frame handler"); - const result = await next({name: "Alice"}); + const result = await next({ name: "Alice" }); console.log("After frame handler"); return result; }, @@ -167,15 +184,16 @@ handleRequest(new Request("/")).then((res) => { You can also pass middleware to the `handleRequest` function to be executed only for that specific route. ```tsx -const handleRequest = frames(async (ctx) => { - return { - image: Test, - }; -}, { - middleware: [ - farcasterHubContext({ hubHttpUrl: process.env.HUB_HTTP_URL }), - ] -}); +const handleRequest = frames( + async (ctx) => { + return { + image: Test, + }; + }, + { + middleware: [farcasterHubContext({ hubHttpUrl: process.env.HUB_HTTP_URL })], + } +); ``` This will only execute the `farcasterHubContext` middleware for the route that `handleRequest` is called with. @@ -243,7 +261,3 @@ The client protocol that was used to send the frame message. - Type: `JsonValue` The state extracted from the frame message. If you are on the initial frame (no button pressed), the value is the `initialState` value passed to `createFrames`. If you are on a frame with a button pressed, the value is the state from the previous frame. - - - - diff --git a/docs/pages/troubleshooting.mdx b/docs/pages/troubleshooting.mdx index 53333f68a..27ad5ca51 100644 --- a/docs/pages/troubleshooting.mdx +++ b/docs/pages/troubleshooting.mdx @@ -60,7 +60,7 @@ export async function generateMetadata() { ### Vercel authentication -When deploying to Vercel, your site will not automatically be accessible to the public. You will need to disable Vercel Authentication under `Settings > Deployment Protection > Vercel Authentication` +When deploying to Vercel, your site will not automatically be accessible to the public. You will need to disable Vercel Authentication under your project's `Settings > Deployment Protection > Vercel Authentication` on vercel.com. ## Import type errors From 1a68dc5014acb9f01d4113ce4a53bbadcd89d2c7 Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Tue, 26 Mar 2024 17:44:08 +0200 Subject: [PATCH 10/18] fix: update use-frame docs --- docs/pages/reference/render/use-frame.mdx | 153 +++++++++++++++++++--- 1 file changed, 135 insertions(+), 18 deletions(-) diff --git a/docs/pages/reference/render/use-frame.mdx b/docs/pages/reference/render/use-frame.mdx index 299b47d86..dc0115491 100644 --- a/docs/pages/reference/render/use-frame.mdx +++ b/docs/pages/reference/render/use-frame.mdx @@ -2,24 +2,141 @@ ## Props -```ts -type Props = { - /** the route used to POST frame actions. The post_url will be added as a the `url` query parameter */ - frameActionProxy: string; - /** the route used to GET the initial frame via proxy */ - frameGetProxy: string; - /** an auth state object used to determine what actions are possible */ - signerState: SignerStateInstance; - /** the url of the homeframe, if null won't load a frame */ - homeframeUrl: string | null; - /** the initial frame. if not specified will fetch it from the url prop */ - frame?: Frame; - /** a function to handle mint buttons */ - onMint?: (t: onMintArgs) => void; - /** the context of this frame, used for generating Frame Action payloads */ - frameContext: FrameContext; -}; -``` +### `dangerousSkipSigning` + +- Type: `boolean` + +If true, the frame will not be signed before being sent to the frameActionProxy. This is useful for frames that don't verify signatures. + +### `frameActionProxy` + +- Type: `string` + +The route used to POST frame actions. The post_url will be added as a the `url` query parameter. + +### `frameGetProxy` + +- Type: `string` + +The route used to GET the initial frame via proxy. + +### `signerState` + +- Type: `SignerStateInstance` + +An signer state object used to determine what actions are possible. + +### `homeframeUrl` + +- Type: `string | null` + +The url of the homeframe, if null won't load a frame. + +### `frame` + +- Type: `Frame` + +The initial frame. if not specified will fetch it from the url prop. + +### `onMint` + +- Type: `(t: onMintArgs) => void` + +A function to handle mint buttons. + +### `onTransaction` + +- Type: `OnTransactionFunc` + +A function to handle transaction button presses, returns the transaction hash or null. + +### `frameContext` + +- Type: `FrameContext` + +The context of this frame, used for generating Frame Action payloads. + +### `extraButtonRequestPayload` + +- Type: `Record` + +Extra data appended to the frame action payload. + +## Returns + +- Type: `FrameState` + +### `fetchFrame` + +- Type: `(request: FrameRequest) => void` + +Fetches a frame from the frameGetProxy. + +### `clearFrameStack` + +- Type: `() => void` + +Clears the frame stack. + +### `frame` + +- Type: `Frame | null` + +The frame at the top of the stack (at index 0). + +### `framesStack` + +- Type: `FramesStack` + +A stack of frames with additional context, with the most recent frame at index 0. + +### `isLoading` + +- Type: `null | FrameStackPending` + +Whether the frame is loading. + +### `inputText` + +- Type: `string` + +The input text. + +### `setInputText` + +- Type: `(s: string) => void` + +Sets the input text. + +### `onButtonPress` + +- Type: `(frameButton: FrameButton, index: number) => void` + +Handles a button press. + +### `isFrameValid` + +- Type: `boolean | undefined` + +Whether the frame at the top of the stack has any frame validation errors. Undefined when the frame is not loaded or set. + +### `frameValidationErrors` + +- Type: `Record | undefined | null` + +The frame validation errors. + +### `error` + +- Type: `null | unknown` + +Whether there was an error loading the frame. + +### `homeframeUrl` + +- Type: `string | null` + +The url of the frame. ## Usage From 22ad5850e072a7567a555a2795a3f6fb0d66f6d9 Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Wed, 27 Mar 2024 10:31:31 +0200 Subject: [PATCH 11/18] chore: move old apis to a deprecated apis section --- docs/vocs.config.tsx | 117 ++++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 56 deletions(-) diff --git a/docs/vocs.config.tsx b/docs/vocs.config.tsx index e3eccd0ec..f79d9b67f 100644 --- a/docs/vocs.config.tsx +++ b/docs/vocs.config.tsx @@ -185,62 +185,6 @@ const sidebar = [ }, ], }, - { - text: "frames.js/next/server", - collapsed: true, - items: [ - { - text: "getPreviousFrame", - link: "/reference/nextjs/getPreviousFrame", - }, - { - text: "POST", - link: "/reference/nextjs/POST", - }, - ], - }, - { - text: "frames.js/next/server - [react]", - collapsed: true, - items: [ - { - text: "types", - link: "/reference/react/types", - }, - { - text: "FrameContainer", - link: "/reference/react/FrameContainer", - }, - { - text: "FrameButton", - link: "/reference/react/FrameButton", - }, - { - text: "FrameImage", - link: "/reference/react/FrameImage", - }, - { - text: "FrameInput", - link: "/reference/react/FrameInput", - }, - { - text: "parseFrameParams", - link: "/reference/react/parseFrameParams", - }, - { - text: "useFramesReducer", - link: "/reference/react/useFramesReducer", - }, - { - text: "validateActionSignature", - link: "/reference/react/validateActionSignature", - }, - { - text: "createPreviousFrame", - link: "/reference/react/createPreviousFrame", - }, - ], - }, { text: "@frames.js/render", collapsed: true, @@ -277,6 +221,67 @@ const sidebar = [ }, ], }, + { + text: "Deprecated APIs", + items: [ + { + text: "frames.js/next/server", + collapsed: true, + items: [ + { + text: "getPreviousFrame", + link: "/reference/nextjs/getPreviousFrame", + }, + { + text: "POST", + link: "/reference/nextjs/POST", + }, + ], + }, + { + text: "frames.js/next/server - [react]", + collapsed: true, + items: [ + { + text: "types", + link: "/reference/react/types", + }, + { + text: "FrameContainer", + link: "/reference/react/FrameContainer", + }, + { + text: "FrameButton", + link: "/reference/react/FrameButton", + }, + { + text: "FrameImage", + link: "/reference/react/FrameImage", + }, + { + text: "FrameInput", + link: "/reference/react/FrameInput", + }, + { + text: "parseFrameParams", + link: "/reference/react/parseFrameParams", + }, + { + text: "useFramesReducer", + link: "/reference/react/useFramesReducer", + }, + { + text: "validateActionSignature", + link: "/reference/react/validateActionSignature", + }, + { + text: "createPreviousFrame", + link: "/reference/react/createPreviousFrame", + }, + ], + }, + ], + }, ], }, ]; From db8e61257f951570af3c6c7a999a1cf65d0e65f1 Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Wed, 27 Mar 2024 10:33:31 +0200 Subject: [PATCH 12/18] fix: make button docs more visible, multi-page docs --- docs/pages/guides/create-frame.mdx | 19 ++- docs/pages/guides/multiple-frames.mdx | 185 ++++++++++++++++++++------ docs/pages/reference/core/Button.mdx | 18 ++- 3 files changed, 172 insertions(+), 50 deletions(-) diff --git a/docs/pages/guides/create-frame.mdx b/docs/pages/guides/create-frame.mdx index f6e9fbae4..3e29ef334 100644 --- a/docs/pages/guides/create-frame.mdx +++ b/docs/pages/guides/create-frame.mdx @@ -8,7 +8,9 @@ description: "Frames.js is the react based framework for making frames. Debugger This guide shows you how to add frames rendering to your next.js + tailwind app using frames.js. ## Steps + ::::steps + ### Create a new repo Create a new Next.js app @@ -24,7 +26,6 @@ Add `frames.js` to your project yarn add frames.js ``` - ### Create your Frames app ```tsx [./app/frames/frames.ts] @@ -34,6 +35,7 @@ export const frames = createFrames(); ``` ### Create a route + ```tsx [./app/frames/route.tsx] /* eslint-disable react/jsx-key */ import { Button } from "frames.js/next"; @@ -49,10 +51,10 @@ const handleRequest = frames(async (ctx) => { ), buttons: [ - , - , ], @@ -73,7 +75,12 @@ export async function generateMetadata() { title: "My Page", // provide a full URL to your /frames endpoint other: await fetchMetadata( - new URL("/frames", process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : "http://localhost:3000") + new URL( + "/frames", + process.env.VERCEL_URL + ? `https://${process.env.VERCEL_URL}` + : "http://localhost:3000" + ) ), }; } @@ -88,3 +95,7 @@ export default function Page() { ### Done! 🎉 :::: + +## Next Steps + +- Read the [`createFrames`](/reference/core/createFrames) and [`Button`](/reference/core/Button) documentation diff --git a/docs/pages/guides/multiple-frames.mdx b/docs/pages/guides/multiple-frames.mdx index 2cff40e25..37e30b68f 100644 --- a/docs/pages/guides/multiple-frames.mdx +++ b/docs/pages/guides/multiple-frames.mdx @@ -5,67 +5,168 @@ description: "" # Multi-Page Frames -You will want to connect multiple frames together. -There's two different ways of navigating between frames. +Frames.js can be used to create multi-page applications by defining multiple Frames that are linked together. -The first way is by defining the `state` prop of a `Button`, and using that state to return a different Frame in the handler. See below +## Creating a Multi-Page Application -```tsx -/* eslint-disable react/jsx-key */ -import { createFrames, Button } from "frames.js/next"; +Frames are connected by [`Button`](/reference/core/Button) targets, similar to how Next.js `Link` components work. + +:::steps + +### Create your frames app + +We create a new directory `./frames` with a `frames.ts` file to export our frames application from because it needs to be used from multiple routes. -const totalPages = 5; +```tsx [frames.ts] +import { createFrames } from "frames.js/next"; -const frames = createFrames({ - basePath: "/examples/new-api/frames", - initialState: { - pageIndex: 0, - }, +export const frames = createFrames({ + basePath: "/frames", }); +``` + +### Define your initial route -const handleRequest = frames(async (ctx) => { - const pageIndex = Number(ctx.searchParams.pageIndex || 0); +The first frame is always fetched via a GET request and is typically included alongside existing OpenGraph data via the [`generateMetadata`](https://nextjs.org/docs/app/api-reference/functions/generate-metadata) function in Next.js if you have an existing site. - const imageUrl = `https://picsum.photos/seed/frames.js-${pageIndex}/300/200`; +#### Define the initial frame +Create a `./frames/route.tsx` file that contains your initial frame. This frame will include buttons to navigate to other frames. + +```tsx [route.tsx] +/* eslint-disable react/jsx-key */ +import { frames } from "./frames"; +import { Button } from "frames.js/next"; + +export const GET = frames(async () => { return { - image: ( -
- Image -
- This is slide {pageIndex + 1} / {totalPages} -
-
- ), + image:
Welcome
, buttons: [ + // With query params , - , ], - textInput: "Type something!", }; }); +``` + +#### Export the initial frame metadata + +In your `page.tsx` file, fetch the initial frame's metadata and include it alongside your existing page's metadata. -export const GET = handleRequest; -export const POST = handleRequest; +`fetchMetadata` is a helper function that fetches the metadata for a frame from the frames.js handler and formats it for use in the `generateMetadata` function. + +```tsx [page.tsx] +import { fetchMetadata } from "frames.js/next"; + +export async function generateMetadata() { + return { + title: "My Page", + // provide a full URL to your /frames endpoint + other: await fetchMetadata( + new URL( + "/frames", + process.env.VERCEL_URL + ? `https://${process.env.VERCEL_URL}` + : "http://localhost:3000" + ) + ), + }; +} + +export default function Page() { + return My existing page; +} ``` -The second way to navigate between frames is by defining a `Button` with `type`, `post`, with a `target` that points at another Frame. -This can be a Frame on the same domain, or a Frame on another website entirely. In order to link between Frames in the same project, you need to set up a frames.js handler on the `POST` route of the path defined in the target. +### Create the other routes + +Create additional frames in the `./frames` directory. + +#### Route 1 + +Create a directory `./frames/route1/route.tsx` with a `POST` handler that returns the frame content. + +```tsx [route1.tsx] +/* eslint-disable react/jsx-key */ +import { frames } from "../frames"; +import { Button } from "frames.js/next"; + +export const POST = frames(async (ctx) => { + const foo = ctx.searchParams.foo; + + return { + image:
Route 1 foo: {foo}
, // foo: bar + buttons: [ + , + ], + }; +}); +``` + +#### Route 2 + +Create a directory `./frames/route2/route.tsx` with a `POST` handler that returns the frame content. + +```tsx [route2.tsx] +/* eslint-disable react/jsx-key */ +import { frames } from "../frames"; +import { Button } from "frames.js/next"; + +export const POST = frames(async () => { + return { + image:
Route 2
, + buttons: [ + , + ], + }; +}); +``` + +### (Optional) Navigate back to the initial frame -{/* -TODO: Link to examples - */} \ No newline at end of file +If you want to navigate back to the initial frame you need to export a `POST` handler for the initial route. You may want to refactor the initial frame handler into a `frameHandler` variable that is exported as both `GET` and `POST` + +```tsx [route.tsx] +import { frames } from "./frames"; + +const frameHandler = frames(async () => { + return { + image:
Welcome
+ buttons: [ + , + , + ], + }; +}); + +export const GET = frameHandler; +export const POST = frameHandler; +``` + +You can then navigate back to the initial frame by linking to the initial route. + +```tsx + +``` + +::: + +## Notes + +The second way to navigate between frames is by defining a [`Button`](/reference/core/Button) with `type`, `post`, with a `target` that points at another Frame. +This can be a Frame on the same domain, or a Frame on another website entirely. In order to link between Frames in the same project, you need to set up a frames.js handler on the `POST` route of the path defined in the target. diff --git a/docs/pages/reference/core/Button.mdx b/docs/pages/reference/core/Button.mdx index 919aefccf..e95a4d30b 100644 --- a/docs/pages/reference/core/Button.mdx +++ b/docs/pages/reference/core/Button.mdx @@ -20,15 +20,22 @@ This button sends a request to a URL on which it was rendered. If you want to na The `target` path will be resolved relatively to current URL. -### Passing state when button is clicked using query parameters +### Passing state between frames ```tsx - ``` -The state will be available in handler `ctx.pressedButton.state`. +The state will be available in the frame handler via `ctx.searchParams` e.g. + +```ts +ctx.searchParams.foo; // bar +``` ## Post Redirect Button @@ -39,7 +46,10 @@ It accepts same props as `post` button. ```tsx import { Button } from "frames.js/core"; -; ``` From 45c1c99ea941dc2afd4f0708ffd918939ec706af Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Wed, 27 Mar 2024 11:54:30 +0200 Subject: [PATCH 13/18] feat: accessing request body via middleware --- docs/pages/guides/middleware.mdx | 42 +++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/docs/pages/guides/middleware.mdx b/docs/pages/guides/middleware.mdx index 11f253e50..aa0d761e2 100644 --- a/docs/pages/guides/middleware.mdx +++ b/docs/pages/guides/middleware.mdx @@ -37,7 +37,9 @@ export const POST = frames(async (ctx) => { return { image: ( -
The user's username is {ctx.message.requesterUserData.username}
+
+ The user's username is {ctx.message.requesterUserData.username} +
), }; }); @@ -73,7 +75,7 @@ export const POST = frames( ## Defining your own middleware -You can define your own middleware by creating a function that returns a promise that resolves to the next middleware, or a [Web API `Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), or a `FrameDefinition`. +You can define your own middleware by creating a function that returns a promise that resolves to the next middleware, or a [Web API `Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), or a [`FrameDefinition`](/reference/core/createFrames#framedefinition). Middleware can modify the context or return a response that will terminate the request early. @@ -84,10 +86,10 @@ Middleware can modify the context or return a response that will terminate the r ```tsx [frames.ts] import { createFrames, types } from "frames.js/next"; -const myMiddleware: types.FramesMiddleware< - any, - { foo: string } -> = async (ctx, next) => { +const myMiddleware: types.FramesMiddleware = async ( + ctx, + next +) => { return next({ foo: "bar" }); }; @@ -117,3 +119,31 @@ export const POST = handler; ``` ::: + + +### Accessing the request object + +Sometimes you want to access the request object in your middleware - whenever you do this, you should clone the request object to avoid mutating it and breaking other middleware. + +Here's an example of creating middleware which will add the request json to your context: + +```tsx +import { createFrames, types } from "frames.js/next"; + +const bodyMiddleware: types.FramesMiddleware = async ( + ctx, + next +) => { + const body = await ctx.request.clone().json(); + return next({ body }); +}; + +export const frames = createFrames({ + basePath: "/", + initialState: { + pageIndex: 0, + }, + middleware: [bodyMiddleware], + // The request body will now be available via `ctx.body` in your frame handlers +}); +``` From 9497b7e85230661e4192e5a8993ece7cceff72bc Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:05:11 +0200 Subject: [PATCH 14/18] chore: update changeset --- .changeset/new-emus-love.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.changeset/new-emus-love.md b/.changeset/new-emus-love.md index e0434dd52..8386bcdd2 100644 --- a/.changeset/new-emus-love.md +++ b/.changeset/new-emus-love.md @@ -2,7 +2,11 @@ "docs": patch --- -- Adds default context reference to `createFrames` reference -- "Frames.js for apps" section -- Clean up example filenames, add steps -- `createFrames` params +- adds default context reference to createFrames reference +- frames.js for apps section +- clean up example filenames, add steps +- createFrames params +- troubleshooting docs +- update useFrame reference +- make button docs more visible +- multi-page guide From ceadc1ff402d306670faee10b5cb55951bce783e Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:39:47 +0200 Subject: [PATCH 15/18] Update docs/pages/troubleshooting.mdx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michal Kvasničák --- docs/pages/troubleshooting.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/troubleshooting.mdx b/docs/pages/troubleshooting.mdx index 27ad5ca51..b78247153 100644 --- a/docs/pages/troubleshooting.mdx +++ b/docs/pages/troubleshooting.mdx @@ -32,7 +32,7 @@ If the image is not rendering, it may be in the wrong format. SVG images are typ ## Initial frame not loading -Ensure that the `fetchMetadata` URL is correct and that it is inside the `other` folder for Next.js. +Ensure that the `fetchMetadata` URL is correct and that it is inside the `other` property for Next.js. ### `VERCEL_URL` environment variable From 26de29b0ad9de70198e563d0492cdb502a2d3706 Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:40:25 +0200 Subject: [PATCH 16/18] Update docs/pages/troubleshooting.mdx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michal Kvasničák --- docs/pages/troubleshooting.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/troubleshooting.mdx b/docs/pages/troubleshooting.mdx index b78247153..89d1d05bc 100644 --- a/docs/pages/troubleshooting.mdx +++ b/docs/pages/troubleshooting.mdx @@ -36,7 +36,7 @@ Ensure that the `fetchMetadata` URL is correct and that it is inside the `other` ### `VERCEL_URL` environment variable -If you are using Vercel, the `VERCEL_URL` environment variable is not a fully qualified URL and may cause issues with `fetchMetadata`. You will have to prepend the protocol `VERCEL_URL`. +If you are using Vercel, the `VERCEL_URL` environment variable is not a fully qualified URL and may cause issues with `fetchMetadata`. You will have to prepend the protocol to `VERCEL_URL`. ```tsx export async function generateMetadata() { From e2d5c17c9903d30a3b9fc9df045ab839d0f53b7d Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:51:06 +0200 Subject: [PATCH 17/18] Update docs/pages/reference/core/createFrames.mdx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michal Kvasničák --- docs/pages/reference/core/createFrames.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/reference/core/createFrames.mdx b/docs/pages/reference/core/createFrames.mdx index 9cbdde563..9a8fbba3f 100644 --- a/docs/pages/reference/core/createFrames.mdx +++ b/docs/pages/reference/core/createFrames.mdx @@ -246,7 +246,7 @@ The index of the button that was clicked on the previous frame. ### `message` -- Type: `FrameMessage` +- Type: `FrameMessage | undefined` The frame message that was parsed from the request body. From fdc917aa252f680c9ee45f55d1c22611e21413b6 Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:51:12 +0200 Subject: [PATCH 18/18] Update docs/pages/reference/core/createFrames.mdx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michal Kvasničák --- docs/pages/reference/core/createFrames.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/reference/core/createFrames.mdx b/docs/pages/reference/core/createFrames.mdx index 9a8fbba3f..be7865df2 100644 --- a/docs/pages/reference/core/createFrames.mdx +++ b/docs/pages/reference/core/createFrames.mdx @@ -252,7 +252,7 @@ The frame message that was parsed from the request body. ### `clientProtocol` -- Type: `ClientProtocolId` +- Type: `ClientProtocolId | undefined` The client protocol that was used to send the frame message.