Replace Axios with the Fetch API (#3899)
This commit is contained in:
parent
4b3eb4a9e4
commit
7eb77cc0a7
12 changed files with 159 additions and 135 deletions
|
@ -1,6 +1,5 @@
|
||||||
|
import { NetworkError } from "@keycloak/keycloak-admin-client";
|
||||||
import { AlertVariant } from "@patternfly/react-core";
|
import { AlertVariant } from "@patternfly/react-core";
|
||||||
import type { AxiosError } from "axios";
|
|
||||||
import axios from "axios";
|
|
||||||
import { FunctionComponent, useCallback, useMemo, useState } from "react";
|
import { FunctionComponent, useCallback, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
@ -85,8 +84,8 @@ function getErrorMessage(error: unknown) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (axios.isAxiosError(error)) {
|
if (error instanceof NetworkError) {
|
||||||
return getErrorMessageAxios(error);
|
return getNetworkErrorMessage(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
|
@ -96,8 +95,8 @@ function getErrorMessage(error: unknown) {
|
||||||
throw new Error("Unable to determine error message.");
|
throw new Error("Unable to determine error message.");
|
||||||
}
|
}
|
||||||
|
|
||||||
function getErrorMessageAxios(error: AxiosError) {
|
function getNetworkErrorMessage({ responseData }: NetworkError) {
|
||||||
const data = (error.response?.data ?? {}) as Record<string, unknown>;
|
const data = responseData as Record<string, unknown>;
|
||||||
|
|
||||||
for (const key of ["error_description", "errorMessage", "error"]) {
|
for (const key of ["error_description", "errorMessage", "error"]) {
|
||||||
const value = data[key];
|
const value = data[key];
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { NetworkError } from "@keycloak/keycloak-admin-client";
|
||||||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||||
import { sortBy } from "lodash-es";
|
import { sortBy } from "lodash-es";
|
||||||
import {
|
import {
|
||||||
|
@ -7,7 +8,6 @@ import {
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
import { RecentUsed } from "../components/realm-selector/recent-used";
|
import { RecentUsed } from "../components/realm-selector/recent-used";
|
||||||
import { createNamedContext } from "../utils/createNamedContext";
|
import { createNamedContext } from "../utils/createNamedContext";
|
||||||
|
@ -46,11 +46,7 @@ export const RealmsProvider: FunctionComponent = ({ children }) => {
|
||||||
try {
|
try {
|
||||||
return await adminClient.realms.find({ briefRepresentation: true });
|
return await adminClient.realms.find({ briefRepresentation: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (
|
if (error instanceof NetworkError && error.response.status < 500) {
|
||||||
axios.isAxiosError(error) &&
|
|
||||||
error.response &&
|
|
||||||
error.response.status < 500
|
|
||||||
) {
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import KeycloakAdminClient from "@keycloak/keycloak-admin-client";
|
import KeycloakAdminClient from "@keycloak/keycloak-admin-client";
|
||||||
import axios from "axios";
|
|
||||||
import Keycloak from "keycloak-js";
|
import Keycloak from "keycloak-js";
|
||||||
import { DependencyList, useEffect } from "react";
|
import { DependencyList, useEffect } from "react";
|
||||||
import { useErrorHandler } from "react-error-boundary";
|
import { useErrorHandler } from "react-error-boundary";
|
||||||
|
@ -39,35 +38,24 @@ export function useFetch<T>(
|
||||||
callback: (param: T) => void,
|
callback: (param: T) => void,
|
||||||
deps?: DependencyList
|
deps?: DependencyList
|
||||||
) {
|
) {
|
||||||
const { adminClient } = useAdminClient();
|
|
||||||
const onError = useErrorHandler();
|
const onError = useErrorHandler();
|
||||||
|
const controller = new AbortController();
|
||||||
|
const { signal } = controller;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const source = axios.CancelToken.source();
|
|
||||||
|
|
||||||
adminClient.setConfig({
|
|
||||||
requestConfig: { cancelToken: source.token },
|
|
||||||
});
|
|
||||||
|
|
||||||
adminClientCall()
|
adminClientCall()
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (!source.token.reason) {
|
if (!signal.aborted) {
|
||||||
callback(result);
|
callback(result);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (!axios.isCancel(error)) {
|
if (!signal.aborted) {
|
||||||
onError(error);
|
onError(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
adminClient.setConfig({
|
return () => controller.abort();
|
||||||
requestConfig: { cancelToken: undefined },
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
source.cancel();
|
|
||||||
};
|
|
||||||
}, deps);
|
}, deps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,8 @@ import KcAdminClient from '@keycloak/keycloak-admin-client';
|
||||||
// {
|
// {
|
||||||
// baseUrl: 'http://127.0.0.1:8080',
|
// baseUrl: 'http://127.0.0.1:8080',
|
||||||
// realmName: 'master',
|
// realmName: 'master',
|
||||||
// requestConfig: {
|
// requestOptions: {
|
||||||
// /* Axios request config options https://github.com/axios/axios#request-config */
|
// /* Fetch request options https://developer.mozilla.org/en-US/docs/Web/API/fetch#options */
|
||||||
// },
|
// },
|
||||||
// }
|
// }
|
||||||
const kcAdminClient = new KcAdminClient();
|
const kcAdminClient = new KcAdminClient();
|
||||||
|
|
|
@ -37,7 +37,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.27.2",
|
|
||||||
"camelize-ts": "^2.1.1",
|
"camelize-ts": "^2.1.1",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"url-join": "^5.0.0",
|
"url-join": "^5.0.0",
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import type { AxiosRequestConfig } from "axios";
|
|
||||||
import type { RequestArgs } from "./resources/agent.js";
|
import type { RequestArgs } from "./resources/agent.js";
|
||||||
import { AttackDetection } from "./resources/attackDetection.js";
|
import { AttackDetection } from "./resources/attackDetection.js";
|
||||||
import { AuthenticationManagement } from "./resources/authenticationManagement.js";
|
import { AuthenticationManagement } from "./resources/authenticationManagement.js";
|
||||||
|
@ -26,7 +25,7 @@ export interface TokenProvider {
|
||||||
export interface ConnectionConfig {
|
export interface ConnectionConfig {
|
||||||
baseUrl?: string;
|
baseUrl?: string;
|
||||||
realmName?: string;
|
realmName?: string;
|
||||||
requestConfig?: AxiosRequestConfig;
|
requestOptions?: RequestInit;
|
||||||
requestArgOptions?: Pick<RequestArgs, "catchNotFound">;
|
requestArgOptions?: Pick<RequestArgs, "catchNotFound">;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,14 +54,14 @@ export class KeycloakAdminClient {
|
||||||
public accessToken?: string;
|
public accessToken?: string;
|
||||||
public refreshToken?: string;
|
public refreshToken?: string;
|
||||||
|
|
||||||
private requestConfig?: AxiosRequestConfig;
|
private requestOptions?: RequestInit;
|
||||||
private globalRequestArgOptions?: Pick<RequestArgs, "catchNotFound">;
|
private globalRequestArgOptions?: Pick<RequestArgs, "catchNotFound">;
|
||||||
private tokenProvider?: TokenProvider;
|
private tokenProvider?: TokenProvider;
|
||||||
|
|
||||||
constructor(connectionConfig?: ConnectionConfig) {
|
constructor(connectionConfig?: ConnectionConfig) {
|
||||||
this.baseUrl = connectionConfig?.baseUrl || defaultBaseUrl;
|
this.baseUrl = connectionConfig?.baseUrl || defaultBaseUrl;
|
||||||
this.realmName = connectionConfig?.realmName || defaultRealm;
|
this.realmName = connectionConfig?.realmName || defaultRealm;
|
||||||
this.requestConfig = connectionConfig?.requestConfig;
|
this.requestOptions = connectionConfig?.requestOptions;
|
||||||
this.globalRequestArgOptions = connectionConfig?.requestArgOptions;
|
this.globalRequestArgOptions = connectionConfig?.requestArgOptions;
|
||||||
|
|
||||||
// Initialize resources
|
// Initialize resources
|
||||||
|
@ -89,7 +88,7 @@ export class KeycloakAdminClient {
|
||||||
baseUrl: this.baseUrl,
|
baseUrl: this.baseUrl,
|
||||||
realmName: this.realmName,
|
realmName: this.realmName,
|
||||||
credentials,
|
credentials,
|
||||||
requestConfig: this.requestConfig,
|
requestOptions: this.requestOptions,
|
||||||
});
|
});
|
||||||
this.accessToken = accessToken;
|
this.accessToken = accessToken;
|
||||||
this.refreshToken = refreshToken;
|
this.refreshToken = refreshToken;
|
||||||
|
@ -115,8 +114,8 @@ export class KeycloakAdminClient {
|
||||||
return this.accessToken;
|
return this.accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRequestConfig() {
|
public getRequestOptions() {
|
||||||
return this.requestConfig;
|
return this.requestOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getGlobalRequestArgOptions():
|
public getGlobalRequestArgOptions():
|
||||||
|
@ -139,6 +138,6 @@ export class KeycloakAdminClient {
|
||||||
) {
|
) {
|
||||||
this.realmName = connectionConfig.realmName;
|
this.realmName = connectionConfig.realmName;
|
||||||
}
|
}
|
||||||
this.requestConfig = connectionConfig.requestConfig;
|
this.requestOptions = connectionConfig.requestOptions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { RequiredActionAlias } from "./defs/requiredActionProviderRepresentation.js";
|
|
||||||
import { KeycloakAdminClient } from "./client.js";
|
import { KeycloakAdminClient } from "./client.js";
|
||||||
|
import { RequiredActionAlias } from "./defs/requiredActionProviderRepresentation.js";
|
||||||
|
|
||||||
export const requiredAction = RequiredActionAlias;
|
export const requiredAction = RequiredActionAlias;
|
||||||
export default KeycloakAdminClient;
|
export default KeycloakAdminClient;
|
||||||
|
export { NetworkError } from "./utils/fetchWithError.js";
|
||||||
|
export type { NetworkErrorOptions } from "./utils/fetchWithError.js";
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
import axios, { AxiosRequestConfig, AxiosRequestHeaders, Method } from "axios";
|
|
||||||
import { isUndefined, last, omit, pick } from "lodash-es";
|
import { isUndefined, last, omit, pick } from "lodash-es";
|
||||||
import urlJoin from "url-join";
|
import urlJoin from "url-join";
|
||||||
import { parseTemplate } from "url-template";
|
import { parseTemplate } from "url-template";
|
||||||
import type { KeycloakAdminClient } from "../client.js";
|
import type { KeycloakAdminClient } from "../client.js";
|
||||||
|
import {
|
||||||
|
fetchWithError,
|
||||||
|
NetworkError,
|
||||||
|
parseResponse,
|
||||||
|
} from "../utils/fetchWithError.js";
|
||||||
import { stringifyQueryParams } from "../utils/stringifyQueryParams.js";
|
import { stringifyQueryParams } from "../utils/stringifyQueryParams.js";
|
||||||
|
|
||||||
// constants
|
// constants
|
||||||
const SLASH = "/";
|
const SLASH = "/";
|
||||||
|
|
||||||
|
type Method = "GET" | "POST" | "PUT" | "DELETE";
|
||||||
|
|
||||||
// interface
|
// interface
|
||||||
export interface RequestArgs {
|
export interface RequestArgs {
|
||||||
method: Method;
|
method: Method;
|
||||||
|
@ -31,7 +37,7 @@ export interface RequestArgs {
|
||||||
* Keys to be ignored, meaning that they will not be filtered out of the request payload even if they are a part of `urlParamKeys` or `queryParamKeys`,
|
* Keys to be ignored, meaning that they will not be filtered out of the request payload even if they are a part of `urlParamKeys` or `queryParamKeys`,
|
||||||
*/
|
*/
|
||||||
ignoredKeys?: string[];
|
ignoredKeys?: string[];
|
||||||
headers?: AxiosRequestHeaders;
|
headers?: HeadersInit;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Agent {
|
export class Agent {
|
||||||
|
@ -76,7 +82,9 @@ export class Agent {
|
||||||
const baseParams = this.getBaseParams?.() ?? {};
|
const baseParams = this.getBaseParams?.() ?? {};
|
||||||
|
|
||||||
// Filter query parameters by queryParamKeys
|
// Filter query parameters by queryParamKeys
|
||||||
const queryParams = queryParamKeys ? pick(payload, queryParamKeys) : null;
|
const queryParams = queryParamKeys
|
||||||
|
? pick(payload, queryParamKeys)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
// Add filtered payload parameters to base parameters
|
// Add filtered payload parameters to base parameters
|
||||||
const allUrlParamKeys = [...Object.keys(baseParams), ...urlParamKeys];
|
const allUrlParamKeys = [...Object.keys(baseParams), ...urlParamKeys];
|
||||||
|
@ -128,7 +136,9 @@ export class Agent {
|
||||||
const baseParams = this.getBaseParams?.() ?? {};
|
const baseParams = this.getBaseParams?.() ?? {};
|
||||||
|
|
||||||
// Filter query parameters by queryParamKeys
|
// Filter query parameters by queryParamKeys
|
||||||
const queryParams = queryParamKeys ? pick(query, queryParamKeys) : null;
|
const queryParams = queryParamKeys
|
||||||
|
? pick(query, queryParamKeys)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
// Add filtered query parameters to base parameters
|
// Add filtered query parameters to base parameters
|
||||||
const allUrlParamKeys = [...Object.keys(baseParams), ...urlParamKeys];
|
const allUrlParamKeys = [...Object.keys(baseParams), ...urlParamKeys];
|
||||||
|
@ -171,64 +181,72 @@ export class Agent {
|
||||||
path: string;
|
path: string;
|
||||||
payload: any;
|
payload: any;
|
||||||
urlParams: any;
|
urlParams: any;
|
||||||
queryParams?: Record<string, any> | null;
|
queryParams?: Record<string, string>;
|
||||||
catchNotFound: boolean;
|
catchNotFound: boolean;
|
||||||
payloadKey?: string;
|
payloadKey?: string;
|
||||||
returnResourceIdInLocationHeader?: { field: string };
|
returnResourceIdInLocationHeader?: { field: string };
|
||||||
headers?: AxiosRequestHeaders;
|
headers?: HeadersInit;
|
||||||
}) {
|
}) {
|
||||||
const newPath = urlJoin(this.basePath, path);
|
const newPath = urlJoin(this.basePath, path);
|
||||||
|
|
||||||
// Parse template and replace with values from urlParams
|
// Parse template and replace with values from urlParams
|
||||||
const pathTemplate = parseTemplate(newPath);
|
const pathTemplate = parseTemplate(newPath);
|
||||||
const parsedPath = pathTemplate.expand(urlParams);
|
const parsedPath = pathTemplate.expand(urlParams);
|
||||||
const url = `${this.getBaseUrl?.() ?? ""}${parsedPath}`;
|
const url = new URL(`${this.getBaseUrl?.() ?? ""}${parsedPath}`);
|
||||||
|
const requestOptions = { ...this.client.getRequestOptions() };
|
||||||
|
const requestHeaders = new Headers([
|
||||||
|
...new Headers(requestOptions.headers).entries(),
|
||||||
|
["authorization", `Bearer ${await this.client.getAccessToken()}`],
|
||||||
|
["accept", "application/json, text/plain, */*"],
|
||||||
|
...new Headers(headers).entries(),
|
||||||
|
]);
|
||||||
|
|
||||||
// Prepare request config
|
const searchParams: Record<string, string> = {};
|
||||||
const requestConfig: AxiosRequestConfig = {
|
|
||||||
paramsSerializer: (params) => stringifyQueryParams(params),
|
|
||||||
...(this.client.getRequestConfig() || {}),
|
|
||||||
method,
|
|
||||||
url,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Headers
|
// Add payload parameters to search params if method is 'GET'.
|
||||||
requestConfig.headers = {
|
|
||||||
...requestConfig.headers,
|
|
||||||
Authorization: `bearer ${await this.client.getAccessToken()}`,
|
|
||||||
...headers,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Put payload into querystring if method is GET
|
|
||||||
if (method === "GET") {
|
if (method === "GET") {
|
||||||
requestConfig.params = payload;
|
Object.assign(searchParams, payload);
|
||||||
|
} else if (requestHeaders.get("content-type") === "text/plain") {
|
||||||
|
// Pass the payload as a plain string if the content type is 'text/plain'.
|
||||||
|
requestOptions.body = payload as unknown as string;
|
||||||
} else {
|
} else {
|
||||||
// Set the request data to the payload, or the value corresponding to the payloadKey, if it's defined
|
// Otherwise assume it's JSON and stringify it.
|
||||||
requestConfig.data = payloadKey ? payload[payloadKey] : payload;
|
requestOptions.body = JSON.stringify(
|
||||||
|
payloadKey ? payload[payloadKey] : payload
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Concat to existing queryParams
|
if (!requestHeaders.has("content-type")) {
|
||||||
if (queryParams) {
|
requestHeaders.set("content-type", "application/json");
|
||||||
requestConfig.params = requestConfig.params
|
|
||||||
? {
|
|
||||||
...requestConfig.params,
|
|
||||||
...queryParams,
|
|
||||||
}
|
}
|
||||||
: queryParams;
|
|
||||||
|
if (queryParams) {
|
||||||
|
Object.assign(searchParams, queryParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
url.search = stringifyQueryParams(searchParams);
|
||||||
|
|
||||||
|
if (!requestHeaders.has("content-type")) {
|
||||||
|
requestHeaders.set("content-type", "application/x-www-form-urlencoded");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axios.default(requestConfig);
|
const res = await fetchWithError(url, {
|
||||||
|
...requestOptions,
|
||||||
|
headers: requestHeaders,
|
||||||
|
method,
|
||||||
|
});
|
||||||
|
|
||||||
// now we get the response of the http request
|
// now we get the response of the http request
|
||||||
// if `resourceIdInLocationHeader` is true, we'll get the resourceId from the location header field
|
// if `resourceIdInLocationHeader` is true, we'll get the resourceId from the location header field
|
||||||
// todo: find a better way to find the id in path, maybe some kind of pattern matching
|
// todo: find a better way to find the id in path, maybe some kind of pattern matching
|
||||||
// for now, we simply split the last sub-path of the path returned in location header field
|
// for now, we simply split the last sub-path of the path returned in location header field
|
||||||
if (returnResourceIdInLocationHeader) {
|
if (returnResourceIdInLocationHeader) {
|
||||||
const locationHeader = res.headers.location;
|
const locationHeader = res.headers.get("location");
|
||||||
|
|
||||||
if (typeof locationHeader !== "string") {
|
if (typeof locationHeader !== "string") {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`location header is not found in request: ${res.config.url}`
|
`location header is not found in request: ${res.url}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,7 +254,7 @@ export class Agent {
|
||||||
if (!resourceId) {
|
if (!resourceId) {
|
||||||
// throw an error to let users know the response is not expected
|
// throw an error to let users know the response is not expected
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`resourceId is not found in Location header from request: ${res.config.url}`
|
`resourceId is not found in Location header from request: ${res.url}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,11 +262,12 @@ export class Agent {
|
||||||
const { field } = returnResourceIdInLocationHeader;
|
const { field } = returnResourceIdInLocationHeader;
|
||||||
return { [field]: resourceId };
|
return { [field]: resourceId };
|
||||||
}
|
}
|
||||||
return res.data;
|
|
||||||
|
return parseResponse(res);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (
|
if (
|
||||||
axios.default.isAxiosError(err) &&
|
err instanceof NetworkError &&
|
||||||
err.response?.status === 404 &&
|
err.response.status === 404 &&
|
||||||
catchNotFound
|
catchNotFound
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -223,7 +223,6 @@ export class Users extends Resource<{ realm?: string }> {
|
||||||
urlParamKeys: ["id"],
|
urlParamKeys: ["id"],
|
||||||
payloadKey: "actions",
|
payloadKey: "actions",
|
||||||
queryParamKeys: ["lifespan", "redirectUri", "clientId"],
|
queryParamKeys: ["lifespan", "redirectUri", "clientId"],
|
||||||
headers: { "content-type": "application/json" },
|
|
||||||
keyTransform: {
|
keyTransform: {
|
||||||
clientId: "client_id",
|
clientId: "client_id",
|
||||||
redirectUri: "redirect_uri",
|
redirectUri: "redirect_uri",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
|
|
||||||
import camelize from "camelize-ts";
|
import camelize from "camelize-ts";
|
||||||
import { defaultBaseUrl, defaultRealm } from "./constants.js";
|
import { defaultBaseUrl, defaultRealm } from "./constants.js";
|
||||||
|
import { fetchWithError } from "./fetchWithError.js";
|
||||||
import { stringifyQueryParams } from "./stringifyQueryParams.js";
|
import { stringifyQueryParams } from "./stringifyQueryParams.js";
|
||||||
|
|
||||||
export type GrantTypes = "client_credentials" | "password" | "refresh_token";
|
export type GrantTypes = "client_credentials" | "password" | "refresh_token";
|
||||||
|
@ -20,7 +20,7 @@ export interface Settings {
|
||||||
realmName?: string;
|
realmName?: string;
|
||||||
baseUrl?: string;
|
baseUrl?: string;
|
||||||
credentials: Credentials;
|
credentials: Credentials;
|
||||||
requestConfig?: AxiosRequestConfig;
|
requestOptions?: RequestInit;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TokenResponseRaw {
|
export interface TokenResponseRaw {
|
||||||
|
@ -69,20 +69,25 @@ export const getToken = async (settings: Settings): Promise<TokenResponse> => {
|
||||||
: {}),
|
: {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const config: AxiosRequestConfig = {
|
const options = settings.requestOptions ?? {};
|
||||||
...settings.requestConfig,
|
const headers = new Headers(options.headers);
|
||||||
};
|
|
||||||
|
|
||||||
if (credentials.clientSecret) {
|
if (credentials.clientSecret) {
|
||||||
config.auth = {
|
headers.set(
|
||||||
username: credentials.clientId,
|
"Authorization",
|
||||||
password: credentials.clientSecret,
|
atob(credentials.clientId + ":" + credentials.clientSecret)
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await axios.default.post<
|
headers.set("content-type", "application/x-www-form-urlencoded");
|
||||||
any,
|
|
||||||
AxiosResponse<TokenResponseRaw>
|
const response = await fetchWithError(url, {
|
||||||
>(url, payload, config);
|
...options,
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
body: payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data: TokenResponseRaw = await response.json();
|
||||||
return camelize(data);
|
return camelize(data);
|
||||||
};
|
};
|
||||||
|
|
44
libs/keycloak-admin-client/src/utils/fetchWithError.ts
Normal file
44
libs/keycloak-admin-client/src/utils/fetchWithError.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
export type NetworkErrorOptions = { response: Response; responseData: unknown };
|
||||||
|
|
||||||
|
export class NetworkError extends Error {
|
||||||
|
response: Response;
|
||||||
|
responseData: unknown;
|
||||||
|
|
||||||
|
constructor(message: string, options: NetworkErrorOptions) {
|
||||||
|
super(message);
|
||||||
|
this.response = options.response;
|
||||||
|
this.responseData = options.responseData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchWithError(
|
||||||
|
input: RequestInfo | URL,
|
||||||
|
init?: RequestInit
|
||||||
|
) {
|
||||||
|
const response = await fetch(input, init);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const responseData = await parseResponse(response);
|
||||||
|
throw new NetworkError("Network response was not OK.", {
|
||||||
|
response,
|
||||||
|
responseData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function parseResponse(response: Response): Promise<any> {
|
||||||
|
if (!response.body) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.text();
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(data);
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
} catch (error) {}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
56
package-lock.json
generated
56
package-lock.json
generated
|
@ -548,7 +548,6 @@
|
||||||
"version": "999.0.0-dev",
|
"version": "999.0.0-dev",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.27.2",
|
|
||||||
"camelize-ts": "^2.1.1",
|
"camelize-ts": "^2.1.1",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"url-join": "^5.0.0",
|
"url-join": "^5.0.0",
|
||||||
|
@ -5691,6 +5690,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/asynckit": {
|
"node_modules/asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/at-least-node": {
|
"node_modules/at-least-node": {
|
||||||
|
@ -5732,14 +5732,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
|
||||||
"version": "0.27.2",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"follow-redirects": "^1.14.9",
|
|
||||||
"form-data": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/babel-loader": {
|
"node_modules/babel-loader": {
|
||||||
"version": "8.2.5",
|
"version": "8.2.5",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
@ -6685,6 +6677,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/combined-stream": {
|
"node_modules/combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"delayed-stream": "~1.0.0"
|
"delayed-stream": "~1.0.0"
|
||||||
|
@ -7597,6 +7590,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/delayed-stream": {
|
"node_modules/delayed-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
|
@ -9065,24 +9059,6 @@
|
||||||
"tabbable": "^5.3.2"
|
"tabbable": "^5.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/follow-redirects": {
|
|
||||||
"version": "1.15.1",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "individual",
|
|
||||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"debug": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/for-in": {
|
"node_modules/for-in": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
@ -9101,6 +9077,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
|
@ -11233,6 +11210,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/mime-db": {
|
"node_modules/mime-db": {
|
||||||
"version": "1.52.0",
|
"version": "1.52.0",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
|
@ -11240,6 +11218,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/mime-types": {
|
"node_modules/mime-types": {
|
||||||
"version": "2.1.35",
|
"version": "2.1.35",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mime-db": "1.52.0"
|
"mime-db": "1.52.0"
|
||||||
|
@ -17819,7 +17798,6 @@
|
||||||
"@types/lodash-es": "^4.17.5",
|
"@types/lodash-es": "^4.17.5",
|
||||||
"@types/mocha": "^10.0.1",
|
"@types/mocha": "^10.0.1",
|
||||||
"@types/node": "^18.0.3",
|
"@types/node": "^18.0.3",
|
||||||
"axios": "^0.27.2",
|
|
||||||
"camelize-ts": "^2.1.1",
|
"camelize-ts": "^2.1.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
@ -19854,7 +19832,8 @@
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"asynckit": {
|
"asynckit": {
|
||||||
"version": "0.4.0"
|
"version": "0.4.0",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"at-least-node": {
|
"at-least-node": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
@ -19875,13 +19854,6 @@
|
||||||
"version": "1.11.0",
|
"version": "1.11.0",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"axios": {
|
|
||||||
"version": "0.27.2",
|
|
||||||
"requires": {
|
|
||||||
"follow-redirects": "^1.14.9",
|
|
||||||
"form-data": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"babel-loader": {
|
"babel-loader": {
|
||||||
"version": "8.2.5",
|
"version": "8.2.5",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
@ -20516,6 +20488,7 @@
|
||||||
},
|
},
|
||||||
"combined-stream": {
|
"combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"delayed-stream": "~1.0.0"
|
"delayed-stream": "~1.0.0"
|
||||||
}
|
}
|
||||||
|
@ -21123,7 +21096,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"delayed-stream": {
|
"delayed-stream": {
|
||||||
"version": "1.0.0"
|
"version": "1.0.0",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"deprecation": {
|
"deprecation": {
|
||||||
"version": "2.3.1"
|
"version": "2.3.1"
|
||||||
|
@ -22140,9 +22114,6 @@
|
||||||
"tabbable": "^5.3.2"
|
"tabbable": "^5.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"follow-redirects": {
|
|
||||||
"version": "1.15.1"
|
|
||||||
},
|
|
||||||
"for-in": {
|
"for-in": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"dev": true
|
"dev": true
|
||||||
|
@ -22153,6 +22124,7 @@
|
||||||
},
|
},
|
||||||
"form-data": {
|
"form-data": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.8",
|
||||||
|
@ -23570,10 +23542,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mime-db": {
|
"mime-db": {
|
||||||
"version": "1.52.0"
|
"version": "1.52.0",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"mime-types": {
|
"mime-types": {
|
||||||
"version": "2.1.35",
|
"version": "2.1.35",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"mime-db": "1.52.0"
|
"mime-db": "1.52.0"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue