Add advanced options to servers

This commit is contained in:
Alejandro Celaya 2025-04-20 11:12:43 +02:00
parent 4947e0490a
commit e997d11c2c
6 changed files with 52 additions and 22 deletions

14
package-lock.json generated
View File

@ -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"
}

View File

@ -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",

View File

@ -4,7 +4,7 @@ import type { GetState } from '../../container/types';
import type { ServerWithId } from '../../servers/data';
import { hasServerData } from '../../servers/data';
const apiClients: Record<string, ShlinkApiClient> = {};
const apiClients: Map<string, ShlinkApiClient> = 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;
};

View File

@ -4,6 +4,7 @@ export interface ServerData {
name: string;
url: string;
apiKey: string;
forwardCredentials?: boolean;
}
export interface ServerWithId extends ServerData {

View File

@ -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<ServerFormProps> = ({ 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 (
<form name="serverForm" onSubmit={handleSubmit}>
@ -31,6 +39,19 @@ export const ServerForm: FC<ServerFormProps> = ({ onSubmit, initialValues, child
onChange={(e) => setApiKey(e.target.value)}
required
/>
<Details summary="Advanced options">
<div className="tw:flex tw:flex-col tw:gap-1">
<Label className="tw:flex tw:items-center tw:gap-x-1.5 tw:cursor-pointer">
<Checkbox onChange={toggleForwardCredentials} checked={forwardCredentials} />
Forward credentials (like cookies) to this server on every request.
</Label>
<small className="tw:pl-5.5 tw:text-gray-600 tw:dark:text-gray-400">
<b>Important!</b> 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 <code className="tw:whitespace-nowrap">Access-Control-Allow-Origin</code> than <code>*</code>.
</small>
</div>
</Details>
</SimpleCard>
<div className="tw:flex tw:items-center tw:justify-end tw:gap-x-2">{children}</div>

View File

@ -1,6 +1,11 @@
import type { SyntheticEvent } from 'react';
import { useCallback } from 'react';
export const handleEventPreventingDefault = <T>(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 = <Event extends SyntheticEvent = SyntheticEvent>(handler: (e: Event) => void) =>
useCallback((e: Event) => {
e.preventDefault();
handler(e);
}, [handler]);