Skip to content

Commit 7018e0c

Browse files
feat: multi specification support (#517)
* feat: parse all specs at once * feat: handle multi protocol result in render package * chore: make use frame backward compatible * feat: unstable multi specification api * feat: support new unstable api in debugger * feat: add useful properties * chore: remove composer and cast actions code * refactor: use resolveSigner function * chore: export unstable hook from use-frame * revert: debugger * fix: incorrect specification * fix: call signerless press if user doesn't have a signer * feat: customizable frame state hook * fix: make hooks generic * fix: allow any frame state in frame ui * chore: changeset * feat: mini app support * feat: add resolveAddress function * feat: send form to post message handler * feat: use new composer action hook in debugger * chore: changeset * feat: export message listener and parser * feat: allow to customize fetch function * chore: rename function * fix: types * chore: use resolveAddress instead of connectedAddress * chore: move debugger frame state hook to debugger
1 parent 0591372 commit 7018e0c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+4905
-828
lines changed

.changeset/cuddly-rivers-hide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@frames.js/render": patch
3+
---
4+
5+
feat: customizable frame state for useFrame hook

.changeset/grumpy-berries-love.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@frames.js/debugger": patch
3+
"@frames.js/render": patch
4+
---
5+
6+
feat: useComposerAction hook

.changeset/twenty-pugs-roll.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"frames.js": patch
3+
"@frames.js/debugger": patch
4+
"@frames.js/render": patch
5+
---
6+
7+
feat: multi specification support

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

Lines changed: 6 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { cn } from "@/lib/utils";
99
import {
1010
type FarcasterFrameContext,
1111
type FrameActionBodyPayload,
12-
OnComposeFormActionFuncReturnType,
1312
defaultTheme,
1413
} from "@frames.js/render";
1514
import { ParsingReport } from "frames.js";
@@ -26,7 +25,6 @@ import React, {
2625
useEffect,
2726
useImperativeHandle,
2827
useMemo,
29-
useRef,
3028
useState,
3129
} from "react";
3230
import { Button } from "../../@/components/ui/button";
@@ -37,12 +35,9 @@ import { useFrame } from "@frames.js/render/use-frame";
3735
import { WithTooltip } from "./with-tooltip";
3836
import { useToast } from "@/components/ui/use-toast";
3937
import type { CastActionDefinitionResponse } from "../frames/route";
40-
import { ComposerFormActionDialog } from "./composer-form-action-dialog";
41-
import { AwaitableController } from "../lib/awaitable-controller";
42-
import type { ComposerActionFormResponse } from "frames.js/types";
43-
import { CastComposer, CastComposerRef } from "./cast-composer";
4438
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
4539
import type { FarcasterSigner } from "@frames.js/render/identity/farcaster";
40+
import { ComposerActionDebugger } from "./composer-action-debugger";
4641

4742
type FrameDebuggerFramePropertiesTableRowsProps = {
4843
actionMetadataItem: CastActionDefinitionResponse;
@@ -227,47 +222,9 @@ export const ActionDebugger = React.forwardRef<
227222
}
228223
}, [copySuccess, setCopySuccess]);
229224

