Skip to content
Merged
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/upset-apples-judge.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions src/apps/components/AppWidgets/AppWidgets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -83,7 +83,7 @@ const IframePost = ({
return (
<Box>
<form ref={formRef} action={extensionUrl} method="POST" target={`ext-frame-${extensionId}`}>
<input type="hidden" name="saleorApiUrl" value={getApiUrl()} />
<input type="hidden" name="saleorApiUrl" value={getAbsoluteApiUrl()} />
<input type="hidden" name="accessToken" value={accessToken} />
<input type="hidden" name="appId" value={appId} />
<>
Expand Down
2 changes: 1 addition & 1 deletion src/apps/urls.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> & { theme: ThemeType }, string]>([
Expand Down
4 changes: 2 additions & 2 deletions src/apps/urls.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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
*/
Expand Down
60 changes: 60 additions & 0 deletions src/config.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
7 changes: 7 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
4 changes: 2 additions & 2 deletions src/extensions/new-tab-actions.ts
Original file line number Diff line number Diff line change
@@ -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 => {
Expand Down Expand Up @@ -57,7 +57,7 @@ export const newTabActions = {
...args.appParams,
accessToken: args.accessToken,
appId: args.appId,
saleorApiUrl: getApiUrl(),
saleorApiUrl: getAbsoluteApiUrl(),
};

const form = document.createElement("form");
Expand Down
4 changes: 2 additions & 2 deletions src/extensions/urls.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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
*/
Expand Down
Loading