diff --git a/.changeset/upset-apples-judge.md b/.changeset/upset-apples-judge.md new file mode 100644 index 00000000000..a510781ddf6 --- /dev/null +++ b/.changeset/upset-apples-judge.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +Fixed resolving Saleor absolute API URL. It was broken for some setups when extensions received a partial (incomplete) URL. diff --git a/src/apps/components/AppWidgets/AppWidgets.tsx b/src/apps/components/AppWidgets/AppWidgets.tsx index d43a7212fb3..36b8d3e16a3 100644 --- a/src/apps/components/AppWidgets/AppWidgets.tsx +++ b/src/apps/components/AppWidgets/AppWidgets.tsx @@ -4,7 +4,7 @@ import { isUrlAbsolute } from "@dashboard/apps/isUrlAbsolute"; import { AppDetailsUrlMountQueryParams, AppUrls } from "@dashboard/apps/urls"; import { DashboardCard } from "@dashboard/components/Card"; import Link from "@dashboard/components/Link"; -import { APP_VERSION, getApiUrl } from "@dashboard/config"; +import { APP_VERSION, getAbsoluteApiUrl } from "@dashboard/config"; import { extensionActions } from "@dashboard/extensions/messages"; import { ExtensionWithParams } from "@dashboard/extensions/types"; import { AppExtensionTargetEnum } from "@dashboard/graphql"; @@ -83,7 +83,7 @@ const IframePost = ({ return (
- + <> diff --git a/src/apps/urls.test.ts b/src/apps/urls.test.ts index 088874bf3c8..f3cc2f2312e 100644 --- a/src/apps/urls.test.ts +++ b/src/apps/urls.test.ts @@ -28,7 +28,7 @@ describe("AppUrls (apps/urls.ts)", () => { describe("For full URL provided in env", () => { beforeEach(() => { jest - .spyOn(config, "getApiUrl") + .spyOn(config, "getAbsoluteApiUrl") .mockImplementation(() => "https://shop.saleor.cloud/graphql/"); }); it.each<[string, string, Record & { theme: ThemeType }, string]>([ diff --git a/src/apps/urls.ts b/src/apps/urls.ts index c0d12bdca8b..b0504adc904 100644 --- a/src/apps/urls.ts +++ b/src/apps/urls.ts @@ -1,4 +1,4 @@ -import { getApiUrl } from "@dashboard/config"; +import { getAbsoluteApiUrl } from "@dashboard/config"; import { FlagList } from "@dashboard/featureFlags"; import { stringifyQs } from "@dashboard/utils/urls"; import { ThemeType } from "@saleor/app-sdk/app-bridge"; @@ -77,7 +77,7 @@ export const AppUrls = { appUrl: string, params: AppDetailsUrlQueryParams & AppDetailsCommonParams, ) => { - const apiUrl = new URL(getApiUrl(), window.location.origin).href; + const apiUrl = getAbsoluteApiUrl(); /** * Use host to preserve port, in case of multiple Saleors running on localhost */ diff --git a/src/config.test.ts b/src/config.test.ts new file mode 100644 index 00000000000..43d262c0f91 --- /dev/null +++ b/src/config.test.ts @@ -0,0 +1,60 @@ +import { getAbsoluteApiUrl, getApiUrl } from "@dashboard/config"; + +describe("global config", () => { + const { location } = window; + + beforeEach((): void => { + jest.clearAllMocks(); + + delete (window as { location?: unknown }).location; + + const testingUrl = new URL("https://foo.saleor.cloud/dashboard/product/asdf?aaaa=bbbb"); + + // Mock window.location for testing purposes + Object.defineProperty(window, "location", { + // URL matches fields from location so we can just put it there + value: testingUrl, + writable: true, + configurable: true, + }); + }); + afterAll((): void => { + Object.defineProperty(window, "location", { + value: location, + writable: true, + configurable: true, + }); + }); + + describe("getApiUrl", () => { + it.each(["/graphql/", "https://foo.saleor.cloud/graphql/"])( + "Returns value assigned to global window: %s", + param => { + window.__SALEOR_CONFIG__.API_URL = param; + + expect(getApiUrl()).toEqual(param); + }, + ); + }); + + describe("getAbsoluteApiUrl", () => { + it.each<{ envParam: string; expected: string }>([ + { + envParam: "/graphql/", + expected: "https://foo.saleor.cloud/graphql/", + }, + { + envParam: "https://foo.saleor.cloud/graphql/", + expected: "https://foo.saleor.cloud/graphql/", + }, + { + envParam: "https://other.saleor.cloud/graphql/", + expected: "https://other.saleor.cloud/graphql/", + }, + ])("Correctly builds absolute url: %s", ({ envParam, expected }) => { + window.__SALEOR_CONFIG__.API_URL = envParam; + + expect(getAbsoluteApiUrl()).toEqual(expected); + }); + }); +}); diff --git a/src/config.ts b/src/config.ts index bff60338291..375da721dcd 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,7 +4,14 @@ import { ListSettings, ListViews, Pagination } from "./types"; export const getAppDefaultUri = () => "/"; export const getAppMountUri = () => window?.__SALEOR_CONFIG__?.APP_MOUNT_URI || getAppDefaultUri(); +// May be a relative path (e.g., '/graphql/'); use getAbsoluteApiUrl() when a fully qualified URL is required. export const getApiUrl = () => window.__SALEOR_CONFIG__.API_URL; +/** + * Resolves full API URL. + * If the config provides an absolute URL, it will be used directly. + * If the config is relative (e.g., /graphql/), it will be resolved against the Dashboard origin. + */ +export const getAbsoluteApiUrl = () => new URL(getApiUrl(), window.location.origin).href; export const SW_INTERVAL = parseInt(process.env.SW_INTERVAL ?? "300", 10); export const IS_CLOUD_INSTANCE = window.__SALEOR_CONFIG__.IS_CLOUD_INSTANCE === "true"; diff --git a/src/extensions/new-tab-actions.ts b/src/extensions/new-tab-actions.ts index 072dc5ca7f4..220cc88f2b7 100644 --- a/src/extensions/new-tab-actions.ts +++ b/src/extensions/new-tab-actions.ts @@ -1,4 +1,4 @@ -import { getApiUrl } from "@dashboard/config"; +import { getAbsoluteApiUrl } from "@dashboard/config"; import { AppDetailsUrlMountQueryParams } from "@dashboard/extensions/urls"; const createInputElement = (name: string, value: string): HTMLInputElement => { @@ -57,7 +57,7 @@ export const newTabActions = { ...args.appParams, accessToken: args.accessToken, appId: args.appId, - saleorApiUrl: getApiUrl(), + saleorApiUrl: getAbsoluteApiUrl(), }; const form = document.createElement("form"); diff --git a/src/extensions/urls.ts b/src/extensions/urls.ts index 65272e38de3..d8ad7ef2c1a 100644 --- a/src/extensions/urls.ts +++ b/src/extensions/urls.ts @@ -1,5 +1,5 @@ import { AppPaths } from "@dashboard/apps/urls"; -import { getApiUrl } from "@dashboard/config"; +import { getAbsoluteApiUrl } from "@dashboard/config"; import { FlagList } from "@dashboard/featureFlags"; import { Dialog, SingleAction } from "@dashboard/types"; import { stringifyQs } from "@dashboard/utils/urls"; @@ -155,7 +155,7 @@ export const ExtensionsUrls = { appUrl: string, params: AppDetailsUrlQueryParams & AppDetailsCommonParams, ) => { - const apiUrl = new URL(getApiUrl(), window.location.origin).href; + const apiUrl = getAbsoluteApiUrl(); /** * Use host to preserve port, in case of multiple Saleors running on localhost */