Skip to content

Commit

Permalink
Add DSCVR adapter (Dcp-1600) (#3)
Browse files Browse the repository at this point in the history
* chore: restrict single quote in vscode settings

* feat: add getDscvrFrameMessage function

* fix: pr feedback

* chore: publish frames.js artifact
  • Loading branch information
jimmymadrigal committed Mar 20, 2024
1 parent ec0234a commit 8c689dd
Show file tree
Hide file tree
Showing 13 changed files with 1,252 additions and 789 deletions.
11 changes: 11 additions & 0 deletions .changeset/nasty-experts-rule.md
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
39 changes: 39 additions & 0 deletions .github/workflows/release-framesjs-package.yml
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ node_modules
.env.test.local
.env.production.local

# NPM lock
package-lock.json

# Testing
coverage

Expand Down
2 changes: 2 additions & 0 deletions .npmrc
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/
1 change: 1 addition & 0 deletions .prettierrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
singleQuote: false
27 changes: 27 additions & 0 deletions docs/pages/reference/js/dscvr/getDscvrFrameMessage.mdx
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: '...',
}
**/
```
224 changes: 224 additions & 0 deletions docs/pages/reference/js/dscvr/index.mdx
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&apos;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 docs/pages/reference/js/dscvr/isDscvrFrameActionPayload.mdx
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
}

```
18 changes: 18 additions & 0 deletions docs/vocs.config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,24 @@ const sidebar = [
},
],
},
{
text: "DSCVR",
collapsed: false,
items: [
{
text: "Tutorial",
link: "/reference/js/dscvr",
},
{
text: "validateDscvrFrameMessage",
link: "/reference/js/dscvr/validateDscvrFrameMessage",
},
{
text: "isDscvrFrameActionPayload",
link: "/reference/js/dscvr/isDscvrFrameActionPayload",
},
],
},
],
},
{
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@
"packages/*"
],
"version": "0.3.0-canary.0"
}
}
Loading

0 comments on commit 8c689dd

Please sign in to comment.