Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: handle interaction to get rewards #16

Merged
merged 5 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/github-actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ on:
jobs:
build:
runs-on: ubuntu-latest
env:
DEV_FRAMES_FUN_API_KEY: "api-key"
DEV_FRAMES_FUN_API_TRPC_URL: "http://localhost:3000/trpc"
FARCASTER_HUB_GRPC_URL: "http://localhost:5000"
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
Expand Down
16 changes: 16 additions & 0 deletions apps/extension/components/frame-iframe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import {
createMessageConsumer,
sendMessageToEmbed
} from "@xframes/shared/messaging"
import { BROWSER_EXTENSION_INTERACTIONS } from "ffun-trpc-types/dist/lib/interactions"
import { useCallback, useEffect, useRef, useState } from "react"

import { frameEmbedProxyUrl } from "~constants"
import { useFarcasterIdentity } from "~hooks/use-farcaster-identity"
import { dispatchInteractionEvent } from "~services/events"

import { LoadingIndicator } from "./loading-indicator"

Expand Down Expand Up @@ -45,6 +47,20 @@ export default function FrameIFrame({ url, frameId, theme }: FrameIFrameProps) {
})
}, [frameId])

useEffect(() => {
if (signer?.status !== "approved") {
return
}

return consumeMessageFromEmbed("frame_button_press", () => {
dispatchInteractionEvent(
BROWSER_EXTENSION_INTERACTIONS.user_interacted_with_frame,
signer.fid,
signer.publicKey
)
})
}, [signer])

