UNPKG

@mussnad/frappe-js-client

Version:

Next-generation TS/JS client for Frappe REST APIs, built on axios for robust, type-safe integration.

269 lines (268 loc) 11.6 kB
"use strict"; /** * @module utils/axios * @description Provides utility functions for creating and configuring Axios instances * with Frappe-specific headers and authentication. This module handles CSRF token management, * site name headers, and authentication token configuration for API requests. * * @packageDocumentation * @preferred * * @example * ```typescript * import { getAxiosClient } from '@frappe/axios' * * // Create a client with Bearer token authentication * const client = getAxiosClient( * 'https://api.example.com', * true, * () => 'your-token', * 'Bearer' * ); * * // Create a client without authentication * const basicClient = getAxiosClient('https://api.example.com'); * ``` */ var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAxiosClient = getAxiosClient; exports.getRequestHeaders = getRequestHeaders; exports.getCSRFToken = getCSRFToken; exports.handleRequest = handleRequest; exports.getSiteName = getSiteName; var axios_1 = require("axios"); /** * Creates and configures an Axios instance with Frappe-specific settings. * * @param appURL - The base URL for API requests * @param useToken - Optional flag to enable token-based authentication * @param token - Optional function that returns the authentication token * @param tokenType - Optional token type, either 'Bearer' or 'token' * @param customHeaders - Optional additional headers to include in requests * * @returns An configured Axios instance * * @example * ```typescript * // Basic usage without authentication * const client = getAxiosClient('https://instance.example.com'); * * // With Bearer token authentication * const authenticatedClient = getAxiosClient( * 'https://instance.example.com', * true, * () => localStorage.getItem('token'), * 'Bearer' * ); * * // With custom headers * const clientWithHeaders = getAxiosClient( * 'https://instance.example.com', * false, * undefined, * undefined, * { 'Custom-Header': 'value' } * ); * ``` */ function getAxiosClient(appURL, useToken, token, tokenType, customHeaders) { return axios_1.default.create({ baseURL: appURL, headers: __assign(__assign({}, getRequestHeaders(useToken, tokenType, token, appURL, customHeaders)), { 'Content-Type': 'application/json; charset=utf-8' }), withCredentials: true, }); } /** * Generates request headers for Frappe API requests. * * @param useToken - Whether to include authentication token in headers * @param tokenType - The type of authentication token ('Bearer' or 'token') * @param token - Function that returns the authentication token * @param appURL - The base URL of the application * @param customHeaders - Additional custom headers to include * * @returns An object containing the configured request headers * * @remarks * This function automatically handles: * - Content-Type and Accept headers for JSON * - Authentication token headers when specified * - Frappe-specific headers (X-Frappe-Site-Name) * - CSRF token headers in browser environments * * @example * ```typescript * const headers = getRequestHeaders( * true, * 'Bearer', * () => 'your-token', * 'https://instance.example.com', * { 'Custom-Header': 'value' } * ); * ``` * * @internal */ function getRequestHeaders(useToken, tokenType, token, appURL, customHeaders) { if (useToken === void 0) { useToken = false; } var headers = { Accept: 'application/json', 'Content-Type': 'application/json; charset=utf-8', }; if (useToken && tokenType && token) { headers.Authorization = "".concat(tokenType, " ").concat(token()); } // Extract site name from appURL if provided var siteName = getSiteName(appURL); // in case of browser environments if (typeof window !== 'undefined' && typeof document !== 'undefined') { // Only set X-Frappe-Site-Name if appURL matches window origin if (!appURL || new URL(appURL).origin === window.location.origin) { headers['X-Frappe-Site-Name'] = siteName || window.location.hostname; } if (window.csrf_token && window.csrf_token !== '{{ csrf_token }}') { headers['X-Frappe-CSRF-Token'] = window.csrf_token; } } return __assign(__assign({}, headers), (customHeaders !== null && customHeaders !== void 0 ? customHeaders : {})); } function getCSRFToken() { if (typeof window === 'undefined' || typeof document === 'undefined') { return undefined; } var meta = document.querySelector('meta[name="csrf_token"]'); if (meta) { var content = meta.getAttribute('content'); return content !== null && content !== void 0 ? content : undefined; } return undefined; } /** * Handles a request and returns the transformed response data. * * @param axios - The axios instance to use for the request * @param config - The request configuration * @param errorMessage - Optional error message to use when request fails * @param transformResponse - Optional function to transform the response data * @returns The transformed response data * * @example * ```typescript * const response = await handleRequest({ * axios, * config, * errorMessage: 'An error occurred while processing the request.', * transformResponse: (data: T): R => data as any as R, * }); * ``` */ function handleRequest(_a) { return __awaiter(this, arguments, void 0, function (_b) { var response, error_1, axiosError; var _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t; var axios = _b.axios, config = _b.config, _u = _b.errorMessage, errorMessage = _u === void 0 ? 'An error occurred while processing the request.' : _u, _v = _b.transformResponse, transformResponse = _v === void 0 ? function (response) { return response; } : _v; return __generator(this, function (_w) { switch (_w.label) { case 0: _w.trys.push([0, 2, , 3]); return [4 /*yield*/, axios.request(config)]; case 1: response = _w.sent(); return [2 /*return*/, transformResponse(response.data)]; case 2: error_1 = _w.sent(); if ((0, axios_1.isAxiosError)(error_1)) { axiosError = error_1; throw __assign(__assign({}, (_c = axiosError.response) === null || _c === void 0 ? void 0 : _c.data), { httpStatus: (_e = (_d = axiosError.response) === null || _d === void 0 ? void 0 : _d.status) !== null && _e !== void 0 ? _e : 500, httpStatusText: (_g = (_f = axiosError.response) === null || _f === void 0 ? void 0 : _f.statusText) !== null && _g !== void 0 ? _g : 'Internal Server Error', message: (_k = (_j = (_h = axiosError.response) === null || _h === void 0 ? void 0 : _h.data) === null || _j === void 0 ? void 0 : _j.message) !== null && _k !== void 0 ? _k : errorMessage, exception: (_r = (_o = (_m = (_l = axiosError.response) === null || _l === void 0 ? void 0 : _l.data) === null || _m === void 0 ? void 0 : _m.exception) !== null && _o !== void 0 ? _o : (_q = (_p = axiosError.response) === null || _p === void 0 ? void 0 : _p.data) === null || _q === void 0 ? void 0 : _q.exc_type) !== null && _r !== void 0 ? _r : 'UnknownException', _server_messages: parseServerMessages((_t = (_s = axiosError.response) === null || _s === void 0 ? void 0 : _s.data) === null || _t === void 0 ? void 0 : _t._server_messages) }); } throw error_1; case 3: return [2 /*return*/]; } }); }); } /** * Parses the server messages from a string to an array of objects. * * @param serverMessages - The server messages to parse * @returns The parsed server messages */ var parseServerMessages = function (serverMessages) { if (!serverMessages) { return []; } try { var parsedArray = JSON.parse(serverMessages); return parsedArray.map(function (item) { return JSON.parse(item); }); } catch (error) { console.warn('Failed to parse server messages:', error); return []; } }; /** * Extracts the site name from the appURL. * * @param appURL - The base URL of the application * @returns The site name */ function getSiteName(appURL) { if (!appURL) { return undefined; } try { var url = new URL(appURL); return url.hostname; } catch (error) { console.warn("Invalid appURL provided: ".concat(appURL), error); return undefined; } }