Skip to content

Commit b081dde

Browse files
feat: debugger use new multiprotocol support
1 parent 8f79c5f commit b081dde

File tree

4 files changed

+160
-89
lines changed

4 files changed

+160
-89
lines changed

packages/debugger/app/components/cast-composer.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { useToast } from "@/components/ui/use-toast";
2323
import { ToastAction } from "@radix-ui/react-toast";
2424
import Link from "next/link";
2525
import type { FarcasterSigner } from "@frames.js/render/identity/farcaster";
26+
import { getFrameParseResultFromStackItemBySpecifications } from "@frames.js/render/helpers";
2627

2728
type CastComposerProps = {
2829
composerAction: Partial<ComposerActionResponse>;
@@ -139,11 +140,15 @@ function createDebugUrl(frameUrl: string, currentUrl: string) {
139140
}
140141

141142
function isAtLeastPartialFrame(stackItem: FrameStackDone): boolean {
143+
const result = getFrameParseResultFromStackItemBySpecifications(stackItem, [
144+
"farcaster",
145+
]);
146+
142147
return (
143-
stackItem.frameResult.status === "success" ||
144-
(!!stackItem.frameResult.frame &&
145-
!!stackItem.frameResult.frame.buttons &&
146-
stackItem.frameResult.frame.buttons.length > 0)
148+
result.status === "success" ||
149+
(!!result.frame &&
150+
!!result.frame.buttons &&
151+
result.frame.buttons.length > 0)
147152
);
148153
}
149154

packages/debugger/app/components/frame-debugger.tsx

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
CollapsedFrameUI,
2323
defaultTheme,
2424
} from "@frames.js/render";
25+
import { getFrameParseResultFromStackItemBySpecifications } from "@frames.js/render/helpers";
2526
import { FrameImageNext } from "@frames.js/render/next";
2627
import { Table, TableBody, TableCell, TableRow } from "@/components/table";
2728
import {
@@ -59,21 +60,18 @@ import { urlSearchParamsToObject } from "../utils/url-search-params-to-object";
5960
import { FrameUI } from "./frame-ui";
6061
import { useToast } from "@/components/ui/use-toast";
6162
import { ToastAction } from "@/components/ui/toast";
62-
import type { AnonymousSigner } from "@frames.js/render/identity/anonymous";
63-
import type { LensSigner } from "@frames.js/render/identity/lens";
64-
import type { FarcasterSigner } from "@frames.js/render/identity/farcaster";
65-
import type { XmtpSigner } from "@frames.js/render/identity/xmtp";
6663

6764
type FrameDiagnosticsProps = {
6865
stackItem: FramesStackItem;
66+
specification: SupportedParsingSpecification;
6967
};
7068

7169
function isPropertyExperimental([key, value]: [string, string]) {
7270
// tx is experimental
7371
return false;
7472
}
7573

76-
function FrameDiagnostics({ stackItem }: FrameDiagnosticsProps) {
74+
function FrameDiagnostics({ stackItem, specification }: FrameDiagnosticsProps) {
7775
const properties = useMemo(() => {
7876
/** tuple of key and value */
7977
const validProperties: [string, string][] = [];
@@ -97,7 +95,9 @@ function FrameDiagnostics({ stackItem }: FrameDiagnosticsProps) {
9795
return { validProperties, invalidProperties, isValid: true };
9896
}
9997

100-
const result = stackItem.frameResult;
98+
const result = getFrameParseResultFromStackItemBySpecifications(stackItem, [
99+
specification,
100+
]);
101101

102102
// we need to check validation errors first because getFrame incorrectly return a value for a key even if it's invalid
103103
for (const [key, reports] of Object.entries(result.reports)) {
@@ -160,8 +160,8 @@ function FrameDiagnostics({ stackItem }: FrameDiagnosticsProps) {
160160
{stackItem.speed > 5
161161
? `Request took more than 5s (${stackItem.speed} seconds). This may be normal: first request will take longer in development (as next.js builds), but in production, clients will timeout requests after 5s`
162162
: stackItem.speed > 4
163-
? `Warning: Request took more than 4s (${stackItem.speed} seconds). Requests will fail at 5s. This may be normal: first request will take longer in development (as next.js builds), but in production, if there's variance here, requests could fail in production if over 5s`
164-
: `${stackItem.speed} seconds`}
163+
? `Warning: Request took more than 4s (${stackItem.speed} seconds). Requests will fail at 5s. This may be normal: first request will take longer in development (as next.js builds), but in production, if there's variance here, requests could fail in production if over 5s`
164+
: `${stackItem.speed} seconds`}
165165
</TableCell>
166166
</TableRow>
167167
{properties.validProperties.map(([propertyKey, value]) => {
@@ -248,7 +248,8 @@ function ShortenedText({
248248

249249
const FramesRequestCardContentIcon: React.FC<{
250250
stackItem: FramesStackItem;
251-
}> = ({ stackItem }) => {
251+
specification: SupportedParsingSpecification;
252+
}> = ({ stackItem, specification }) => {
252253
if (stackItem.status === "pending") {
253254
return <LoaderIcon className="animate-spin" size={20} />;
254255
}
@@ -269,11 +270,15 @@ const FramesRequestCardContentIcon: React.FC<{
269270
return <ExternalLinkIcon size={20} color="green" />;
270271
}
271272

272-
if (stackItem.frameResult?.status === "failure") {
273+
const result = getFrameParseResultFromStackItemBySpecifications(stackItem, [
274+
specification,
275+
]);
276+
277+
if (result.status === "failure") {
273278
return <XCircle size={20} color="red" />;
274279
}
275280

276-
if (hasWarnings(stackItem.frameResult.reports)) {
281+
if (hasWarnings(result.reports)) {
277282
return <AlertTriangle size={20} color="orange" />;
278283
}
279284

@@ -282,10 +287,9 @@ const FramesRequestCardContentIcon: React.FC<{
282287

283288
const FramesRequestCardContent: React.FC<{
284289
stack: FramesStack;
285-
fetchFrame: FrameState<
286-
FarcasterSigner | XmtpSigner | LensSigner | AnonymousSigner | null
287-
>["fetchFrame"];
288-
}> = ({ fetchFrame, stack }) => {
290+
fetchFrame: FrameState["fetchFrame"];
291+
specification: SupportedParsingSpecification;
292+
}> = ({ fetchFrame, specification, stack }) => {
289293
return stack.map((frameStackItem, i) => {
290294
return (
291295
<button
@@ -312,7 +316,8 @@ const FramesRequestCardContent: React.FC<{
312316
<span className="ml-auto">
313317
<FramesRequestCardContentIcon
314318
stackItem={frameStackItem}
315-
></FramesRequestCardContentIcon>
319+
specification={specification}
320+
/>
316321
</span>
317322
</span>
318323
<span className="flex flex-row w-full">
@@ -369,10 +374,10 @@ type FrameDebuggerSharedProps = {
369374
type FrameDebuggerProps = FrameDebuggerSharedProps &
370375
(
371376
| {
372-
useFrameHook: () => FrameState<any, any>;
377+
useFrameHook: () => FrameState;
373378
}
374379
| {
375-
frameState: FrameState<any, any>;
380+
frameState: FrameState;
376381
}
377382
);
378383

@@ -478,10 +483,16 @@ export const FrameDebugger = React.forwardRef<
478483
[toast, showConsole]
479484
);
480485

486+
const currentParseResult =
487+
currentFrameStackItem && currentFrameStackItem.status === "done"
488+
? getFrameParseResultFromStackItemBySpecifications(
489+
currentFrameStackItem,
490+
[specification]
491+
)
492+
: undefined;
493+
481494
const isImageDebuggingAvailable =
482-
currentFrameStackItem &&
483-
"frameResult" in currentFrameStackItem &&
484-
!!currentFrameStackItem.frameResult.framesDebugInfo?.image;
495+
currentParseResult?.framesDebugInfo?.image;
485496

486497
return (
487498
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-[300px_500px_1fr] p-4 gap-4 bg-slate-50 max-w-full w-full">
@@ -541,6 +552,7 @@ export const FrameDebugger = React.forwardRef<
541552
<FramesRequestCardContent
542553
fetchFrame={frameState.fetchFrame}
543554
stack={frameState.framesStack}
555+
specification={specification}
544556
></FramesRequestCardContent>
545557
</CardContent>
546558
</Card>
@@ -621,8 +633,11 @@ export const FrameDebugger = React.forwardRef<
621633
)}
622634
<div className="space-y-1">
623635
{currentFrameStackItem?.status === "done" &&
624-
currentFrameStackItem.frameResult.frame.buttons
625-
?.filter(
636+
getFrameParseResultFromStackItemBySpecifications(
637+
currentFrameStackItem,
638+
[specification]
639+
)
640+
.frame.buttons?.filter(
626641
(button) =>
627642
button.target?.startsWith(
628643
"https://warpcast.com/~/add-cast-action"
@@ -685,7 +700,8 @@ export const FrameDebugger = React.forwardRef<
685700
<TabsContent className="overflow-y-auto" value="diagnostics">
686701
<FrameDiagnostics
687702
stackItem={currentFrameStackItem}
688-
></FrameDiagnostics>
703+
specification={specification}
704+
/>
689705
</TabsContent>
690706
<TabsContent
691707
className="overflow-y-auto"
@@ -719,15 +735,15 @@ export const FrameDebugger = React.forwardRef<
719735
<button
720736
className="underline"
721737
onClick={() => {
722-
if (!currentFrameStackItem) {
723-
return;
724-
}
738+
const frame =
739+
getFrameParseResultFromStackItemBySpecifications(
740+
currentFrameStackItem,
741+
[specification]
742+
).frame;
725743

726744
// Copy the text inside the text field
727745
navigator.clipboard.writeText(
728-
getFrameHtmlHead(
729-
currentFrameStackItem.frameResult.frame
730-
)
746+
getFrameHtmlHead(frame)
731747
);
732748
setCopySuccess(true);
733749
}}
@@ -748,7 +764,10 @@ export const FrameDebugger = React.forwardRef<
748764
"sourceFrame" in currentFrameStackItem.request &&
749765
currentFrameStackItem.request.sourceFrame
750766
? currentFrameStackItem.request.sourceFrame
751-
: currentFrameStackItem.frameResult.frame
767+
: getFrameParseResultFromStackItemBySpecifications(
768+
currentFrameStackItem,
769+
[specification]
770+
).frame
752771
)
753772
.split("<meta")
754773
.filter((t) => !!t)

packages/debugger/app/debugger-page.tsx

Lines changed: 81 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
type OnSignatureFunc,
1111
type FrameActionBodyPayload,
1212
type OnConnectWalletFunc,
13+
FarcasterFrameContext,
1314
} from "@frames.js/render";
1415
import { attribution } from "@frames.js/render/farcaster";
1516
import { useFrame } from "@frames.js/render/use-frame";
@@ -38,7 +39,10 @@ import {
3839
ActionDebugger,
3940
ActionDebuggerRef,
4041
} from "./components/action-debugger";
41-
import type { ParseResult } from "frames.js/frame-parsers";
42+
import type {
43+
ParseFramesWithReportsResult,
44+
SupportedParsingSpecification,
45+
} from "frames.js/frame-parsers";
4246
import { Loader2 } from "lucide-react";
4347
import { useToast } from "@/components/ui/use-toast";
4448
import { ToastAction } from "@/components/ui/toast";
@@ -120,7 +124,8 @@ export default function DebuggerPage({
120124
return undefined;
121125
}
122126
}, [searchParams.url]);
123-
const [initialFrame, setInitialFrame] = useState<ParseResult>();
127+
const [initialFrame, setInitialFrame] =
128+
useState<ParseFramesWithReportsResult>();
124129
const [initialAction, setInitialAction] =
125130
useState<CastActionDefinitionResponse>();
126131
const [mockHubContext, setMockHubContext] = useState<
@@ -573,7 +578,8 @@ export default function DebuggerPage({
573578

574579
const farcasterFrameConfig: UseFrameOptions<
575580
FarcasterSigner | null,
576-
FrameActionBodyPayload
581+
FrameActionBodyPayload,
582+
FarcasterFrameContext
577583
> = useMemo(() => {
578584
const attributionData = process.env.NEXT_PUBLIC_FARCASTER_ATTRIBUTION_FID
579585
? attribution(parseInt(process.env.NEXT_PUBLIC_FARCASTER_ATTRIBUTION_FID))
@@ -599,44 +605,82 @@ export default function DebuggerPage({
599605
return () => {
600606
const selectedProtocol = protocolConfiguration?.protocol ?? "farcaster";
601607

602-
switch (selectedProtocol) {
603-
case "anonymous": {
604-
// eslint-disable-next-line react-hooks/rules-of-hooks -- this is used as a hook in FrameDebugger
605-
return useFrame({
606-
...useFrameConfig,
607-
signerState: anonymousSignerState,
608-
specification: "openframes",
609-
frameContext: anonymousFrameContext,
610-
});
611-
}
612-
case "lens": {
613-
// eslint-disable-next-line react-hooks/rules-of-hooks -- this is used as a hook in FrameDebugger
614-
return useFrame({
615-
...useFrameConfig,
616-
signerState: lensSignerState,
617-
specification: "openframes",
618-
frameContext: lensFrameContext.frameContext,
619-
});
620-
}
621-
case "xmtp": {
622-
// eslint-disable-next-line react-hooks/rules-of-hooks -- this is used as a hook in FrameDebugger
623-
return useFrame({
624-
...useFrameConfig,
625-
signerState: xmtpSignerState,
626-
specification: "openframes",
627-
frameContext: xmtpFrameContext.frameContext,
628-
});
629-
}
630-
default: {
631-
// eslint-disable-next-line react-hooks/rules-of-hooks -- this is used as a hook in FrameDebugger
632-
return useFrame(farcasterFrameConfig);
633-
}
608+
/**
609+
* We need to send specification to server because of cast and composer actions
610+
*/
611+
function createFetchFn(
612+
specification: SupportedParsingSpecification
613+
): typeof fetch {
614+
return (input, init) => {
615+
if (typeof input !== "string") {
616+
throw new Error(
617+
"Expected first argument to fetchFn to be a string"
618+
);
619+
}
620+
621+
let url: string = input;
622+
623+
if (URL.canParse(input)) {
624+
const parsed = new URL(input);
625+
626+
parsed.searchParams.set("protocol", specification);
627+
628+
url = parsed.toString();
629+
} else {
630+
const temporaryUrl = "http://temporary-for-parsing-purposes.tld";
631+
const parsed = new URL(input, temporaryUrl);
632+
633+
parsed.searchParams.set("protocol", specification);
634+
635+
url = parsed.toString().replace(temporaryUrl, "");
636+
}
637+
638+
return fetch(url, init);
639+
};
634640
}
641+
642+
// eslint-disable-next-line react-hooks/rules-of-hooks -- this is used as a hook in FrameDebugger
643+
return useFrame({
644+
...useFrameConfig,
645+
fetchFn: createFetchFn(
646+
selectedProtocol === "farcaster" ? "farcaster" : "openframes"
647+
),
648+
signerState: anonymousSignerState,
649+
frameContext: anonymousFrameContext,
650+
// in debugger we to show only one specification at the time
651+
specification:
652+
selectedProtocol === "farcaster" ? "farcaster" : "openframes",
653+
async resolveActionContext() {
654+
switch (selectedProtocol) {
655+
case "anonymous":
656+
return {
657+
signerState: anonymousSignerState,
658+
frameContext: anonymousFrameContext,
659+
};
660+
case "lens":
661+
return {
662+
signerState: lensSignerState,
663+
frameContext: lensFrameContext.frameContext,
664+
};
665+
case "xmtp":
666+
return {
667+
signerState: xmtpSignerState,
668+
frameContext: xmtpFrameContext.frameContext,
669+
};
670+
default:
671+
return {
672+
signerState: farcasterSignerState,
673+
frameContext: farcasterFrameContext.frameContext,
674+
};
675+
}
676+
},
677+
});
635678
};
636679
}, [
637-
anonymousSignerState,
638680
anonymousFrameContext,
639-
farcasterFrameConfig,
681+
anonymousSignerState,
682+
farcasterFrameContext.frameContext,
683+
farcasterSignerState,
640684
lensFrameContext.frameContext,
641685
lensSignerState,
642686
protocolConfiguration?.protocol,

0 commit comments

Comments
 (0)