Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev #297

Merged
merged 14 commits into from
Apr 5, 2024
Merged

Dev #297

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tall-wolves-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"frames.js": patch
---

chore: remove unnecessary url detection and use nextUrl instead
5 changes: 5 additions & 0 deletions .changeset/two-hairs-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"docs": patch
---

docs: render frame nextjs api docs
5 changes: 5 additions & 0 deletions .changeset/violet-adults-compare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"frames.js": patch
---

feat: add baseURL option
24 changes: 18 additions & 6 deletions docs/pages/reference/core/createFrames.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ The function passed to `frames` will be called with the context of a frame actio

- Type: `string`

A string that specifies the base path for all relative URLs in the frame definition. It defaults to `/`.
A string that specifies the base path for all relative URLs in the frame definition. It defaults to `/`. If `baseUrl` is provided, it will be resolved relatively to `baseUrl`. If the `baseUrl` option is not provided, it will use URL of current request and override its path with `basePath` when generating target URLs.

### `baseUrl`

- Type: `string | URL`

A string or URL object that specifies the base URL for all relative URLs in the frame definition. Can be used to override the URL detected from the request.

### `initialState`

Expand All @@ -50,10 +56,10 @@ For strong type support in the handler, the middleware should be typed as `Frame
```tsx
import { createFrames, types } from "frames.js/next";