useEffect(() => {
const iframe = iframeRef.current?.contentWindow
if (!iframe) {
Expand Down
2 changes: 2 additions & 0 deletions apps/extension/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ export const framesProxyUrl =
process.env.PLASMO_PUBLIC_FRAMES_PROXY_URL || `${baseProxyUrl}/api/v1/frames`
export const frameEmbedProxyUrl =
process.env.PLASMO_FRAME_EMBED_PROXY_URL || `${baseProxyUrl}/embed`
export const eventsProxyUrl =
process.env.PLASMO_EVENTS_PROXY_URL || `${baseProxyUrl}/api/v1/events`
Binary file added apps/extension/ffun-trpc-types-0.1.0.tgz
Binary file not shown.
12 changes: 12 additions & 0 deletions apps/extension/hooks/use-farcaster-identity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import type {
FarcasterSignerApproved as BaseFarcasterSignerApproved,
FarcasterSignerPendingApproval as BaseFarcasterSignerPendingApproval
} from "@frames.js/render"
import { BROWSER_EXTENSION_INTERACTIONS } from "ffun-trpc-types/dist/lib/interactions"
import { useEffect, useRef, useState } from "react"

import { signerProxyUrl } from "~constants"
import { dispatchInteractionEvent } from "~services/events"
// TODO - extract to a common lib
import {
convertKeypairToHex,
Expand Down Expand Up @@ -107,6 +109,16 @@ export function useFarcasterIdentity(
const [signer, setSigner, { remove: removeSigner }] = useSignerStorage(null)
const isSignedInRef = useRef(!!signer)

useEffect(() => {
if (signer?.status === "approved") {
dispatchInteractionEvent(
BROWSER_EXTENSION_INTERACTIONS.user_installed_extension,
signer.fid,
signer.publicKey
)
}
}, [signer])

async function logout() {
if (signer) {
// this check is just to make typescript happy, this status is not used in the extension at all
Expand Down
3 changes: 2 additions & 1 deletion apps/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
"@noble/ed25519": "^2.0.0",
"@plasmohq/messaging": "^0.6.2",
"@plasmohq/storage": "^1.9.3",
"@xframes/shared": "workspace:*",
"@uidotdev/usehooks": "^2.4.1",
"@xframes/shared": "workspace:*",
"cheerio": "1.0.0-rc.12",
"ffun-trpc-types": "file:ffun-trpc-types-0.1.0.tgz",
"frames.js": "^0.16.5",
"plasmo": "0.86.3",
"posthog-js": "^1.164.1",
Expand Down
22 changes: 22 additions & 0 deletions apps/extension/services/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { BROWSER_EXTENSION_INTERACTIONS } from "ffun-trpc-types/dist/lib/interactions"

import { eventsProxyUrl } from "~constants"

export function dispatchInteractionEvent(
event: BROWSER_EXTENSION_INTERACTIONS,
fid: number,
signerPublicKey: string
) {
// we don't care about the result of this request
fetch(eventsProxyUrl, {
headers: {
"X-FID": fid.toString(),
"X-Signer-Public-Key": signerPublicKey,
"Content-Type": "application/json"
},
method: "POST",
body: JSON.stringify({
type: event
})
})
}
10 changes: 9 additions & 1 deletion apps/server/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ FARCASTER_SPONSOR_MNEMONIC=
# FID for the sponsor account
FARCASTER_SPONSOR_FID=

FARCASTER_HUB_GRPC_URL=""

# Transaction attribution
NEXT_PUBLIC_FARCASTER_CLIENT_ID=

Expand All @@ -16,4 +18,10 @@ NEXT_PUBLIC_WALLETCONNECT_ID=

# posthog
NEXT_PUBLIC_POSTHOG_API_HOST=
NEXT_PUBLIC_POSTHOG_API_KEY=
NEXT_PUBLIC_POSTHOG_API_KEY=

# trpc api key
DEV_FRAMES_FUN_API_KEY=""

# trpc url
DEV_FRAMES_FUN_API_TRPC_URL="https://dev.frames.fun/api/trpc"
70 changes: 70 additions & 0 deletions apps/server/app/api/v1/events/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { NextRequest } from "next/server";
import { z } from "zod";
import { getSSLHubRpcClient } from "@farcaster/hub-nodejs";
import { hexToBytes } from "viem/utils";
import { BROWSER_EXTENSION_INTERACTIONS } from "ffun-trpc-types/dist/lib/interactions";
import { getDevFramesFunApiClient } from "@/services/trpc";

const farcasterHubClient = getSSLHubRpcClient(
process.env.FARCASTER_HUB_GRPC_URL
);

const bodyValidator = z.object({
type: z.nativeEnum(BROWSER_EXTENSION_INTERACTIONS),
});

const headersValidator = z.object({
fid: z.coerce.number().int().positive(),
signerPublicKey: z.custom<`0x${string}`>(
(val) => z.string().startsWith("0x").safeParse(val).success
),
});

export async function GET() {
return new Response(undefined, { status: 204 });
}

export async function POST(req: NextRequest) {
try {
const { signerPublicKey, fid } = headersValidator.parse({
fid: req.headers.get("x-fid"),
signerPublicKey: req.headers.get("x-signer-public-key"),
});

// validate that signerPublicKey is a valid public key
const signerOnChain = await farcasterHubClient.getOnChainSigner({
fid,
signer: hexToBytes(signerPublicKey),
});

if (signerOnChain.isErr()) {
return new Response("Unauthenticated", { status: 401 });
}

const body = await req.json();
const event = bodyValidator.parse(body);

const devFrameFunClient = getDevFramesFunApiClient();

await devFrameFunClient.interaction.recordInteraction.mutate({
type: event.type,
userFid: fid,
});

return new Response(undefined, {
status: 204,
headers: {
"Cache-Control": "no-cache",
},
});
} catch (e) {
console.error(e);
if (e instanceof z.ZodError) {
return new Response("Invalid event", { status: 400 });
}

console.error(e);

return new Response("Internal Server Error", { status: 500 });
}
}
6 changes: 6 additions & 0 deletions apps/server/components/frame-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ function FrameComponent({
};

const trackButtonPress: OnButtonPressFn = ({ button, index, url }) => {
sendMessageToExtension({
type: "frame_button_press",
buttonIndex: index,
frameUrl: url,
});

posthog.capture("frame_button_press", {
label: button.label,
action: button.action,
Expand Down
4 changes: 4 additions & 0 deletions apps/server/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ declare global {
FARCASTER_DEVELOPER_MNEMONIC?: string;
FARCASTER_DEVELOPER_FID?: string;
FARCASTER_SPONSOR_MNEMONIC?: string;
FARCASTER_HUB_GRPC_URL: string;
FARCASTER_SPONSOR_FID?: string;
DEV_FRAMES_FUN_API_KEY: string;
DEV_FRAMES_FUN_API_TRPC_URL: string;
NEXT_PUBLIC_FARCASTER_CLIENT_ID?: string;
}
}
}
Expand Down
Binary file added apps/server/ffun-trpc-types-0.1.0.tgz
Binary file not shown.
34 changes: 33 additions & 1 deletion apps/server/next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,35 @@
import { z } from "zod";

const envVariablesValidator = z
.object({
DEV_FRAMES_FUN_API_KEY: z.string().min(1),
DEV_FRAMES_FUN_API_TRPC_URL: z.string().min(1).url(),
FARCASTER_DEVELOPER_FID: z.string().optional(),
FARCASTER_DEVELOPER_MNEMONIC: z.string().optional(),
FARCASTER_SPONSOR_FID: z.string().optional(),
FARCASTER_SPONSOR_MNEMONIC: z.string().optional(),
FARCASTER_HUB_GRPC_URL: z.string(),
NEXT_PUBLIC_POSTHOG_API_HOST: z.string().url().optional(),
NEXT_PUBLIC_POSTHOG_API_KEY: z.string().optional(),
NEXT_PUBLIC_WALLETCONNECT_ID: z.string().optional(),
NEXT_PUBLIC_FARCASTER_CLIENT_ID: z.coerce.number().int().optional(),
})
.passthrough();

const envVariablesValidationResult = envVariablesValidator.safeParse(
process.env
);

if (envVariablesValidationResult.success === false) {
for (const issue of envVariablesValidationResult.error.issues) {
console.error(
`Environment variable ${issue.path.join(".")} error: ${issue.message}`
);
}

process.exit(1);
}

/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
Expand All @@ -14,7 +46,7 @@ const nextConfig = {
{
key: "Access-Control-Allow-Headers",
value:
"X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version",
"X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, X-Signer-Public-Key, X-FID",
},
],
},
Expand Down
10 changes: 8 additions & 2 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,27 @@
"lint": "next lint"
},
"dependencies": {
"@farcaster/hub-nodejs": "^0.12.7",
"@frames.js/render": "^0.2.16",
"@noble/ed25519": "^2.0.0",
"@rainbow-me/rainbowkit": "^2.1.0",
"@reservoir0x/reservoir-sdk": "^2.2.3",
"@tanstack/react-query": "^5.37.1",
"@trpc/client": "11.0.0-rc.638",
"@trpc/server": "11.0.0-rc.638",
"@uidotdev/usehooks": "^2.4.1",
"@xframes/ui": "workspace:*",
"@xframes/shared": "workspace:*",
"@xframes/ui": "workspace:*",
"ffun-trpc-types": "file:ffun-trpc-types-0.1.0.tgz",
"frames.js": "^0.16.5",
"next": "14.1.4",
"posthog-js": "^1.144.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"superjson": "^2.2.1",
"viem": "^2.9.15",
"wagmi": "^2.9.4"
"wagmi": "^2.9.4",
"zod": "^3.23.8"
},
"devDependencies": {
"@frames.js/debugger": "^0.2.3",
Expand Down
24 changes: 24 additions & 0 deletions apps/server/services/trpc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createTRPCClient, httpBatchLink } from "@trpc/client";
import type { APIRouter } from "ffun-trpc-types/dist/server/api/root";
import superjson from "superjson";

export function getDevFramesFunApiClient(authToken?: string) {
const client = createTRPCClient<APIRouter>({
links: [
httpBatchLink({
url: process.env.DEV_FRAMES_FUN_API_TRPC_URL,
transformer: superjson,
async headers() {
return {
...(authToken && {
authorization: authToken,
}),
"x-api-key": process.env.DEV_FRAMES_FUN_API_KEY,
};
},
}),
],
});

return client;
}
File renamed without changes.
2 changes: 1 addition & 1 deletion packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"lint": "eslint . --max-warnings 0"
"lint": "eslint ."
},
"devDependencies": {
"@xframes/eslint-config": "workspace:*",
Expand Down
9 changes: 8 additions & 1 deletion packages/shared/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,17 @@ type MessageToServerSignerlessPress = {
frameId: string;
};

type MessageToServerFrameButtonPressed = {
type: "frame_button_press";
frameUrl: string;
buttonIndex: number;
};

export type MessagesToExtension =
| MessageToServerFrameRendered
| MessageToServerSignerSignOut
| MessageToServerSignerlessPress;
| MessageToServerSignerlessPress
| MessageToServerFrameButtonPressed;

type MessageFromServerSignerSignedIn = {
type: "signed_in";
Expand Down
Loading
Loading