230-
const [composeFormActionDialogSignal, setComposerFormActionDialogSignal] =
231-
useState<AwaitableController<
232-
OnComposeFormActionFuncReturnType,
233-
ComposerActionFormResponse
234-
> | null>(null);
235225
const actionFrameState = useFrame({
236226
...farcasterFrameConfig,
237-
async onComposerFormAction({ form }) {
238-
try {
239-
const dialogSignal = new AwaitableController<
240-
OnComposeFormActionFuncReturnType,
241-
ComposerActionFormResponse
242-
>(form);
243-
244-
setComposerFormActionDialogSignal(dialogSignal);
245-
246-
const result = await dialogSignal;
247-
248-
// if result is undefined then user closed the dialog window without submitting
249-
// otherwise we have updated data
250-
if (result?.composerActionState) {
251-
castComposerRef.current?.updateState(result.composerActionState);
252-
}
253-
254-
return result;
255-
} catch (e) {
256-
console.error(e);
257-
toast({
258-
title: "Error occurred",
259-
description:
260-
e instanceof Error
261-
? e.message
262-
: "Unexpected error, check the console for more info",
263-
variant: "destructive",
264-
});
265-
} finally {
266-
setComposerFormActionDialogSignal(null);
267-
}
268-
},
269227
});
270-
const castComposerRef = useRef<CastComposerRef>(null);
271228
const [castActionDefinition, setCastActionDefinition] = useState<Exclude<
272229
CastActionDefinitionResponse,
273230
{ status: "failure" }
@@ -401,57 +358,14 @@ export const ActionDebugger = React.forwardRef<
401358
actionMetadataItem={actionMetadataItem}
402359
onRefreshUrl={() => refreshUrl()}
403360
>
404-
<CastComposer
405-
farcasterFrameConfig={farcasterFrameConfig}
406-
ref={castComposerRef}
407-
composerAction={actionMetadataItem.action}
408-
onComposerActionClick={(composerActionState) => {
409-
if (actionMetadataItem.status !== "success") {
410-
console.error(actionMetadataItem);
411-
412-
toast({
413-
title: "Invalid action metadata",
414-
description:
415-
"Please check the console for more information",
416-
variant: "destructive",
417-
});
418-
return;
419-
}
420-
421-
Promise.resolve(
422-
actionFrameState.onComposerActionButtonPress({
423-
castAction: {
424-
...actionMetadataItem.action,
425-
url: actionMetadataItem.url,
426-
},
427-
composerActionState,
428-
// clear stack, this removes first item that will appear in the debugger
429-
clearStack: true,
430-
})
431-
).catch((e: unknown) => {
432-
// eslint-disable-next-line no-console -- provide feedback to the user
433-
console.error(e);
434-
});
361+
<ComposerActionDebugger
362+
actionMetadata={actionMetadataItem.action}
363+
url={actionMetadataItem.url}
364+
onToggleToCastActionDebugger={() => {
365+
setActiveTab("cast-action");
435366
}}
436367
/>
437368
</ActionInfo>
438-
439-
{!!composeFormActionDialogSignal && (
440-
<ComposerFormActionDialog
441-
connectedAddress={farcasterFrameConfig.connectedAddress}
442-
composerActionForm={composeFormActionDialogSignal.data}
443-
onClose={() => {
444-
composeFormActionDialogSignal.resolve(undefined);
445-
}}
446-
onSave={({ composerState }) => {
447-
composeFormActionDialogSignal.resolve({
448-
composerActionState: composerState,
449-
});
450-
}}
451-
onTransaction={farcasterFrameConfig.onTransaction}
452-
onSignature={farcasterFrameConfig.onSignature}
453-
/>
454-
)}
455369
</TabsContent>
456370
</Tabs>
457371
</>

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

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,21 @@ import {
1111
ExternalLinkIcon,
1212
} from "lucide-react";
1313
import IconByName from "./octicons";
14-
import { useFrame } from "@frames.js/render/use-frame";
14+
import { useFrame_unstable } from "@frames.js/render/use-frame";
1515
import { WithTooltip } from "./with-tooltip";
16-
import type {
17-
FarcasterFrameContext,
18-
FrameActionBodyPayload,
19-
FrameStackDone,
20-
} from "@frames.js/render";
16+
import { fallbackFrameContext } from "@frames.js/render";
2117
import { FrameUI } from "./frame-ui";
2218
import { useToast } from "@/components/ui/use-toast";
2319
import { ToastAction } from "@radix-ui/react-toast";
2420
import Link from "next/link";
25-
import type { FarcasterSigner } from "@frames.js/render/identity/farcaster";
21+
import { useFarcasterIdentity } from "../hooks/useFarcasterIdentity";
22+
import { useAccount } from "wagmi";
23+
import { FrameStackDone } from "@frames.js/render/unstable-types";
24+
import { useDebuggerFrameState } from "../hooks/useDebuggerFrameState";
2625

2726
type CastComposerProps = {
2827
composerAction: Partial<ComposerActionResponse>;
2928
onComposerActionClick: (state: ComposerActionState) => any;
30-
farcasterFrameConfig: Parameters<
31-
typeof useFrame<
32-
FarcasterSigner | null,
33-
FrameActionBodyPayload,
34-
FarcasterFrameContext
35-
>
36-
>[0];
3729
};
3830

3931
export type CastComposerRef = {
@@ -43,7 +35,7 @@ export type CastComposerRef = {
4335
export const CastComposer = React.forwardRef<
4436
CastComposerRef,
4537
CastComposerProps
46-
>(({ composerAction, farcasterFrameConfig, onComposerActionClick }, ref) => {
38+
>(({ composerAction, onComposerActionClick }, ref) => {
4739
const [state, setState] = useState<ComposerActionState>({
4840
text: "",
4941
embeds: [],
@@ -79,7 +71,6 @@ export const CastComposer = React.forwardRef<
7971
{state.embeds.slice(0, 2).map((embed, index) => (
8072
<li key={`${embed}-${index}`} className="flex flex-grow">
8173
<CastEmbedPreview
82-
farcasterFrameConfig={farcasterFrameConfig}
8374
onRemove={() => {
8475
const filteredEmbeds = state.embeds.filter(
8576
(_, i) => i !== index
@@ -119,13 +110,6 @@ export const CastComposer = React.forwardRef<
119110
CastComposer.displayName = "CastComposer";
120111

121112
type CastEmbedPreviewProps = {
122-
farcasterFrameConfig: Parameters<
123-
typeof useFrame<
124-
FarcasterSigner | null,
125-
FrameActionBodyPayload,
126-
FarcasterFrameContext
127-
>
128-
>[0];
129113
url: string;
130114
onRemove: () => void;
131115
};
@@ -147,15 +131,21 @@ function isAtLeastPartialFrame(stackItem: FrameStackDone): boolean {
147131
);
148132
}
149133

150-
function CastEmbedPreview({
151-
farcasterFrameConfig,
152-
onRemove,
153-
url,
154-
}: CastEmbedPreviewProps) {
134+
function CastEmbedPreview({ onRemove, url }: CastEmbedPreviewProps) {
135+
const account = useAccount();
155136
const { toast } = useToast();
156-
const frame = useFrame({
157-
...farcasterFrameConfig,
137+
const farcasterIdentity = useFarcasterIdentity();
138+
const frame = useFrame_unstable({
139+
frameStateHook: useDebuggerFrameState,
140+
async resolveAddress() {
141+
return account.address ?? null;
142+
},
158143
homeframeUrl: url,
144+
frameActionProxy: "/frames",
145+
frameGetProxy: "/frames",
146+
resolveSigner() {
147+
return farcasterIdentity.withContext(fallbackFrameContext);
148+
},
159149
});
160150

161151
const handleFrameError = useCallback(
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type {
2+
ComposerActionResponse,
3+
ComposerActionState,
4+
} from "frames.js/types";
5+
import { CastComposer, CastComposerRef } from "./cast-composer";
6+
import { useRef, useState } from "react";
7+
import { ComposerFormActionDialog } from "./composer-form-action-dialog";
8+
import { useFarcasterIdentity } from "../hooks/useFarcasterIdentity";
9+
10+
type ComposerActionDebuggerProps = {
11+
url: string;
12+
actionMetadata: Partial<ComposerActionResponse>;
13+
onToggleToCastActionDebugger: () => void;
14+
};
15+
16+
export function ComposerActionDebugger({
17+
actionMetadata,
18+
url,
19+
onToggleToCastActionDebugger,
20+
}: ComposerActionDebuggerProps) {
21+
const castComposerRef = useRef<CastComposerRef>(null);
22+
const signer = useFarcasterIdentity();
23+
const [actionState, setActionState] = useState<ComposerActionState | null>(
24+
null
25+
);
26+
27+
return (
28+
<>
29+
<CastComposer
30+
composerAction={actionMetadata}
31+
onComposerActionClick={setActionState}
32+
ref={castComposerRef}
33+
/>
34+
{!!actionState && (
35+
<ComposerFormActionDialog
36+
actionState={actionState}
37+
signer={signer}
38+
url={url}
39+
onClose={() => {
40+
setActionState(null);
41+
}}
42+
onSubmit={(newActionState) => {
43+
castComposerRef.current?.updateState(newActionState);
44+
setActionState(null);
45+
}}
46+
onToggleToCastActionDebugger={onToggleToCastActionDebugger}
47+
/>
48+
)}
49+
</>
50+
);
51+
}

0 commit comments

Comments
 (0)