const myMiddleware: types.FramesMiddleware<
any,
{ foo?: string }
> = async (ctx, next) => {
const myMiddleware: types.FramesMiddleware<any, { foo?: string }> = async (
ctx,
next
) => {
return next({ foo: "bar" });
};
```
Expand Down Expand Up @@ -161,7 +167,7 @@ const handleRequest = frames(async (ctx) => {
aspectRatio: "1:1",
},
buttons: [<Button action="post">Refresh</Button>],
headers: {// [!code focus]
headers: { // [!code focus]
// Max cache age in seconds // [!code focus]
"Cache-Control": "max-age=0", // [!code focus]
}, // [!code focus]
Expand Down Expand Up @@ -208,6 +214,12 @@ Core middleware is included and executed by default and gives you access to the

Specifies the base path for all relative URLs in the frame definition.

### `baseUrl`

- Type: `URL`

The resolved base URL for all relative URLs in the frame definition. All relative URLs are resolved relatively to this value.

### `initialState`

- Type: generic
Expand Down
27 changes: 27 additions & 0 deletions docs/pages/reference/render/next/get.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,34 @@
# GET

Fetches HTML content from a given URL and attempts to extract a frame from it.

## Usage

```tsx [./frames/route.tsx]
export { GET, POST } from "@frames.js/render/next';
```

### Request

- **URL Query Parameters:**
- `url` (required): The URL from which the HTML content is to be fetched.

### Response

Depending on the outcome of the fetch and frame extraction process, the response can vary:

- **Success:**
- Returns a JSON object containing the extracted frame data and any errors encountered during the frame extraction process.
- **Invalid URL:**

- If the `url` parameter is missing or invalid, returns a JSON object with a `message` indicating "Invalid URL" and a status code of 400.

- **Fetch or Processing Errors:**
- In case of errors during the fetch operation or frame extraction, returns a JSON object with a `message` detailing the error and a status code of 500.

### Errors

Errors are communicated through the status codes and messages in the JSON response:

- **400 Bad Request:** Indicates that the required `url` query parameter is missing or invalid.
- **500 Internal Server Error:** Indicates an error during the fetch operation or while processing the fetched content.
32 changes: 32 additions & 0 deletions docs/pages/reference/render/next/post.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,39 @@
# POST

Proxies a POST request to the specified `postUrl` with the payload provided in the request body.

## Usage

```tsx [./frames/route.tsx]
export { GET, POST } from "@frames.js/render/next';
```

### Request

- **URL Query Parameters:**

- `postType` (optional): A string indicating the type of the POST request. It can be either `post_redirect` to handle redirects manually or `tx` for transaction requests.
- `postUrl` (required): The URL to which the POST request should be proxied.

- **Body:** JSON payload conforming to the `FrameActionPayload` structure imported from `frames.js`.

### Response

The response varies depending on the `postType` and the status of the request to the `postUrl`.

- **For a 302 status code from `postUrl`:**

- Returns a JSON object containing the `location` header value from the response, with a status code of 302.

- **For `postType` of `tx`:**

- Returns the JSON response received from the `postUrl`.

- **For other responses:**
- Attempts to extract a frame from the HTML content returned by the `postUrl`, using the `getFrame` function from `frames.js`. Returns a JSON object containing the frame data and any errors encountered during the frame extraction process.

### Errors

- Returns a response generated by `Response.error()` in case of:
- Absence of the `postUrl` parameter in the request.
- Any fetch operation or processing errors.
90 changes: 90 additions & 0 deletions packages/frames.js/src/core/createFrames.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
});

it("supports custom global middleware", async () => {
const customMiddleware: FramesMiddleware<any, { custom: string }> = async (

Check warning on line 46 in packages/frames.js/src/core/createFrames.test.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 46 in packages/frames.js/src/core/createFrames.test.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
ctx,
next
) => {
Expand All @@ -65,7 +65,7 @@
});

it("supports per route middleware", async () => {
const customMiddleware: FramesMiddleware<any, { custom: string }> = async (

Check warning on line 68 in packages/frames.js/src/core/createFrames.test.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 68 in packages/frames.js/src/core/createFrames.test.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
ctx,
next
) => {
Expand All @@ -90,13 +90,13 @@
});

it("works with parallel middleware", async () => {
const middleware0: FramesMiddleware<any, { test0: boolean }> = async (

Check warning on line 93 in packages/frames.js/src/core/createFrames.test.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 93 in packages/frames.js/src/core/createFrames.test.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
context,
next
) => {
return next({ test0: true });
};
const middleware1: FramesMiddleware<any, { test1: boolean }> = async (

Check warning on line 99 in packages/frames.js/src/core/createFrames.test.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 99 in packages/frames.js/src/core/createFrames.test.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
context,
next
) => {
Expand Down Expand Up @@ -137,4 +137,94 @@

expect(response).toBeInstanceOf(Response);
});

it("fails if invalid URL is set as baseUrl", () => {
expect(() => createFrames({ baseUrl: "invalid" })).toThrow(
"Invalid baseUrl: Invalid URL"
);
});

it("sets baseUrl on context if provided", async () => {
const handler = createFrames({ baseUrl: "http://override.com" });

const routeHandler = handler((ctx) => {
expect(ctx.baseUrl.toString()).toBe("http://override.com/");
return Response.json({ test: true });
});

await expect(
routeHandler(new Request("http://test.com"))
).resolves.toHaveProperty("status", 200);
});

it("resolves resolvedUrl against request URL and / if no basePath or baseUrl are provided", async () => {
const handler = createFrames();

const routeHandler = handler((ctx) => {
expect(ctx.baseUrl.toString()).toBe("http://test.com/");
return Response.json({ test: true });
});

await expect(
routeHandler(new Request("http://test.com/this-will-be-removed"))
).resolves.toHaveProperty("status", 200);
});

it("resolves resolvedUrl against request URL when only basePath is provided", async () => {
const handler = createFrames({ basePath: "/test" });

const routeHandler = handler((ctx) => {
expect(ctx.baseUrl.toString()).toBe("http://test.com/test");
return Response.json({ test: true });
});

await expect(
routeHandler(new Request("http://test.com/this-will-be-removed"))
).resolves.toHaveProperty("status", 200);
});

it("resolves resolvedUrl against baseUrl and / when only baseUrl is provided", async () => {
const handler = createFrames({ baseUrl: "http://override.com" });

const routeHandler = handler((ctx) => {
expect(ctx.baseUrl.toString()).toBe("http://override.com/");
return Response.json({ test: true });
});

await expect(
routeHandler(new Request("http://test.com/this-will-be-removed"))
).resolves.toHaveProperty("status", 200);
});

it("resolves resolvedUrl against baseUrl and basePath if both are provided", async () => {
const handler = createFrames({
baseUrl: "http://override.com",
basePath: "/test",
});

const routeHandler = handler((ctx) => {
expect(ctx.baseUrl.toString()).toBe("http://override.com/test");
return Response.json({ test: true });
});

await expect(
routeHandler(new Request("http://test.com/this-will-be-removed"))
).resolves.toHaveProperty("status", 200);
});

it("resolves basePath relatively to baseUrl", async () => {
const handler = createFrames({
baseUrl: "http://override.com/test",
basePath: "/test2",
});

const routeHandler = handler((ctx) => {
expect(ctx.baseUrl.toString()).toBe("http://override.com/test/test2");
return Response.json({ test: true });
});

await expect(
routeHandler(new Request("http://test.com/this-will-be-removed"))
).resolves.toHaveProperty("status", 200);
});
});
15 changes: 15 additions & 0 deletions packages/frames.js/src/core/createFrames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
FramesRequestHandlerFunction,
JsonValue,
} from "./types";
import { resolveBaseUrl } from "./utils";

export function createFrames<
TState extends JsonValue | undefined = JsonValue | undefined,
Expand All @@ -17,6 +18,7 @@ export function createFrames<
basePath = "/",
initialState,
middleware,
baseUrl,
}: FramesOptions<TState, TMiddlewares> = {}): FramesRequestHandlerFunction<
TState,
typeof coreMiddleware,
Expand All @@ -25,6 +27,18 @@ export function createFrames<
> {
const globalMiddleware: FramesMiddleware<TState, FramesContext<TState>>[] =
middleware || [];
let url: URL | undefined;

// validate baseURL
if (typeof baseUrl === "string") {
try {
url = new URL(baseUrl);
} catch (e) {
throw new Error(`Invalid baseUrl: ${(e as Error).message}`);
}
} else {
url = baseUrl;
}

/**
* This function takes handler function that does the logic with the help of context and returns one of possible results
Expand Down Expand Up @@ -65,6 +79,7 @@ export function createFrames<
initialState: initialState as TState,
request,
url: new URL(request.url),
baseUrl: resolveBaseUrl(request, url, basePath),
};

const result = await composedMiddleware(context);
Expand Down
61 changes: 60 additions & 1 deletion packages/frames.js/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ export type FramesContext<TState extends JsonValue | undefined = JsonValue> = {
* All frame relative targets will be resolved relative to this
*/
basePath: string;
/**
* URL resolved based on current request URL, baseUrl and basePath. This URL is used to generate target URLs.
*/
baseUrl: URL;
/**
* Values passed to createFrames()
*/
Expand Down Expand Up @@ -192,10 +196,65 @@ export type FramesOptions<
TFrameMiddleware extends FramesMiddleware<any, any>[] | undefined,
> = {
/**
* All frame relative targets will be resolved relative to this
* All frame relative targets will be resolved relative to this. `basePath` is always resolved relatively to baseUrl (if provided). If `baseUrl` is not provided then `basePath` overrides the path part of current request's URL.
*
* @example
* ```ts
* {
* basePath: '/foo'
* }
*
* // if the request URL is http://mydomain.dev/bar then context.url will be set to http://mydomain.dev/foo
* // if the request URL is http://mydomain.dev/ then context.url will be set to http://mydomain.dev/foo
* ```
*
* @example
* ```ts
* {
* basePath: '/foo',
* baseUrl: 'http://mydomain.dev'
* }
*
* // context.url will be set to http://mydomain.dev/foo
* ```
*
* @example
* ```ts
* {
* basePath: '/foo',
* baseUrl: 'http://localhost:3000/test'
* }
*
* // context.url will be set to http://localhost:3000/test/foo
* ```
*
* @defaultValue '/'
*/
basePath?: string;
/**
* Overrides the detected URL of the request. URL is used in combination with `basePath` to generate target URLs for Buttons.
* Provided value must be full valid URL with protocol and domain. `basePath` if provided is resolved relatively to this URL.
* This is useful if the URL detection fails to recognize the correct URL or if you want to override it.
*
* This URL also overrides the request.url value with the provided value.
*
* @example
* ```ts
* // using string, the value of ctx.url and request.url will be set to this value
* {
* baseUrl: 'https://example.com',
* }
* ```
*
* @example
* ```ts
* // using URL, the value of ctx.url and request.url will be set to this value
* {
* baseUrl: new URL('https://example.com'),
* }
* ```
*/
baseUrl?: string | URL;
/**
* Initial state, used if no state is provided in the message or you are on initial frame.
*
Expand Down
Loading
Loading