Skip to content

Commit 740ca39

Browse files
chore: make use frame backward compatible
1 parent 0bbcc19 commit 740ca39

File tree

6 files changed

+145
-41
lines changed

6 files changed

+145
-41
lines changed

packages/render/src/helpers.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import type { ParseFramesWithReportsResult } from "frames.js/frame-parsers";
1+
import type {
2+
ParseFramesWithReportsResult,
3+
ParseResult,
4+
} from "frames.js/frame-parsers";
25

36
export async function tryCallAsync<TResult>(
47
promiseFn: () => Promise<TResult>
@@ -38,3 +41,13 @@ export function isParseFramesWithReportsResult(
3841
"farcaster" in value
3942
);
4043
}
44+
45+
export function isParseResult(value: unknown): value is ParseResult {
46+
return (
47+
typeof value === "object" &&
48+
value !== null &&
49+
"status" in value &&
50+
!("openframes" in value) &&
51+
!("farcaster" in value)
52+
);
53+
}

packages/render/src/next/GET.tsx

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import type { NextRequest } from "next/server";
22
import { parseFramesWithReports } from "frames.js/parseFramesWithReports";
33
import type { ParseFramesWithReportsResult } from "frames.js/frame-parsers";
4+
import { getFrame, type GetFrameResult } from "frames.js";
5+
import { isSpecificationValid } from "./validators";
46

5-
export type GETResponse = ParseFramesWithReportsResult | { message: string };
7+
export type GETResponse =
8+
| ParseFramesWithReportsResult
9+
| GetFrameResult
10+
| { message: string };
611

