From 5dc523a730eae37650330cbf0a727563e52d9e37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Kvasnic=CC=8Ca=CC=81k?= Date: Wed, 3 Apr 2024 09:02:33 +0200 Subject: [PATCH] fix: even more issues --- .eslintignore | 1 - .eslintrc.js | 1 + packages/eslint-config/library.js | 2 + packages/frames.js/src/core/createFrames.ts | 6 +- packages/frames.js/src/getTokenFromUrl.ts | 2 +- packages/frames.js/src/hono/index.test.tsx | 6 +- packages/frames.js/src/hono/index.ts | 24 +++++--- packages/frames.js/src/hono/test.types.tsx | 31 ++++++++-- packages/frames.js/src/lib/stream-pump.ts | 59 +++++++++---------- .../middleware/farcasterHubContext.test.ts | 3 +- .../src/middleware/farcasterHubContext.ts | 4 +- .../src/middleware/openframes.test.ts | 10 ++-- .../frames.js/src/validateFrameMessage.ts | 1 + 13 files changed, 94 insertions(+), 56 deletions(-) delete mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 5e94b0c8c..000000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -packages/frames.js/src/farcaster/generated/** \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index fd0ba955a..22f3ef9bd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,4 +5,5 @@ module.exports = { parserOptions: { project: true, }, + ignorePatterns: ["**/farcaster/generated/*.ts"], }; diff --git a/packages/eslint-config/library.js b/packages/eslint-config/library.js index 8b66f3684..28223ab3f 100644 --- a/packages/eslint-config/library.js +++ b/packages/eslint-config/library.js @@ -33,9 +33,11 @@ module.exports = { "@typescript-eslint/require-await": "warn", "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-unnecessary-condition": "warn", + "@typescript-eslint/consistent-type-imports": "error", "jest/expect-expect": "warn", "react/jsx-sort-props": "off", "unicorn/filename-case": "off", eqeqeq: "off", + "no-await-in-loop": "off", }, }; diff --git a/packages/frames.js/src/core/createFrames.ts b/packages/frames.js/src/core/createFrames.ts index 2c78f3995..8cf98fb6c 100644 --- a/packages/frames.js/src/core/createFrames.ts +++ b/packages/frames.js/src/core/createFrames.ts @@ -30,8 +30,10 @@ export function createFrames< * This function takes handler function that does the logic with the help of context and returns one of possible results */ return function createFramesRequestHandler(handler, options = {}) { - const perRouteMiddleware: FramesMiddleware>[] = - Array.isArray(options.middleware) ? options.middleware : []; + const perRouteMiddleware: FramesMiddleware< + JsonValue | undefined, + FramesContext + >[] = Array.isArray(options.middleware) ? options.middleware : []; const composedMiddleware = composeMiddleware< FramesContext, diff --git a/packages/frames.js/src/getTokenFromUrl.ts b/packages/frames.js/src/getTokenFromUrl.ts index cf3b12d5b..84c1aa936 100644 --- a/packages/frames.js/src/getTokenFromUrl.ts +++ b/packages/frames.js/src/getTokenFromUrl.ts @@ -15,7 +15,7 @@ export function getTokenFromUrl(url: string): ParsedToken { } return { - namespace: namespace, + namespace, chainId: parseInt(chainId), address, tokenId: tokenId || undefined, diff --git a/packages/frames.js/src/hono/index.test.tsx b/packages/frames.js/src/hono/index.test.tsx index 205b61c25..de3949743 100644 --- a/packages/frames.js/src/hono/index.test.tsx +++ b/packages/frames.js/src/hono/index.test.tsx @@ -8,12 +8,12 @@ describe("hono adapter", () => { it("correctly integrates with Hono", async () => { const frames = lib.createFrames(); - const handler = frames(async (ctx) => { + const handler = frames((ctx) => { expect(ctx.request.url).toBe("http://localhost:3000/"); return { image: Test, - buttons: [Click me], + buttons: [Click me], }; }); @@ -39,7 +39,7 @@ describe("hono adapter", () => { }, }); - const handler = frames(async (ctx) => { + const handler = frames((ctx) => { expect(ctx.state).toEqual({ test: false }); return { diff --git a/packages/frames.js/src/hono/index.ts b/packages/frames.js/src/hono/index.ts index f580d6f4c..71a7f953b 100644 --- a/packages/frames.js/src/hono/index.ts +++ b/packages/frames.js/src/hono/index.ts @@ -1,7 +1,9 @@ -export { Button, type types } from "../core"; -import { createFrames as coreCreateFrames, types } from "../core"; import type { Handler } from "hono"; -import { CoreMiddleware } from "../middleware"; +import type { types } from "../core"; +import { createFrames as coreCreateFrames } from "../core"; +import type { CoreMiddleware } from "../middleware"; + +export { Button, type types } from "../core"; type CreateFramesForHono = types.CreateFramesFunctionDefinition< CoreMiddleware, @@ -12,6 +14,7 @@ type CreateFramesForHono = types.CreateFramesFunctionDefinition< * Creates Frames instance to use with you Hono server * * @example + * ```tsx * import { createFrames, Button } from 'frames.js/hono'; * import { Hono } from 'hono'; * @@ -30,17 +33,24 @@ type CreateFramesForHono = types.CreateFramesFunctionDefinition< * const app = new Hono(); * * app.on(['GET', 'POST'], '/', honoHandler); + * ``` */ -// @ts-expect-error +// @ts-expect-error -- this code is correct just function doesn't satisfy the type export const createFrames: CreateFramesForHono = function createFramesForHono( - options?: types.FramesOptions + options?: types.FramesOptions ) { const frames = coreCreateFrames(options); return function honoFramesHandler< - TPerRouteMiddleware extends types.FramesMiddleware[], + TPerRouteMiddleware extends types.FramesMiddleware< + types.JsonValue | undefined, + Record + >[], >( - handler: types.FrameHandlerFunction, + handler: types.FrameHandlerFunction< + types.JsonValue | undefined, + Record + >, handlerOptions?: types.FramesRequestHandlerFunctionOptions ) { const framesHandler = frames(handler, handlerOptions); diff --git a/packages/frames.js/src/hono/test.types.tsx b/packages/frames.js/src/hono/test.types.tsx index 29502138d..43368bf2a 100644 --- a/packages/frames.js/src/hono/test.types.tsx +++ b/packages/frames.js/src/hono/test.types.tsx @@ -1,5 +1,7 @@ -import { Handler } from 'hono'; -import { createFrames, types } from '.'; +/* eslint-disable @typescript-eslint/require-await -- we are testing for promise compatibility */ +import type { Handler } from 'hono'; +import type { types } from '.'; +import { createFrames } from '.'; const framesWithoutState = createFrames(); framesWithoutState(async ctx => { @@ -38,8 +40,29 @@ framesWithExplicitState(async ctx => { test: boolean; }; ctx satisfies { - message?: any; - pressedButton?: any; + message?: unknown; + pressedButton?: unknown; + request: Request; + } + + return { + image: 'http://test.png' + }; +}) satisfies Handler; + +const framesWithExplicitStateNoPromise = createFrames<{ + test: boolean; +}>({}); +framesWithExplicitStateNoPromise(ctx => { + ctx.state satisfies { + test: boolean; + }; + ctx.initialState satisfies { + test: boolean; + }; + ctx satisfies { + message?: unknown; + pressedButton?: unknown; request: Request; } diff --git a/packages/frames.js/src/lib/stream-pump.ts b/packages/frames.js/src/lib/stream-pump.ts index 0e643ba00..d89dd54b6 100644 --- a/packages/frames.js/src/lib/stream-pump.ts +++ b/packages/frames.js/src/lib/stream-pump.ts @@ -27,16 +27,13 @@ export class StreamPump { new Stream.Readable().readableHighWaterMark; this.accumalatedSize = 0; this.stream = stream; - this.enqueue = this.enqueue.bind(this); - this.error = this.error.bind(this); - this.close = this.close.bind(this); } - size(chunk: Uint8Array) { - return chunk?.byteLength || 0; + size(chunk: Uint8Array): number { + return chunk.byteLength || 0; } - start(controller: ReadableStreamController) { + start(controller: ReadableStreamController): void { this.controller = controller; this.stream.on("data", this.enqueue); this.stream.once("error", this.error); @@ -44,11 +41,11 @@ export class StreamPump { this.stream.once("close", this.close); } - pull() { + pull(): void { this.resume(); } - cancel(reason?: Error) { + cancel(reason?: Error): void { if (this.stream.destroy) { this.stream.destroy(reason); } @@ -59,17 +56,17 @@ export class StreamPump { this.stream.off("close", this.close); } - enqueue(chunk: Uint8Array | string) { + enqueue = (chunk: Uint8Array | string): void => { if (this.controller) { try { - let bytes = chunk instanceof Uint8Array ? chunk : Buffer.from(chunk); + const bytes = chunk instanceof Uint8Array ? chunk : Buffer.from(chunk); - let available = (this.controller.desiredSize || 0) - bytes.byteLength; + const available = (this.controller.desiredSize || 0) - bytes.byteLength; this.controller.enqueue(bytes); if (available <= 0) { this.pause(); } - } catch (error: any) { + } catch (error) { this.controller.error( new Error( "Could not create Buffer, chunk must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object" @@ -78,53 +75,54 @@ export class StreamPump { this.cancel(); } } - } + }; - pause() { + pause(): void { if (this.stream.pause) { this.stream.pause(); } } - resume() { + resume(): void { if (this.stream.readable && this.stream.resume) { this.stream.resume(); } } - close() { + close = (): void => { if (this.controller) { this.controller.close(); delete this.controller; } - } + }; - error(error: Error) { + error = (error: Error): void => { if (this.controller) { this.controller.error(error); delete this.controller; } - } + }; } -export const createReadableStreamFromReadable = ( +export function createReadableStreamFromReadable( source: Readable & { readableHighWaterMark?: number } -) => { - let pump = new StreamPump(source); - let stream = new ReadableStream(pump, pump); +): ReadableStream { + const pump = new StreamPump(source); + const stream = new ReadableStream(pump, pump); return stream; -}; +} -export async function writeReadableStreamToWritable( - stream: ReadableStream, +export async function writeReadableStreamToWritable( + stream: ReadableStream, writable: Writable -) { - let reader = stream.getReader(); - let flushable = writable as { flush?: Function }; +): Promise { + const reader = stream.getReader(); + const flushable = writable as { flush?: () => void }; try { + // eslint-disable-next-line no-constant-condition, @typescript-eslint/no-unnecessary-condition -- this is expected to be exhaustive while (true) { - let { done, value } = await reader.read(); + const { done, value } = await reader.read(); if (done) { writable.end(); @@ -132,6 +130,7 @@ export async function writeReadableStreamToWritable( } writable.write(value); + if (typeof flushable.flush === "function") { flushable.flush(); } diff --git a/packages/frames.js/src/middleware/farcasterHubContext.test.ts b/packages/frames.js/src/middleware/farcasterHubContext.test.ts index fb1e97ce9..872f6a55d 100644 --- a/packages/frames.js/src/middleware/farcasterHubContext.test.ts +++ b/packages/frames.js/src/middleware/farcasterHubContext.test.ts @@ -2,6 +2,7 @@ import { Message } from "../farcaster"; import { redirect } from "../core/redirect"; import type { FramesContext } from "../core/types"; import { createFrames } from "../core"; +import type { UserDataReturnType } from ".."; import { farcasterHubContext } from "./farcasterHubContext"; describe("farcasterHubContext middleware", () => { @@ -113,7 +114,7 @@ describe("farcasterHubContext middleware", () => { inputText: "hello", requesterFid: 123, state: JSON.stringify({ test: true }), - requestedUserData: expect.anything(), + requestedUserData: expect.anything() as UserDataReturnType, }, }) ); diff --git a/packages/frames.js/src/middleware/farcasterHubContext.ts b/packages/frames.js/src/middleware/farcasterHubContext.ts index 65d2c5789..e068b26bd 100644 --- a/packages/frames.js/src/middleware/farcasterHubContext.ts +++ b/packages/frames.js/src/middleware/farcasterHubContext.ts @@ -29,12 +29,12 @@ async function decodeFrameActionPayloadFromRequest( ): Promise { try { // use clone just in case someone wants to read body somewhere along the way - const body = await request + const body = (await request .clone() .json() .catch(() => { throw new RequestBodyNotJSONError(); - }); + })) as JSON; if (!isValidFrameActionPayload(body)) { throw new InvalidFrameActionPayloadError(); diff --git a/packages/frames.js/src/middleware/openframes.test.ts b/packages/frames.js/src/middleware/openframes.test.ts index aa4a78725..09bd63444 100644 --- a/packages/frames.js/src/middleware/openframes.test.ts +++ b/packages/frames.js/src/middleware/openframes.test.ts @@ -208,8 +208,8 @@ describe("openframes middleware", () => { clientProtocol: "foo@vNext", handler: { isValidPayload: () => false, - getFrameMessage: async () => { - return { test1: true }; + getFrameMessage: () => { + return Promise.resolve({ test1: true }); }, }, }); @@ -217,8 +217,8 @@ describe("openframes middleware", () => { clientProtocol: "bar@vNext", handler: { isValidPayload: () => true, - getFrameMessage: async () => { - return { test2: true }; + getFrameMessage: () => { + return Promise.resolve({ test2: true }); }, }, }); @@ -227,7 +227,7 @@ describe("openframes middleware", () => { middleware: [mw1, mw2], }); - const routeHandler = handler(async (ctx) => { + const routeHandler = handler((ctx) => { return { image: `/?test1=${ctx.message?.test1}&test2=${ctx.message?.test2}`, }; diff --git a/packages/frames.js/src/validateFrameMessage.ts b/packages/frames.js/src/validateFrameMessage.ts index d10ea0262..6e4d9a035 100644 --- a/packages/frames.js/src/validateFrameMessage.ts +++ b/packages/frames.js/src/validateFrameMessage.ts @@ -40,6 +40,7 @@ export async function validateFrameMessage( isValid: boolean; message: FrameActionMessage | undefined; }> { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- just in case if (!body) { throw new Error( "Tried to call validateFrameMessage with no frame action payload. You may be calling it incorrectly on the homeframe"