diff --git a/.changeset/violet-adults-compare.md b/.changeset/violet-adults-compare.md new file mode 100644 index 00000000..1f59d48d --- /dev/null +++ b/.changeset/violet-adults-compare.md @@ -0,0 +1,5 @@ +--- +"frames.js": patch +--- + +feat: add baseURL option diff --git a/docs/pages/reference/core/createFrames.mdx b/docs/pages/reference/core/createFrames.mdx index be7865df..e7e7e67a 100644 --- a/docs/pages/reference/core/createFrames.mdx +++ b/docs/pages/reference/core/createFrames.mdx @@ -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` @@ -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 = async ( + ctx, + next +) => { return next({ foo: "bar" }); }; ``` @@ -161,7 +167,7 @@ const handleRequest = frames(async (ctx) => { aspectRatio: "1:1", }, buttons: [], - headers: {// [!code focus] + headers: { // [!code focus] // Max cache age in seconds // [!code focus] "Cache-Control": "max-age=0", // [!code focus] }, // [!code focus] @@ -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 diff --git a/packages/frames.js/src/core/createFrames.test.ts b/packages/frames.js/src/core/createFrames.test.ts index c03ae3ae..c399b43f 100644 --- a/packages/frames.js/src/core/createFrames.test.ts +++ b/packages/frames.js/src/core/createFrames.test.ts @@ -137,4 +137,94 @@ describe("createFrames", () => { 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); + }); }); diff --git a/packages/frames.js/src/core/createFrames.ts b/packages/frames.js/src/core/createFrames.ts index 8cf98fb6..bd340f6a 100644 --- a/packages/frames.js/src/core/createFrames.ts +++ b/packages/frames.js/src/core/createFrames.ts @@ -9,6 +9,7 @@ import type { FramesRequestHandlerFunction, JsonValue, } from "./types"; +import { resolveBaseUrl } from "./utils"; export function createFrames< TState extends JsonValue | undefined = JsonValue | undefined, @@ -17,6 +18,7 @@ export function createFrames< basePath = "/", initialState, middleware, + baseUrl, }: FramesOptions = {}): FramesRequestHandlerFunction< TState, typeof coreMiddleware, @@ -25,6 +27,18 @@ export function createFrames< > { const globalMiddleware: FramesMiddleware>[] = 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 @@ -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); diff --git a/packages/frames.js/src/core/types.ts b/packages/frames.js/src/core/types.ts index b4c97a02..808346fd 100644 --- a/packages/frames.js/src/core/types.ts +++ b/packages/frames.js/src/core/types.ts @@ -41,6 +41,10 @@ export type FramesContext = { * 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() */ @@ -192,10 +196,65 @@ export type FramesOptions< TFrameMiddleware extends FramesMiddleware[] | 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. * diff --git a/packages/frames.js/src/core/utils.test.ts b/packages/frames.js/src/core/utils.test.ts index 195f4db2..f5442e12 100644 --- a/packages/frames.js/src/core/utils.test.ts +++ b/packages/frames.js/src/core/utils.test.ts @@ -1,20 +1,74 @@ import { generatePostButtonTargetURL, parseButtonInformationFromTargetURL, + resolveBaseUrl, } from "./utils"; +describe("resolveBaseUrl", () => { + it("uses URL from request if no baseUrl is provided", () => { + const request = new Request("http://test.com"); + const basePath = "/"; + + expect(resolveBaseUrl(request, undefined, basePath).toString()).toBe( + "http://test.com/" + ); + }); + + it('uses baseUrl if it is provided and basePath is "/"', () => { + const request = new Request("http://test.com"); + const basePath = "/"; + + expect( + resolveBaseUrl( + request, + new URL("http://override.com"), + basePath + ).toString() + ).toBe("http://override.com/"); + + expect( + resolveBaseUrl( + request, + new URL("http://override.com/test"), + basePath + ).toString() + ).toBe("http://override.com/test"); + }); + + it("resolves basePath relatively to baseUrl", () => { + const request = new Request("http://test.com"); + const basePath = "/test"; + + expect( + resolveBaseUrl( + request, + new URL("http://override.com"), + basePath + ).toString() + ).toBe("http://override.com/test"); + }); + + it("overrides path on request URL with basePath", () => { + const request = new Request("http://test.com/this-will-be-removed"); + const basePath = "/test"; + + expect(resolveBaseUrl(request, undefined, basePath).toString()).toBe( + "http://test.com/test" + ); + }); +}); + describe("generatePostButtonTargetURL", () => { it("generates an URL for post button without target and without state", () => { - const expected = new URL("/test", "http://test.com"); + const expected = new URL("/", "http://test.com"); expected.searchParams.set("__bi", "1-p"); expect( generatePostButtonTargetURL({ target: undefined, - basePath: "/", buttonAction: "post", buttonIndex: 1, - currentURL: new URL("http://test.com/test"), + baseUrl: new URL("http://test.com"), }) ).toBe(expected.toString()); }); @@ -26,10 +80,9 @@ describe("generatePostButtonTargetURL", () => { expect( generatePostButtonTargetURL({ - basePath: "/", buttonAction: "post", buttonIndex: 1, - currentURL: new URL("http://test.com/test"), + baseUrl: new URL("http://test.com"), target: { query: { test: "test" } }, }) ).toBe(expected.toString()); @@ -42,10 +95,9 @@ describe("generatePostButtonTargetURL", () => { expect( generatePostButtonTargetURL({ target: "/test", - basePath: "/", buttonAction: "post", buttonIndex: 1, - currentURL: new URL("http://test.com"), + baseUrl: new URL("http://test.com"), }) ).toBe(expected.toString()); }); @@ -57,44 +109,24 @@ describe("generatePostButtonTargetURL", () => { expect( generatePostButtonTargetURL({ - basePath: "/", buttonAction: "post", buttonIndex: 1, - currentURL: new URL("http://test.com"), + baseUrl: new URL("http://test.com"), target: { query: { test: "test" }, pathname: "/test" }, }) ).toBe(expected.toString()); }); - it.each(["/", "/test", "/test/test"])( - "resolves target relatively to basePath and current path %s", - (currentPath) => { - const expected = new URL("/prefixed/test/my-target", "http://test.com"); - expected.searchParams.set("__bi", "1-p"); - - expect( - generatePostButtonTargetURL({ - target: "/my-target", - basePath: "/prefixed/test", - buttonAction: "post", - buttonIndex: 1, - currentURL: new URL(currentPath, "http://test.com/"), - }) - ).toBe(expected.toString()); - } - ); - it("also supports post_redirect button", () => { const expected = new URL("/test", "http://test.com"); expected.searchParams.set("__bi", "1-pr"); expect( generatePostButtonTargetURL({ - target: undefined, - basePath: "/", + target: "/test", buttonAction: "post_redirect", buttonIndex: 1, - currentURL: new URL("http://test.com/test"), + baseUrl: new URL("http://test.com"), }) ).toBe(expected.toString()); }); diff --git a/packages/frames.js/src/core/utils.ts b/packages/frames.js/src/core/utils.ts index 176d7bbc..70a460e5 100644 --- a/packages/frames.js/src/core/utils.ts +++ b/packages/frames.js/src/core/utils.ts @@ -9,6 +9,28 @@ const buttonActionToCode = { const BUTTON_INFORMATION_SEARCH_PARAM_NAME = "__bi"; +export function joinPaths(pathA: string, pathB: string): string { + return pathB === "/" + ? pathA + : [pathA, pathB].join("/").replace(/\/{2,}/g, "/"); +} + +export function resolveBaseUrl( + request: Request, + baseUrl: URL | undefined, + basePath: string +): URL { + if (baseUrl) { + if (basePath === "/" || basePath === "") { + return baseUrl; + } + + return new URL(joinPaths(baseUrl.pathname, basePath), baseUrl); + } + + return new URL(basePath, request.url); +} + function isValidButtonIndex(index: unknown): index is 1 | 2 | 3 | 4 { return ( typeof index === "number" && @@ -28,57 +50,45 @@ function isValidButtonAction( } export function generateTargetURL({ - currentURL, + baseUrl, target, - basePath, }: { - currentURL: URL; - basePath: string; + baseUrl: URL; target: string | UrlObject | undefined; -}): string { - let url = new URL(currentURL); - - if ( - target && - typeof target === "string" && - (target.startsWith("http://") || target.startsWith("https://")) - ) { - // handle absolute urls - url = new URL(target); - } else if (target && typeof target === "string") { - // resolve target relatively to basePath - const baseUrl = new URL(basePath, currentURL); - const preformatted = `${baseUrl.pathname}/${target}`; - const parts = preformatted.split("/").filter(Boolean); - const finalPathname = parts.join("/"); - - url = new URL(`/${finalPathname}`, currentURL); - } else if (target && typeof target === "object") { - // resolve target relatively to basePath +}): URL { + if (!target) { + return new URL(baseUrl); + } - url = new URL( + if (typeof target === "object") { + return new URL( formatUrl({ - host: url.host, - hash: url.hash, - hostname: url.hostname, - href: url.href, + host: baseUrl.host, + hash: baseUrl.hash, + hostname: baseUrl.hostname, + href: baseUrl.href, // pathname: url.pathname, - protocol: url.protocol, + protocol: baseUrl.protocol, // we ignore existing search params and uses only new ones // search: url.search, - port: url.port, + port: baseUrl.port, // query: url.searchParams, ...target, - pathname: `/${[basePath, "/", target.pathname ?? ""] - .join("") - .split("/") - .filter(Boolean) - .join("/")}`, + pathname: joinPaths(baseUrl.pathname, target.pathname ?? ""), }) ); } - return url.toString(); + try { + // check if target is absolute url + return new URL(target); + } catch { + // resolve target relatively to basePath + const url = new URL(baseUrl); + const finalPathname = joinPaths(url.pathname, target); + + return new URL(finalPathname, url); + } } /** @@ -88,17 +98,15 @@ export function generateTargetURL({ export function generatePostButtonTargetURL({ buttonIndex, buttonAction, - currentURL, target, - basePath, + baseUrl, }: { buttonIndex: 1 | 2 | 3 | 4; buttonAction: "post" | "post_redirect"; - currentURL: URL; - basePath: string; target: string | UrlObject | undefined; + baseUrl: URL; }): string { - const url = new URL(generateTargetURL({ currentURL, basePath, target })); + const url = new URL(generateTargetURL({ baseUrl, target })); // Internal param, store what button has been clicked in the URL. url.searchParams.set( diff --git a/packages/frames.js/src/middleware/__snapshots__/renderResponse.test.tsx.snap b/packages/frames.js/src/middleware/__snapshots__/renderResponse.test.tsx.snap index 1338b437..06a96e58 100644 --- a/packages/frames.js/src/middleware/__snapshots__/renderResponse.test.tsx.snap +++ b/packages/frames.js/src/middleware/__snapshots__/renderResponse.test.tsx.snap @@ -82,6 +82,6 @@ exports[`renderResponse middleware correctly renders image wich conditional cont } `; -exports[`renderResponse middleware properly resolves against basePath 1`] = `""`; +exports[`renderResponse middleware properly resolves against baseUrl 1`] = `""`; exports[`renderResponse middleware renders text input 1`] = `""`; diff --git a/packages/frames.js/src/middleware/framesjsMiddleware.test.ts b/packages/frames.js/src/middleware/framesjsMiddleware.test.ts index 1943eb16..2f024f2a 100644 --- a/packages/frames.js/src/middleware/framesjsMiddleware.test.ts +++ b/packages/frames.js/src/middleware/framesjsMiddleware.test.ts @@ -1,14 +1,16 @@ /* eslint-disable no-console -- we are expecting console.log usage */ import { redirect } from "../core/redirect"; import type { FramesContext } from "../core/types"; -import { generatePostButtonTargetURL } from "../core/utils"; +import { generatePostButtonTargetURL, resolveBaseUrl } from "../core/utils"; import { framesjsMiddleware } from "./framesjsMiddleware"; describe("framesjsMiddleware middleware", () => { it("does not provide pressedButton to context if no supported button is detected", async () => { + const request = new Request("https://example.com", { method: "POST" }); const context = { url: new URL("https://example.com"), - request: new Request("https://example.com", { method: "POST" }), + request, + baseUrl: resolveBaseUrl(request, undefined, "/"), } as unknown as FramesContext; const next = jest.fn(); const middleware = framesjsMiddleware(); @@ -25,8 +27,7 @@ describe("framesjsMiddleware middleware", () => { const url = generatePostButtonTargetURL({ buttonAction: "post", buttonIndex: 1, - basePath: "/", - currentURL: new URL("https://example.com"), + baseUrl: new URL("https://example.com"), target: { pathname: "/test", query: { test: true }, @@ -54,8 +55,7 @@ describe("framesjsMiddleware middleware", () => { const url = generatePostButtonTargetURL({ buttonAction: "post_redirect", buttonIndex: 1, - basePath: "/", - currentURL: new URL("https://example.com"), + baseUrl: new URL("https://example.com"), target: "/test", }); const context = { @@ -83,8 +83,7 @@ describe("framesjsMiddleware middleware", () => { const url = generatePostButtonTargetURL({ buttonAction: "post_redirect", buttonIndex: 1, - basePath: "/", - currentURL: new URL("https://example.com"), + baseUrl: new URL("https://example.com"), target: "/test", }); const context = { @@ -108,8 +107,7 @@ describe("framesjsMiddleware middleware", () => { const url = generatePostButtonTargetURL({ buttonAction: "post", buttonIndex: 1, - basePath: "/", - currentURL: new URL("https://example.com"), + baseUrl: new URL("https://example.com"), target: { pathname: "/test", query: { test: true }, @@ -134,8 +132,7 @@ describe("framesjsMiddleware middleware", () => { const url = generatePostButtonTargetURL({ buttonAction: "post", buttonIndex: 1, - basePath: "/", - currentURL: new URL("https://example.com"), + baseUrl: new URL("https://example.com"), target: { pathname: "/test", query: { test: true }, @@ -161,8 +158,7 @@ describe("framesjsMiddleware middleware", () => { const url = generatePostButtonTargetURL({ buttonAction: "post", buttonIndex: 1, - basePath: "/", - currentURL: new URL("https://example.com"), + baseUrl: new URL("https://example.com"), target: { pathname: "/test", query: { test: true }, diff --git a/packages/frames.js/src/middleware/openframes.test.ts b/packages/frames.js/src/middleware/openframes.test.ts index 1a2beeaa..a3a98fa4 100644 --- a/packages/frames.js/src/middleware/openframes.test.ts +++ b/packages/frames.js/src/middleware/openframes.test.ts @@ -114,6 +114,7 @@ describe("openframes middleware", () => { }), url: new URL("https://example.com").toString(), basePath: "/", + baseUrl: new URL("https://example.com"), } as unknown as FramesContext; const mw1 = openframes({ diff --git a/packages/frames.js/src/middleware/renderResponse.test.tsx b/packages/frames.js/src/middleware/renderResponse.test.tsx index 04f024c6..53ecb11a 100644 --- a/packages/frames.js/src/middleware/renderResponse.test.tsx +++ b/packages/frames.js/src/middleware/renderResponse.test.tsx @@ -7,6 +7,7 @@ import { Button } from "../core/components"; import { error } from "../core/error"; import { redirect } from "../core/redirect"; import type { FramesContext } from "../core/types"; +import { resolveBaseUrl } from "../core/utils"; import { renderResponse } from "./renderResponse"; jest.mock("@vercel/og", () => { @@ -39,18 +40,22 @@ describe("renderResponse middleware", () => { vercelOg as unknown as { constructorMock: jest.Mock } ).constructorMock; const render = renderResponse(); + const request =new Request("https://example.com"); const context: FramesContext = { basePath: "/", initialState: undefined, - request: new Request("https://example.com"), + request, url: new URL("https://example.com"), + baseUrl: resolveBaseUrl(request, undefined, '/'), }; beforeEach(() => { arrayBufferMock.mockClear(); constructorMock.mockClear(); + context.basePath = "/"; context.request = new Request("https://example.com"); context.url = new URL("https://example.com"); + context.baseUrl = resolveBaseUrl(context.request, undefined, "/"); }); it("returns redirect Response if redirect is returned from handler", async () => { @@ -158,8 +163,8 @@ describe("renderResponse middleware", () => { await expect((result as Response).text()).resolves.toMatchSnapshot(); }); - it("properly resolves against basePath", async () => { - const newContext = { ...context, basePath: "/prefixed" }; + it("properly resolves against baseUrl", async () => { + const newContext = { ...context, baseUrl: new URL("https://example.com/prefixed") }; const result = await render(newContext, async () => { return { image:
My image
, @@ -501,7 +506,7 @@ describe("renderResponse middleware", () => { ); }); - it("properly renders button with targer object containing query", async () => { + it("properly renders button with target object containing query", async () => { const url = new URL("https://example.com"); url.searchParams.set("some_existing_param", "1"); @@ -512,7 +517,7 @@ describe("renderResponse middleware", () => { }, }); context.url = url; - context.basePath = "/test"; + context.baseUrl = new URL("https://example.com/test"); const result = await render(context, async () => { return { image:
My image
, diff --git a/packages/frames.js/src/middleware/renderResponse.ts b/packages/frames.js/src/middleware/renderResponse.ts index 4af36fcc..27e405c9 100644 --- a/packages/frames.js/src/middleware/renderResponse.ts +++ b/packages/frames.js/src/middleware/renderResponse.ts @@ -126,9 +126,8 @@ export function renderResponse(): FramesMiddleware> { typeof result.image === "string" ? generateTargetURL({ target: result.image, - currentURL: context.url, - basePath: context.basePath, - }) + baseUrl: context.baseUrl, + }).toString() : await renderImage(result.image, result.imageOptions).catch( (e) => { // eslint-disable-next-line no-console -- provide feedback to the user @@ -160,9 +159,8 @@ export function renderResponse(): FramesMiddleware> { label: props.children, target: generateTargetURL({ target: props.target, - currentURL: context.url, - basePath: context.basePath, - }), + baseUrl: context.baseUrl, + }).toString(), }; case "mint": return { @@ -178,16 +176,14 @@ export function renderResponse(): FramesMiddleware> { buttonIndex: (i + 1) as 1 | 2 | 3 | 4, buttonAction: "post", target: props.target, - currentURL: context.url, - basePath: context.basePath, - }), + baseUrl: context.baseUrl, + }).toString(), post_url: props.post_url ? generatePostButtonTargetURL({ buttonIndex: (i + 1) as 1 | 2 | 3 | 4, buttonAction: "post", target: props.post_url, - currentURL: context.url, - basePath: context.basePath, + baseUrl: context.baseUrl, }) : undefined, }; @@ -200,8 +196,7 @@ export function renderResponse(): FramesMiddleware> { buttonIndex: (i + 1) as 1 | 2 | 3 | 4, buttonAction: props.action, target: props.target, - currentURL: context.url, - basePath: context.basePath, + baseUrl: context.baseUrl, }), }; default: