diff --git a/package-lock.json b/package-lock.json index 90819167..06dddfde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@json2csv/plainjs": "^7.0.6", "@reduxjs/toolkit": "^2.7.0", "@shlinkio/data-manipulation": "^1.0.3", - "@shlinkio/shlink-frontend-kit": "^0.8.10", + "@shlinkio/shlink-frontend-kit": "^0.8.12", "@shlinkio/shlink-js-sdk": "^2.1.0", "@shlinkio/shlink-web-component": "^0.13.3", "bootstrap": "5.2.3", @@ -3438,9 +3438,9 @@ } }, "node_modules/@shlinkio/shlink-frontend-kit": { - "version": "0.8.10", - "resolved": "https://registry.npmjs.org/@shlinkio/shlink-frontend-kit/-/shlink-frontend-kit-0.8.10.tgz", - "integrity": "sha512-cB5qyZBCWEwLzEf3XK6ih/32x8i4ER4Tn6WNqIROhcr6Myjot0gvAfNStoXbEeYjJSw2+5wRFSccbAh3w5RxJA==", + "version": "0.8.12", + "resolved": "https://registry.npmjs.org/@shlinkio/shlink-frontend-kit/-/shlink-frontend-kit-0.8.12.tgz", + "integrity": "sha512-J3t0HnvOaZDLSZ1zjbAn9l025GNTy7XvcKEV5+t8iYirf6THyGCK7JDoY1CfgRWfjiWBCFA+WmzrK92a2PqcAA==", "license": "MIT", "dependencies": { "clsx": "^2.1.1" @@ -13699,9 +13699,9 @@ "requires": {} }, "@shlinkio/shlink-frontend-kit": { - "version": "0.8.10", - "resolved": "https://registry.npmjs.org/@shlinkio/shlink-frontend-kit/-/shlink-frontend-kit-0.8.10.tgz", - "integrity": "sha512-cB5qyZBCWEwLzEf3XK6ih/32x8i4ER4Tn6WNqIROhcr6Myjot0gvAfNStoXbEeYjJSw2+5wRFSccbAh3w5RxJA==", + "version": "0.8.12", + "resolved": "https://registry.npmjs.org/@shlinkio/shlink-frontend-kit/-/shlink-frontend-kit-0.8.12.tgz", + "integrity": "sha512-J3t0HnvOaZDLSZ1zjbAn9l025GNTy7XvcKEV5+t8iYirf6THyGCK7JDoY1CfgRWfjiWBCFA+WmzrK92a2PqcAA==", "requires": { "clsx": "^2.1.1" } diff --git a/package.json b/package.json index 40c85f15..ce439246 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@json2csv/plainjs": "^7.0.6", "@reduxjs/toolkit": "^2.7.0", "@shlinkio/data-manipulation": "^1.0.3", - "@shlinkio/shlink-frontend-kit": "^0.8.10", + "@shlinkio/shlink-frontend-kit": "^0.8.12", "@shlinkio/shlink-js-sdk": "^2.1.0", "@shlinkio/shlink-web-component": "^0.13.3", "bootstrap": "5.2.3", diff --git a/src/api/services/ShlinkApiClientBuilder.ts b/src/api/services/ShlinkApiClientBuilder.ts index cf468bd9..8e64c3ee 100644 --- a/src/api/services/ShlinkApiClientBuilder.ts +++ b/src/api/services/ShlinkApiClientBuilder.ts @@ -4,7 +4,7 @@ import type { GetState } from '../../container/types'; import type { ServerWithId } from '../../servers/data'; import { hasServerData } from '../../servers/data'; -const apiClients: Record = {}; +const apiClients: Map = new Map(); const isGetState = (getStateOrSelectedServer: GetState | ServerWithId): getStateOrSelectedServer is GetState => typeof getStateOrSelectedServer === 'function'; @@ -18,19 +18,22 @@ const getSelectedServerFromState = (getState: GetState): ServerWithId => { }; export const buildShlinkApiClient = (httpClient: HttpClient) => (getStateOrSelectedServer: GetState | ServerWithId) => { - const { url: baseUrl, apiKey } = isGetState(getStateOrSelectedServer) + const { url: baseUrl, apiKey, forwardCredentials } = isGetState(getStateOrSelectedServer) ? getSelectedServerFromState(getStateOrSelectedServer) : getStateOrSelectedServer; - const serverKey = `${apiKey}_${baseUrl}`; + const serverKey = `${apiKey}_${baseUrl}_${forwardCredentials ? 'forward' : 'no-forward'}`; + const existingApiClient = apiClients.get(serverKey); - const apiClient = apiClients[serverKey] ?? new ShlinkApiClient( + if (existingApiClient) { + return existingApiClient; + } + + const apiClient = new ShlinkApiClient( httpClient, { apiKey, baseUrl }, - // FIXME Disabling this as it's breaking existing Shlink servers as configured out of the box - // { requestCredentials: 'include' }, + { requestCredentials: forwardCredentials ? 'include' : undefined }, ); - apiClients[serverKey] = apiClient; - + apiClients.set(serverKey, apiClient); return apiClient; }; diff --git a/src/servers/data/index.ts b/src/servers/data/index.ts index cae33087..c6642f54 100644 --- a/src/servers/data/index.ts +++ b/src/servers/data/index.ts @@ -4,6 +4,7 @@ export interface ServerData { name: string; url: string; apiKey: string; + forwardCredentials?: boolean; } export interface ServerWithId extends ServerData { diff --git a/src/servers/helpers/ServerForm.tsx b/src/servers/helpers/ServerForm.tsx index 0229367e..1953c918 100644 --- a/src/servers/helpers/ServerForm.tsx +++ b/src/servers/helpers/ServerForm.tsx @@ -1,11 +1,15 @@ +import { useToggle } from '@shlinkio/shlink-frontend-kit'; import { + Checkbox, + Details, + Label, LabelledInput, LabelledRevealablePasswordInput, SimpleCard, } from '@shlinkio/shlink-frontend-kit/tailwind'; import type { FC, PropsWithChildren, ReactNode } from 'react'; import { useState } from 'react'; -import { handleEventPreventingDefault } from '../../utils/utils'; +import { usePreventDefault } from '../../utils/utils'; import type { ServerData } from '../data'; type ServerFormProps = PropsWithChildren<{ @@ -18,7 +22,11 @@ export const ServerForm: FC = ({ onSubmit, initialValues, child const [name, setName] = useState(initialValues?.name ?? ''); const [url, setUrl] = useState(initialValues?.url ?? ''); const [apiKey, setApiKey] = useState(initialValues?.apiKey ?? ''); - const handleSubmit = handleEventPreventingDefault(() => onSubmit({ name, url, apiKey })); + const { flag: forwardCredentials, toggle: toggleForwardCredentials } = useToggle( + initialValues?.forwardCredentials ?? false, + true, + ); + const handleSubmit = usePreventDefault(() => onSubmit({ name, url, apiKey, forwardCredentials })); return (
@@ -31,6 +39,19 @@ export const ServerForm: FC = ({ onSubmit, initialValues, child onChange={(e) => setApiKey(e.target.value)} required /> +
+
+ + + Important! If you are not sure what this means, leave it unchecked. Enabling this option will + make all requests fail for Shlink older than v4.5.0, as it requires the server to set a more strict + value for Access-Control-Allow-Origin than *. + +
+
{children}
diff --git a/src/utils/utils.ts b/src/utils/utils.ts index b1b11ca0..33a88306 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,6 +1,11 @@ import type { SyntheticEvent } from 'react'; +import { useCallback } from 'react'; -export const handleEventPreventingDefault = (handler: () => T) => (e: SyntheticEvent) => { - e.preventDefault(); - handler(); -}; +/** + * Wraps an event handler so that it calls e.preventDefault() before invoking the event handler + */ +export const usePreventDefault = (handler: (e: Event) => void) => + useCallback((e: Event) => { + e.preventDefault(); + handler(e); + }, [handler]);