@shopware/api-client
Version:
Shopware client for API connection.
356 lines (345 loc) • 11.3 kB
JavaScript
;
const defu = require('defu');
const hookable = require('hookable');
const ofetch = require('ofetch');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
const defu__default = /*#__PURE__*/_interopDefaultCompat(defu);
function createHeaders(init, hookCallback) {
const _headers = {
"Content-Type": "application/json"
};
const handler = {
get: (target, prop) => {
if (prop === "apply") {
return apply;
}
return Reflect.get(target, prop);
},
set: (target, prop, value) => {
if (prop === "apply") {
throw new Error("Cannot override apply method");
}
hookCallback?.(prop, value);
return Reflect.set(target, prop, value);
},
deleteProperty: (target, prop) => {
hookCallback?.(prop);
return Reflect.deleteProperty(target, prop);
}
};
const headersProxy = new Proxy(
_headers,
handler
);
function apply(headers) {
for (const [key, value] of Object.entries(headers)) {
if (value) {
headersProxy[key] = value;
} else {
delete headersProxy[key];
}
}
}
headersProxy.apply({ ...init });
return headersProxy;
}
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
class ApiClientError extends Error {
constructor(response) {
let message = "Failed request";
const errorDetails = response._data || {
errors: [
{
title: "Unknown error",
detail: "API did not return errors, but request failed. Please check the network tab."
}
]
};
message += errorDetails.errors?.reduce((message2, error) => {
let pointer = "";
if (error.source?.pointer) {
pointer = `[${error.source.pointer}]`;
}
const details = error.detail ?? "No error details provided.";
return `${message2}
- [${error.title}]${pointer} ${details}`;
}, "") ?? "";
super(message);
/**
* Flag to indicate if the request was successful.
*/
__publicField(this, "ok");
/**
* HTTP status code of the response.
*/
__publicField(this, "status");
/**
* HTTP status text of the response.
*/
__publicField(this, "statusText");
/**
* URL of the request.
*/
__publicField(this, "url");
/**
* Details of the error.
*/
__publicField(this, "details");
/**
* Headers of the response.
*/
__publicField(this, "headers");
this.name = "ApiClientError";
this.details = errorDetails;
this.ok = response.ok;
this.status = response.status;
this.statusText = response.statusText;
this.url = response.url;
this.headers = response.headers;
}
}
function errorInterceptor(response) {
throw new ApiClientError(response);
}
function createPathWithParams(requestPath, pathParams) {
return Object.keys(pathParams || {}).reduce((acc, paramName) => {
return acc.replace(`{${paramName}}`, pathParams[paramName]);
}, requestPath || "");
}
function createAPIClient(params) {
const apiClientHooks = hookable.createHooks();
const defaultHeaders = createHeaders(
{
"sw-access-key": params.accessToken,
accept: "application/json",
"sw-context-token": params.contextToken,
...params.defaultHeaders
},
(key, value) => {
apiClientHooks.callHook("onDefaultHeaderChanged", key, value);
if (key === "sw-context-token" && value) {
apiClientHooks.callHook("onContextChanged", value);
}
}
);
let currentBaseURL = params.baseURL;
let currentAccessToken = params.accessToken;
function createFetchClient(baseURL) {
return ofetch.ofetch.create({
baseURL,
...params.fetchOptions,
async onRequest(context) {
apiClientHooks.callHook("onRequest", context);
},
async onResponse(context) {
apiClientHooks.callHook("onSuccessResponse", context.response);
if (context.response.headers.has("sw-context-token") && defaultHeaders["sw-context-token"] !== context.response.headers.get("sw-context-token")) {
const newContextToken = context.response.headers.get(
"sw-context-token"
);
defaultHeaders["sw-context-token"] = newContextToken;
}
},
async onResponseError({ response }) {
apiClientHooks.callHook("onResponseError", response);
errorInterceptor(response);
}
});
}
let apiFetch = createFetchClient(currentBaseURL);
async function invoke(pathParam, ...params2) {
const [, method, requestPath] = pathParam.split(" ");
const currentParams = params2[0] || {};
const requestPathWithParams = createPathWithParams(
requestPath,
currentParams.pathParams
);
const fetchOptions = {
...currentParams.fetchOptions || {}
};
let mergedHeaders = defu__default(currentParams.headers, defaultHeaders);
if (mergedHeaders?.["Content-Type"]?.includes("multipart/form-data") && typeof window !== "undefined") {
const { "Content-Type": _, ...headersWithoutContentType } = mergedHeaders;
mergedHeaders = headersWithoutContentType;
}
const resp = await apiFetch.raw(requestPathWithParams, {
...fetchOptions,
method,
body: currentParams.body,
headers: mergedHeaders,
query: currentParams.query
});
return {
data: resp._data,
status: resp.status
};
}
return {
invoke,
/**
* Default headers used in every client request (if not overriden in specific request).
*/
defaultHeaders,
hook: apiClientHooks.hook,
/**
* Update the base configuration for API client
*/
updateBaseConfig: (config) => {
let shouldRecreateClient = false;
if (config.baseURL !== void 0 && config.baseURL !== currentBaseURL) {
currentBaseURL = config.baseURL;
shouldRecreateClient = true;
}
if (config.accessToken !== void 0 && config.accessToken !== currentAccessToken) {
currentAccessToken = config.accessToken;
defaultHeaders["sw-access-key"] = config.accessToken;
}
if (shouldRecreateClient) {
apiFetch = createFetchClient(currentBaseURL);
}
},
/**
* Get the current base configuration
*/
getBaseConfig: () => ({
baseURL: currentBaseURL,
accessToken: currentAccessToken
})
};
}
function createAuthorizationHeader(token) {
if (!token) return "";
if (token.startsWith("Bearer ")) return token;
return `Bearer ${token}`;
}
function createAdminAPIClient(params) {
const isTokenBasedAuth = params.credentials?.grant_type === "client_credentials";
const apiClientHooks = hookable.createHooks();
const sessionData = {
accessToken: params.sessionData?.accessToken || "",
refreshToken: params.sessionData?.refreshToken || "",
expirationTime: Number(params.sessionData?.expirationTime || 0)
};
const defaultHeaders = createHeaders(
{
Authorization: createAuthorizationHeader(sessionData.accessToken),
Accept: "application/json"
},
(key, value) => {
apiClientHooks.callHook("onDefaultHeaderChanged", key, value);
}
);
function getSessionData() {
return { ...sessionData };
}
function setSessionData(data) {
sessionData.accessToken = data.accessToken;
sessionData.refreshToken = data.refreshToken || "";
sessionData.expirationTime = data.expirationTime;
return getSessionData();
}
function updateSessionData(responseData) {
if (responseData?.access_token) {
defaultHeaders.Authorization = createAuthorizationHeader(
responseData.access_token
);
const dataCopy = setSessionData({
accessToken: responseData.access_token,
refreshToken: responseData.refresh_token,
expirationTime: Date.now() + responseData.expires_in * 1e3
});
apiClientHooks.callHook("onAuthChange", dataCopy);
}
}
const apiFetch = ofetch.ofetch.create({
baseURL: params.baseURL,
...params.fetchOptions,
async onRequest({ request, options }) {
const isExpired = sessionData.expirationTime <= Date.now();
if (isExpired && !request.toString().includes("/oauth/token")) {
if (!params.credentials && !isTokenBasedAuth && !sessionData.refreshToken) {
console.warn(
"[ApiClientWarning] No `credentials` or `sessionData` provided. Provide at least one of them to ensure authentication."
);
}
const body = params.credentials && !sessionData.refreshToken ? params.credentials : {
grant_type: "refresh_token",
client_id: "administration",
refresh_token: sessionData.refreshToken
};
await ofetch.ofetch("/oauth/token", {
baseURL: params.baseURL,
method: "POST",
body,
headers: defaultHeaders,
onResponseError({ response }) {
errorInterceptor(response);
},
onResponse(context) {
if (!context.response._data) return;
updateSessionData(context.response._data);
options.headers.set(
"Authorization",
createAuthorizationHeader(sessionData.accessToken)
);
}
});
}
},
async onResponse(context) {
apiClientHooks.callHook("onSuccessResponse", context.response);
updateSessionData(context.response._data);
},
async onResponseError({ response }) {
apiClientHooks.callHook("onResponseError", response);
errorInterceptor(response);
}
});
async function invoke(pathParam, ...params2) {
const [, method, requestPath] = pathParam.split(" ");
const currentParams = params2[0] || {};
const requestPathWithParams = createPathWithParams(
requestPath,
currentParams.pathParams
);
const fetchOptions = {
...currentParams.fetchOptions || {}
};
const resp = await apiFetch.raw(requestPathWithParams, {
...fetchOptions,
method,
body: currentParams.body,
headers: defu__default(currentParams.headers, defaultHeaders),
query: currentParams.query
});
return {
data: resp._data,
status: resp.status
};
}
return {
invoke,
/**
* Enables to change session data in runtime. Useful for testing purposes.
* Setting session data with this method will **not** fire `onAuthChange` hook.
*/
setSessionData,
/**
* Returns current session data. Useful for testing purposes, as in most cases you'll want to use `onAuthChange` hook for that.
*/
getSessionData,
/**
* Default headers used in every client request (if not overriden in specific request).
*/
defaultHeaders,
/**
* Available hooks for the client.
*/
hook: apiClientHooks.hook
};
}
exports.ApiClientError = ApiClientError;
exports.createAPIClient = createAPIClient;
exports.createAdminAPIClient = createAdminAPIClient;