forked from framesjs/frames.js
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* chore: restrict single quote in vscode settings * feat: add getDscvrFrameMessage function * fix: pr feedback * chore: publish frames.js artifact
- Loading branch information
1 parent
ec0234a
commit 8c689dd
Showing
13 changed files
with
1,252 additions
and
789 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
--- | ||
"frames.js": minor | ||
"docs": minor | ||
"framesjs-starter": patch | ||
"utils-starter": patch | ||
"@frames.js/debugger": patch | ||
"@framesjs/eslint-config": patch | ||
"@framesjs/typescript-config": patch | ||
--- | ||
|
||
Add DSCVR adapter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
name: "Release Package" | ||
|
||
on: | ||
release: | ||
types: [created] | ||
|
||
jobs: | ||
publish-gpr: | ||
runs-on: ubuntu-latest | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
defaults: | ||
run: | ||
working-directory: ./packages/frames.js | ||
permissions: | ||
contents: read | ||
packages: write | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: actions/setup-node@v4 | ||
with: | ||
node-version: "20.x" | ||
registry-url: https://npm.pkg.github.com/ | ||
# this is a workaround for publishing with npm publish | ||
- run: npm pkg set 'name'='@dscvr-one/frames.js' | ||
- run: rm ../../.npmrc | ||
- run: | | ||
echo "//npm.pkg.github.com/:_authToken=\${GITHUB_TOKEN}" > .npmrc | ||
echo "@dscvr-one:registry=https://npm.pkg.github.com/" >> .npmrc | ||
- run: yarn install | ||
- run: yarn build | ||
- run: yarn pack | ||
- run: npm publish --access=public --registry=https://npm.pkg.github.com *.tgz | ||
env: | ||
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
- uses: actions/upload-artifact@v4 | ||
with: | ||
name: release-package | ||
path: ./*.tgz |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,9 @@ node_modules | |
.env.test.local | ||
.env.production.local | ||
|
||
# NPM lock | ||
package-lock.json | ||
|
||
# Testing | ||
coverage | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} | ||
@dscvr-one:registry=https://npm.pkg.github.com/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
singleQuote: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# validateDscvrFrameMessage | ||
|
||
Returns a `DscvrFrameMessageReturnType` object from the message payload. Throws an error if the message is invalid. Requires the `@dscvr-one/frames-adapter` peer dependency to be installed. | ||
|
||
## Usage | ||
|
||
```ts | ||
yarn add @dscvr-one/frames-adapter | ||
``` | ||
|
||
```ts | ||
import { validateDscvrFrameMessage } from "@dscvr-one/frames.js/dscvr"; | ||
|
||
const frameMessage = await validateDscvrFrameMessage(frameActionPayload); | ||
console.log(frameMessage); | ||
/** | ||
{ | ||
url: 'https://example.com', | ||
timestamp: 1234567890, | ||
buttonIndex: 1, | ||
inputText: '...', | ||
state: '...', | ||
dscvrId: '...', | ||
contentId: '...', | ||
} | ||
**/ | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
import { HomePage } from "vocs/components"; | ||
|
||
# DSCVR Support | ||
|
||
Cross-protocol frames are supported by frames.js via familiar APIs. This guide will showcase how to write a simple stateless frame, which returns the identity of the user that interacted with the frame in Farcaster or DSCVR. | ||
|
||
## Setup | ||
|
||
First, you need to install `@dscvr-one/frames.js` and `@dscvr-one/frames-adapter`. You can do this by running the following command: | ||
|
||
<HomePage.InstallPackage | ||
name="@dscvr-one/frames.js @dscvr-one/frames-adapter" | ||
type="install" | ||
/> | ||
|
||
## Writing a Frame | ||
|
||
To write a frame using Next.js, we need to create a page which renders the frame and a route which handles frame action requests. | ||
|
||
### API Route | ||
|
||
We start by creating a new API route which will handle POST requests to our frame. In your Next.js project, create a new directory `/frames` and inside it, create a `route.ts` file which contains the following code: | ||
|
||
```tsx filename="example0.ts" | ||
export { POST } from "frames.js/next/server"; | ||
``` | ||
|
||
This will handle POST requests to our frame and redirect them to page we are about to create, which will handle the rendering logic. | ||
|
||
### Page | ||
|
||
In your Next.js project, create a new `page.tsx` at the root of the project and write the following code: | ||
|
||
First import the necessary functions and components from `frames.js`: | ||
|
||
```tsx filename="example1.tsx" | ||
import { | ||
FrameButton, | ||
FrameContainer, | ||
FrameImage, | ||
NextServerPageProps, | ||
getFrameMessage, | ||
getPreviousFrame, | ||
} from "frames.js/next/server"; | ||
``` | ||
|
||
`FrameButton`, `FrameContainer`, and `FrameImage` are components that are used to construct Frame metadata tags in HTML. `NextServerPageProps` is a type that you can use to define the props of your page. `getFrameMessage` and `getPreviousFrame` are functions that you can use to get the message and the previous frame of your page to determine the next state to return. | ||
|
||
Next, import the DSCVR validation methods from frames.js | ||
|
||
```tsx filename="example2.tsx" | ||
// [!include example1.tsx] | ||
import { | ||
validateDscvrFrameMessage, | ||
isDscvrFrameActionPayload, | ||
} from "@dscvr-one/frames.js/dscvr"; // [!code focus] | ||
``` | ||
|
||
Then define the client protocols that your frame will support. In our case we will support DSCVR and Farcaster. | ||
|
||
```tsx filename="example3.tsx" | ||
import { | ||
FrameButton, | ||
FrameContainer, | ||
FrameImage, | ||
NextServerPageProps, | ||
getFrameMessage, | ||
getPreviousFrame, | ||
} from "frames.js/next/server"; | ||
import { | ||
validateDscvrFrameMessage, | ||
isDscvrFrameActionPayload, | ||
} from "@dscvr-one/frames.js/dscvr"; | ||
|
||
const acceptedProtocols: ClientProtocolId[] = [ | ||
// [!code focus] | ||
{ | ||
// [!code focus] | ||
id: "dscvr", // [!code focus] | ||
version: "vNext", // [!code focus] | ||
}, // [!code focus] | ||
{ | ||
// [!code focus] | ||
id: "farcaster", // [!code focus] | ||
version: "vNext", // [!code focus] | ||
}, // [!code focus] | ||
]; // [!code focus] | ||
``` | ||
|
||
Now define the render method for your frame. This will take place in a server component, so all the logic will be executed on the server and a plain HTML response containing our frame will be sent to the client. | ||
|
||
```tsx filename="example4.tsx" | ||
// ... | ||
|
||
export default async function Home({ | ||
params, | ||
searchParams, | ||
}: NextServerPageProps) { | ||
const previousFrame = getPreviousFrame(searchParams); | ||
|
||
// do some logic to determine the next frame | ||
|
||
// return the frame | ||
|
||
return ( | ||
<FrameContainer | ||
pathname="/" | ||
postUrl="/frames" | ||
state={{}} | ||
previousFrame={previousFrame} | ||
accepts={acceptedProtocols} | ||
> | ||
<FrameImage>Hello world</FrameImage> | ||
<FrameButton>Next</FrameButton> | ||
</FrameContainer> | ||
); | ||
} | ||
``` | ||
|
||
Here we use `previousFrame` to extract the frame action payload and state from the previous frame which are stored in URL params. We then use these to determine the next frame to return, passing the new state and accepted protocols to the `FrameContainer` component. The props of the `FrameContainer` component determine the routing and functionality of the frame. `pathname` is the path of the rendering method of the frame, `postUrl` is the path of the API route that handles frame action requests, `state` is the state of the frame, `previousFrame` is the previous frame, and `accepts` is an array of client protocols that this frame supports. | ||
|
||
### Validating a Frame Action | ||
|
||
Before returning a frame or doing any processing, you may want to validate the frame and extract the context from which the frame was interacted with e.g. the user that clicked the button in a farcaster in an DSCVR chat. This is where the `getFrameMessage`, `validateDscvrFrameMessage`, and `isDscvrFrameActionPayload` functions come in. | ||
|
||
```tsx filename="example5.tsx" | ||
const previousFrame = getPreviousFrame(searchParams); | ||
|
||
// do some logic to determine the next frame | ||
|
||
let fid: number | undefined; | ||
let walletAddress: string | undefined; | ||
let verifiedDscvrUserId: string | undefined; | ||
let verifiedDscvrContentId: string | undefined; | ||
|
||
if ( | ||
// [!code focus] | ||
previousFrame.postBody && // [!code focus] | ||
isDscvrFrameActionPayload(previousFrame.postBody) // [!code focus] | ||
) { | ||
// [!code focus] | ||
const frameMessage = await validateDscvrFrameMessage(previousFrame.postBody); // [!code focus] | ||
// do something with DSCVR frame message // [!code focus] | ||
} else { | ||
// [!code focus] | ||
const frameMessage = await getFrameMessage(previousFrame.postBody); // [!code focus] | ||
// do something with Farcaster frame message // [!code focus] | ||
} // [!code focus] | ||
|
||
// ... | ||
``` | ||
|
||
Here we use `previousFrame.postBody` to extract the frame action payload from the previous frame. We then use `isDscvrFrameActionPayload` to determine if the frame action payload is an DSCVR frame action payload. If it is, we use `validateDscvrFrameMessage` to extract the DSCVR frame message from the frame action payload. If it isn't, we use `getFrameMessage` to extract the Farcaster frame message from the frame action payload. | ||
|
||
Now we can use data from the different message contexts to populate our `fid`, `walletAddress`, `verifiedDscvrUserId`, `verifiedDscvrContentId` variables. | ||
|
||
```tsx filename="example6.tsx" | ||
// ... | ||
|
||
let fid: number | undefined; | ||
let walletAddress: string | undefined; | ||
let verifiedDscvrUserId: string | undefined; | ||
let verifiedDscvrContentId: string | undefined; | ||
|
||
if ( | ||
previousFrame.postBody && | ||
isDscvrFrameActionPayload(previousFrame.postBody) | ||
) { | ||
const frameMessage = await validateDscvrFrameMessage(previousFrame.postBody); | ||
dscvrUser = frameMessage?.verifiedUser; // [!code focus] | ||
} else { | ||
const frameMessage = await getFrameMessage(previousFrame.postBody); | ||
if (frameMessage && frameMessage?.isValid) { | ||
// [!code focus] | ||
fid = frameMessage?.requesterFid; // [!code focus] | ||
walletAddress = // [!code focus] | ||
frameMessage?.requesterCustodyAddress.length > 0 // [!code focus] | ||
? frameMessage?.requesterCustodyAddress // [!code focus] | ||
: frameMessage.requesterCustodyAddress; // [!code focus] | ||
} // [!code focus] | ||
} | ||
|
||
// ... | ||
``` | ||
|
||
Here we use the `frameMessage` to extract the `verifiedDscvrUserId` and `verifiedDscvrContentId` from the DSCVR frame messages. We then use these to populate our `verifiedDscvrUserId` and `verifiedDscvrContentId` variables. You can use this information to execute some action like a graphql query or an onchain transaction. | ||
|
||
### Returning a Frame | ||
|
||
Now that we have our `fid`, `walletAddress`, `verifiedDscvrUserId` or `verifiedDscvrContentId` variables populated, we can use them to determine the next frame to return. | ||
|
||
```tsx filename="example7.tsx" | ||
// ... | ||
|
||
return ( | ||
<FrameContainer | ||
pathname="/" | ||
postUrl="/frames" | ||
state={{}} | ||
previousFrame={previousFrame} | ||
accepts={acceptedProtocols} | ||
> | ||
<FrameImage> | ||
<div tw="flex flex-col"> | ||
<div tw="flex"> | ||
This frame gets the interactor's wallet address or FID depending | ||
on the client protocol. | ||
</div> | ||
{fid && <div tw="flex">FID: {fid}</div>} | ||
{walletAddress && <div tw="flex">Wallet Address: {walletAddress}</div>} | ||
{verifiedDscvrUserId && ( | ||
<div tw="flex">Dscvr user id: {verifiedDscvrUserId}</div> | ||
)} | ||
{verifiedDscvrContentId && ( | ||
<div tw="flex">Dscvr content id: {verifiedDscvrContentId}</div> | ||
)} | ||
</div> | ||
</FrameImage> | ||
<FrameButton>Check</FrameButton> | ||
</FrameContainer> | ||
); | ||
``` | ||
|
||
Above, we conditionally render the `fid`, `verifiedDscvrUserId` or `verifiedDscvrContentId` variables in the frame. |
17 changes: 17 additions & 0 deletions
17
docs/pages/reference/js/dscvr/isDscvrFrameActionPayload.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# isDscvrFrameActionPayload | ||
|
||
Returns true if the given frame action payload is an DSCVR frame action payload. | ||
|
||
## Usage | ||
|
||
```ts | ||
|
||
import { isDscvrFrameActionPayload } from '@dscvr-one/frames.js/dscvr'; | ||
|
||
... | ||
|
||
if (isDscvrFrameActionPayload(payload)) { | ||
// payload is an DSCVR frame action payload | ||
} | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,4 +35,4 @@ | |
"packages/*" | ||
], | ||
"version": "0.3.0-canary.0" | ||
} | ||
} |
Oops, something went wrong.