712
/** Proxies fetching a frame through a backend to avoid CORS issues and preserve user IP privacy */
813
export async function GET(request: Request | NextRequest): Promise<Response> {
@@ -12,6 +17,9 @@ export async function GET(request: Request | NextRequest): Promise<Response> {
1217
? request.nextUrl.searchParams
1318
: new URL(request.url).searchParams;
1419
const url = searchParams.get("url");
20+
const specification = searchParams.get("specification") ?? "farcaster";
21+
const multiSpecificationEnabled =
22+
searchParams.get("multispecification") === "true";
1523

1624
if (!url) {
1725
return Response.json({ message: "Invalid URL" } satisfies GETResponse, {
@@ -21,9 +29,29 @@ export async function GET(request: Request | NextRequest): Promise<Response> {
2129

2230
const urlRes = await fetch(url);
2331
const html = await urlRes.text();
24-
const result: ParseFramesWithReportsResult = parseFramesWithReports({
25-
html,
26-
fallbackPostUrl: url,
32+
33+
if (multiSpecificationEnabled) {
34+
const result: ParseFramesWithReportsResult = parseFramesWithReports({
35+
html,
36+
fallbackPostUrl: url,
37+
});
38+
39+
return Response.json(result satisfies GETResponse);
40+
}
41+
42+
if (!isSpecificationValid(specification)) {
43+
return Response.json(
44+
{ message: "Invalid specification" } satisfies GETResponse,
45+
{
46+
status: 400,
47+
}
48+
);
49+
}
50+
51+
const result = getFrame({
52+
htmlString: html,
53+
url,
54+
specification,
2755
});
2856

2957
return Response.json(result satisfies GETResponse);

packages/render/src/next/POST.tsx

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
import type { FrameActionPayload } from "frames.js";
2-
import type { ParseFramesWithReportsResult } from "frames.js/frame-parsers";
1+
import {
2+
getFrame,
3+
type FrameActionPayload,
4+
type GetFrameResult,
5+
} from "frames.js";
6+
import { type ParseFramesWithReportsResult } from "frames.js/frame-parsers";
37
import { parseFramesWithReports } from "frames.js/parseFramesWithReports";
48
import type { JsonObject, JsonValue } from "frames.js/types";
59
import type { NextRequest } from "next/server";
610
import { tryCallAsync } from "../helpers";
11+
import { isSpecificationValid } from "./validators";
712

813
export type POSTResponseError = { message: string };
914

@@ -12,6 +17,7 @@ export type POSTResponseRedirect = { location: string };
1217
export type POSTTransactionResponse = JsonObject;
1318

1419
export type POSTResponse =
20+
| GetFrameResult
1521
| ParseFramesWithReportsResult
1622
| POSTResponseError
1723
| POSTResponseRedirect
@@ -37,6 +43,9 @@ export async function POST(req: Request | NextRequest): Promise<Response> {
3743
const isPostRedirect = searchParams.get("postType") === "post_redirect";
3844
const isTransactionRequest = searchParams.get("postType") === "tx";
3945
const postUrl = searchParams.get("postUrl");
46+
const multiSpecificationEnabled =
47+
searchParams.get("multispecification") === "true";
48+
const specification = searchParams.get("specification") ?? "farcaster";
4049

4150
if (!postUrl) {
4251
return Response.json(
@@ -170,9 +179,31 @@ export async function POST(req: Request | NextRequest): Promise<Response> {
170179
}
171180

172181
const html = await response.text();
173-
const result: ParseFramesWithReportsResult = parseFramesWithReports({
174-
html,
175-
fallbackPostUrl: body.untrustedData.url,
182+
183+
if (multiSpecificationEnabled) {
184+
const result = parseFramesWithReports({
185+
html,
186+
fallbackPostUrl: body.untrustedData.url,
187+
fromRequestMethod: "POST",
188+
});
189+
190+
return Response.json(result satisfies ParseFramesWithReportsResult);
191+
}
192+
193+
if (!isSpecificationValid(specification)) {
194+
return Response.json(
195+
{
196+
message: "Invalid specification",
197+
} satisfies POSTResponseError,
198+
{ status: 400 }
199+
);
200+
}
201+
202+
const result = getFrame({
203+
htmlString: html,
204+
url: body.untrustedData.url,
205+
fromRequestMethod: "POST",
206+
specification,
176207
});
177208

178209
return Response.json(result satisfies POSTResponse);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { SupportedParsingSpecification } from "frames.js";
2+
3+
export function isSpecificationValid(
4+
specification: unknown
5+
): specification is SupportedParsingSpecification {
6+
return (
7+
typeof specification === "string" &&
8+
["farcaster", "openframes"].includes(specification)
9+
);
10+
}

packages/render/src/use-fetch-frame.ts

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
} from "./errors";
4040
import {
4141
isParseFramesWithReportsResult,
42+
isParseResult,
4243
tryCall,
4344
tryCallAsync,
4445
} from "./helpers";
@@ -241,30 +242,41 @@ export function useFetchFrame<
241242
return;
242243
}
243244

244-
if (!isParseFramesWithReportsResult(parseResult)) {
245-
const error = new Error("The server returned an unexpected response.");
246-
247-
stackAPI.markAsFailed({
248-
endTime,
245+
if (isParseFramesWithReportsResult(parseResult)) {
246+
stackAPI.markAsDone({
249247
pendingItem: frameStackPendingItem,
250-
requestError: error,
248+
endTime,
249+
frameResult: parseResult[specification],
251250
response,
252-
responseBody: parseResult,
253-
responseStatus: 500,
254251
});
255252

256-
tryCall(() => {
257-
onError(error);
253+
return;
254+
}
255+
256+
if (isParseResult(parseResult)) {
257+
stackAPI.markAsDone({
258+
pendingItem: frameStackPendingItem,
259+
endTime,
260+
frameResult: parseResult,
261+
response,
258262
});
259263

260264
return;
261265
}
262266

263-
stackAPI.markAsDone({
264-
pendingItem: frameStackPendingItem,
267+
const error = new Error("The server returned an unexpected response.");
268+
269+
stackAPI.markAsFailed({
265270
endTime,
266-
frameResult: parseResult[specification],
271+
pendingItem: frameStackPendingItem,
272+
requestError: error,
267273
response,
274+
responseBody: parseResult,
275+
responseStatus: 500,
276+
});
277+
278+
tryCall(() => {
279+
onError(error);
268280
});
269281

270282
return;
@@ -480,33 +492,46 @@ export function useFetchFrame<
480492
return;
481493
}
482494

483-
if (!isParseFramesWithReportsResult(responseData)) {
484-
const error = new Error("The server returned an unexpected response.");
485-
486-
stackAPI.markAsFailed({
495+
if (isParseFramesWithReportsResult(responseData)) {
496+
stackAPI.markAsDone({
487497
endTime,
498+
frameResult: responseData[specification],
488499
pendingItem,
489-
requestError: error,
490500
response,
491-
responseBody: responseData,
492-
responseStatus: 500,
493501
});
494-
tryCall(() => {
495-
onError(error);
502+
503+
tryCall(() => options?.onSuccess?.());
504+
505+
return;
506+
}
507+
508+
if (isParseResult(responseData)) {
509+
stackAPI.markAsDone({
510+
endTime,
511+
frameResult: responseData,
512+
pendingItem,
513+
response,
496514
});
497-
tryCall(() => options?.onError?.(error));
515+
516+
tryCall(() => options?.onSuccess?.());
498517

499518
return;
500519
}
501520

502-
stackAPI.markAsDone({
521+
const error = new Error("The server returned an unexpected response.");
522+
523+
stackAPI.markAsFailed({
503524
endTime,
504-
frameResult: responseData[specification],
505525
pendingItem,
526+
requestError: error,
506527
response,
528+
responseBody: responseData,
529+
responseStatus: 500,
507530
});
508-
509-
tryCall(() => options?.onSuccess?.());
531+
tryCall(() => {
532+
onError(error);
533+
});
534+
tryCall(() => options?.onError?.(error));
510535

511536
return;
512537
}

packages/render/src/use-frame-stack.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,12 @@ import type {
1616
SignedFrameAction,
1717
SignerStateActionContext,
1818
} from "./types";
19+
import { isParseResult } from "./helpers";
1920

2021
function computeDurationInSeconds(start: Date, end: Date): number {
2122
return Number(((end.getTime() - start.getTime()) / 1000).toFixed(2));
2223
}
2324

24-
export function isParseResult(result: unknown): result is ParseResult {
25-
return typeof result === "object" && result !== null && "status" in result;
26-
}
27-
2825
function framesStackReducer(
2926
state: FramesStack,
3027
action: FrameReducerActions

0 commit comments

Comments
 (0)