From ff8c075c21f68adcc8554c9cba1ad3f3208f43e5 Mon Sep 17 00:00:00 2001 From: Tom Meagher Date: Thu, 8 Feb 2024 01:13:35 -0500 Subject: [PATCH] feat(preview): input --- example/src/index.tsx | 10 ++-- src/index.tsx | 103 ++++++++++++++++++++++++++++++++---------- src/types.ts | 35 ++++++++------ 3 files changed, 107 insertions(+), 41 deletions(-) diff --git a/example/src/index.tsx b/example/src/index.tsx index 3d98f035..46f5a9ca 100644 --- a/example/src/index.tsx +++ b/example/src/index.tsx @@ -6,7 +6,9 @@ import { Button, Framework, TextInput } from 'farc' const app = new Framework() -app.frame('/', ({ status }) => { +app.frame('/', (props) => { + const { status, untrustedData } = props + const fruit = untrustedData?.inputText return { image: (
{ whiteSpace: 'pre-wrap', }} > - {status === 'response' ? 'Nice choice.' : 'Welcome!'} + {status === 'response' + ? `Nice choice.${fruit ? ` ${fruit.toUpperCase()}!!` : ''}` + : 'Welcome!'}
), - intents: status === 'initial' && [ + intents: [ , , , diff --git a/src/index.tsx b/src/index.tsx index 8e49d2b5..b82355f4 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -119,6 +119,7 @@ export class Framework extends Hono { ? (formData.get('buttonIndex') as string) : '', ) + // TODO: Sanitize input const inputText = formData.get('inputText') ? Buffer.from(formData.get('inputText') as string) : undefined @@ -200,7 +201,9 @@ export class Framework extends Hono { hash: `0x${bytesToHex(castId.hash)}`, }, fid, - inputText, + inputText: inputText + ? Buffer.from(inputText).toString('utf-8') + : undefined, messageHash: `0x${bytesToHex(message.hash)}`, network: 1, timestamp: message.data.timestamp, @@ -320,44 +323,72 @@ function Preview({ baseUrl, frame }: PreviewProps) { - {/* TODO: Text input */} - {frame.buttons && ( + + {Boolean(frame.input || frame.buttons?.length) && (
- {frame.buttons.map((button) => ( - - ))} + {frame.buttons.map((button) => ( + + ))} +
+ )} )} @@ -370,8 +401,13 @@ type DevtoolsProps = { } async function Devtools({ frame }: DevtoolsProps) { + const { debug: _d, title: _t, ...rest } = frame return ( -
+
+
+        {JSON.stringify(rest, null, 2)}
+      
+
         {frame.debug?.htmlTags.map((x) => (
           {x}
@@ -508,13 +544,21 @@ function htmlToFrame(html: string) {
   }
   buttons = buttons.toSorted((a, b) => a.index - b.index)
 
+  const input = properties['fc:frame:input:text']
+    ? {
+        text: properties['fc:frame:input:text'] ?? '',
+      }
+    : undefined
+
   const fallbackImageToUrl = !imageUrl
   const postUrlTooLong = postUrl.length > 2_048
+  // TODO: Validate
+  const inputTextTooLong = false
   // TODO: Figure out how this is determined
   // https://warpcast.com/~/developers/frames
   const valid = true
 
-  const frame = { buttons, imageUrl, postUrl, version }
+  const frame = { buttons, imageUrl, input, postUrl, version }
   return {
     ...frame,
     debug: {
@@ -523,6 +567,7 @@ function htmlToFrame(html: string) {
       fallbackImageToUrl,
       htmlTags: metaTags.map((x) => x.outerHTML),
       image,
+      inputTextTooLong,
       invalidButtons,
       postUrlTooLong,
       valid,
@@ -582,6 +627,16 @@ function getGlobalStyles() {
       background: var(--bn);
     }
 
+    input {
+      background: var(--bg);
+    }
+
+    pre {
+      margin: 0;
+    }
+
+    /** Reset **/
+
     button,
     input {
       font-family: inherit; 
diff --git a/src/types.ts b/src/types.ts
index 3f27db50..01dea6b2 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -8,7 +8,7 @@ export type UntrustedData = {
   buttonIndex?: FrameButton['index'] | undefined
   castId: { fid: number; hash: string }
   fid: number
-  inputText?: string
+  inputText?: string | undefined
   messageHash: string
   network: number
   timestamp: number
@@ -19,24 +19,25 @@ export type Frame = {
   buttons?: readonly FrameButton[] | undefined
   debug?: FrameDebug | undefined
   imageUrl: string
+  input?: FrameInput | undefined
   postUrl: string
   title: string
   version: FrameVersion
 }
 
-export type FrameDebug = {
-  buttons?: readonly FrameButton[] | undefined
-  buttonsAreOutOfOrder: boolean
-  fallbackImageToUrl: boolean
-  htmlTags: readonly string[]
-  image: string
-  imageUrl: string
-  invalidButtons: readonly FrameButton['index'][]
-  postUrl: string
-  postUrlTooLong: boolean
-  valid: boolean
-  version: FrameVersion
-}
+export type FrameDebug = Pretty<
+  Omit & {
+    buttonsAreOutOfOrder: boolean
+    fallbackImageToUrl: boolean
+    htmlTags: readonly string[]
+    image: string
+    inputTextTooLong: boolean
+    invalidButtons: readonly FrameButton['index'][]
+    postUrl: string
+    postUrlTooLong: boolean
+    valid: boolean
+  }
+>
 
 export type FrameButton = {
   index: 1 | 2 | 3 | 4
@@ -44,6 +45,10 @@ export type FrameButton = {
   type: 'post' | 'post_redirect'
 }
 
+export type FrameInput = {
+  text: string
+}
+
 export type FrameVersion = 'vNext'
 
 export type FrameMetaTagPropertyName =
@@ -56,3 +61,5 @@ export type FrameMetaTagPropertyName =
   | `fc:frame:button:${FrameButton['index']}:action`
   | `fc:frame:button:${FrameButton['index']}:target`
   | `fc:frame:button:${FrameButton['index']}`
+
+type Pretty = { [key in keyof type]: type[key] } & unknown