Skip to content

Commit c51c18f

Browse files
committed
fix: avoid unused api in farcaster frame
1 parent 837fb5f commit c51c18f

File tree

3 files changed

+124
-1
lines changed

3 files changed

+124
-1
lines changed

src/config/farcasterFrame.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import {
2+
ClientProtocolId,
3+
FrameActionDataParsedAndHubContext,
4+
FrameActionPayload,
5+
FrameMessageReturnType,
6+
getAddressesForFid,
7+
getFrameMessage,
8+
HubHttpUrlOptions,
9+
} from 'frames.js';
10+
import { FramesMiddleware, JsonValue } from 'frames.js/core/types';
11+
import { MessageWithWalletAddressImplementation } from 'frames.js/middleware/walletAddressMiddleware';
12+
13+
import { InvalidFrameActionPayloadError, RequestBodyNotJSONError } from '@/constants/error';
14+
15+
type FrameMessage = Omit<FrameMessageReturnType<{ fetchHubContext: true }>, 'message'> & {
16+
state?: JsonValue;
17+
} & MessageWithWalletAddressImplementation;
18+
type FramesMessageContext = {
19+
message?: FrameMessage;
20+
clientProtocol?: ClientProtocolId;
21+
};
22+
23+
function isValidFrameActionPayload(value: unknown): value is FrameActionPayload {
24+
return typeof value === 'object' && value !== null && 'trustedData' in value && 'untrustedData' in value;
25+
}
26+
27+
async function decodeFrameActionPayloadFromRequest(request: Request): Promise<FrameActionPayload | undefined> {
28+
try {
29+
// use clone just in case someone wants to read body somewhere along the way
30+
const body = (await request
31+
.clone()
32+
.json()
33+
.catch(() => {
34+
throw new RequestBodyNotJSONError();
35+
})) as JSON;
36+
37+
if (!isValidFrameActionPayload(body)) {
38+
throw new InvalidFrameActionPayloadError();
39+
}
40+
41+
return body;
42+
} catch (e) {
43+
if (e instanceof RequestBodyNotJSONError || e instanceof InvalidFrameActionPayloadError) {
44+
return undefined;
45+
}
46+
47+
// eslint-disable-next-line no-console -- provide feedback to the developer
48+
console.error(e);
49+
50+
return undefined;
51+
}
52+
}
53+
54+
export function farcasterHubContext(options: HubHttpUrlOptions): FramesMiddleware<any, FramesMessageContext> {
55+
return async (context, next) => {
56+
if (context.request.method !== 'POST') {
57+
return next();
58+
}
59+
60+
const payload = await decodeFrameActionPayloadFromRequest(context.request);
61+
if (!payload) {
62+
return next();
63+
}
64+
65+
try {
66+
const message = (await getFrameMessage(payload, {
67+
...options,
68+
fetchHubContext: false,
69+
})) as FrameActionDataParsedAndHubContext;
70+
71+
const requesterEthAddresses = await getAddressesForFid({
72+
fid: message.requesterFid,
73+
options: {
74+
hubHttpUrl: options.hubHttpUrl,
75+
hubRequestOptions: options.hubRequestOptions,
76+
},
77+
});
78+
const requesterCustodyAddress = requesterEthAddresses.find((item) => item.type === 'custody')?.address;
79+
if (!requesterCustodyAddress) {
80+
throw new Error('Custody address not found');
81+
}
82+
83+
const requesterVerifiedAddresses = requesterEthAddresses
84+
.filter((item) => item.type === 'verified')
85+
.map((item) => item.address);
86+
87+
message.requesterVerifiedAddresses = requesterVerifiedAddresses;
88+
message.requesterCustodyAddress = requesterCustodyAddress;
89+
90+
const [address] = message.requesterVerifiedAddresses;
91+
92+
return next({
93+
message: {
94+
...message,
95+
walletAddress() {
96+
return Promise.resolve(address ?? message.requesterCustodyAddress);
97+
},
98+
},
99+
clientProtocol: {
100+
id: 'farcaster',
101+
version: 'vNext', // TODO: Pass version in getFrameMessage
102+
},
103+
});
104+
} catch (error) {
105+
// eslint-disable-next-line no-console -- provide feedback to the developer
106+
console.info(
107+
'farcasterHubContext middleware: could not decode farcaster message from payload, calling next.',
108+
);
109+
return next();
110+
}
111+
};
112+
}

src/config/frames.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { farcasterHubContext } from 'frames.js/middleware';
21
import { imagesWorkerMiddleware } from 'frames.js/middleware/images-worker';
32
import { createFrames } from 'frames.js/next';
43

4+
import { farcasterHubContext } from '@/config/farcasterFrame';
55
import { lensFrame } from '@/config/lensFrame';
66
import { IMAGE_ZOOM_SCALE } from '@/constants';
77
import { env } from '@/constants/env';

src/constants/error.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export class InvalidFrameActionPayloadError extends Error {
2+
constructor(message = 'Invalid frame action payload') {
3+
super(message);
4+
}
5+
}
6+
7+
export class RequestBodyNotJSONError extends Error {
8+
constructor() {
9+
super('Invalid frame action payload, request body is not JSON');
10+
}
11+
}

0 commit comments

Comments
 (0)