umay-render
Version:
Free, high-performance HTML to PDF and HTML to Image conversion SDK for both browser and Node.js
154 lines (153 loc) • 6.77 kB
JavaScript
import axios from "axios";
import { UmayError, ErrorCodes } from "./errors";
function ensureUint8Array(data) {
if (data instanceof Uint8Array)
return data;
if (data instanceof ArrayBuffer)
return new Uint8Array(data);
if (typeof data === "object" &&
data !== null &&
typeof data.byteLength === "number") {
try {
return new Uint8Array(data);
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Cannot convert to Uint8Array: ${errorMessage}`);
}
}
throw new Error(`Cannot convert to Uint8Array: Unsupported data type ${typeof data}`);
}
export class HttpClient {
constructor(config) {
this.config = config;
this.client = axios.create({
baseURL: config.API_URL,
timeout: config.TIMEOUT,
headers: {
"Content-Type": "application/json",
Accept: "application/json, image/*, application/pdf",
},
responseType: "arraybuffer",
validateStatus: (status) => status < 500,
});
}
async request(endpoint, data) {
try {
const response = await this.client.post(endpoint, data, {});
return this.processSuccessResponse(response);
}
catch (error) {
console.error(`HTTP client error during request to ${endpoint}:`, error);
if (axios.isAxiosError(error)) {
const axiosError = error;
if (axiosError.code === "ECONNABORTED" ||
axiosError.message.includes("timeout")) {
throw new UmayError(ErrorCodes.TIMEOUT, `Request timed out after ${this.config.TIMEOUT}ms`, axiosError.config);
}
if (axiosError.response) {
return this.processErrorResponse(axiosError.response);
}
else if (axiosError.request) {
throw new UmayError(ErrorCodes.NETWORK_ERROR, `Network error: No response received from server. ${axiosError.message}`, { requestDetails: axiosError.request });
}
else {
throw new UmayError(ErrorCodes.NETWORK_ERROR, `Request setup error: ${axiosError.message}`, axiosError.config);
}
}
throw new UmayError(ErrorCodes.NETWORK_ERROR, error instanceof Error
? error.message
: "Unknown network or client error", error);
}
}
processSuccessResponse(response) {
const contentType = response.headers["content-type"]?.toLowerCase() || "";
const data = response.data;
if (contentType.includes("application/pdf") ||
contentType.includes("image/")) {
try {
const uint8Array = ensureUint8Array(data);
return uint8Array;
}
catch (e) {
console.error("Failed to process binary data:", e);
throw new UmayError(ErrorCodes.API_ERROR, "Failed to process binary response from API", e);
}
}
if (contentType.includes("application/json")) {
try {
const text = new TextDecoder().decode(data);
const jsonData = JSON.parse(text);
return jsonData;
}
catch (e) {
console.warn("Received JSON content-type but failed to parse response body.");
throw new UmayError(ErrorCodes.API_ERROR, "Failed to parse JSON response from API", { responseText: new TextDecoder().decode(data) });
}
}
console.warn(`Unexpected content type received: ${contentType}. Returning raw data.`);
try {
const uint8Array = ensureUint8Array(data);
return uint8Array;
}
catch (e) {
return data;
}
}
processErrorResponse(response) {
const status = response.status;
let errorCode = ErrorCodes.API_ERROR;
let errorMessage = `API request failed with status ${status}`;
let errorDetails = null;
let publicMessage = undefined;
try {
if (response.data && response.data.byteLength > 0) {
const text = new TextDecoder().decode(response.data);
try {
const json = JSON.parse(text);
if (json.error && typeof json.error === "object") {
const errObj = json.error;
const backendCode = errObj.code;
if (backendCode && typeof backendCode === "string") {
errorCode = Object.prototype.hasOwnProperty.call(ErrorCodes, backendCode)
? backendCode
: backendCode;
}
publicMessage =
typeof errObj.message === "string" ? errObj.message : undefined;
errorMessage =
typeof errObj.internal_message === "string"
? errObj.internal_message
: publicMessage || errorMessage;
}
else if (typeof json.error === "string") {
publicMessage = json.error;
errorMessage = json.error;
}
errorDetails = json.details || json.stack || json;
}
catch (parseError) {
console.warn("API error response was not valid JSON:", text);
errorMessage = `API Error (${status}): ${text || "No response body"}`;
publicMessage = `An API error occurred (Status: ${status})`;
errorDetails = { responseText: text };
}
}
else {
errorMessage = `API request failed with status ${status} and empty response body`;
publicMessage = `An API error occurred (Status: ${status})`;
}
}
catch (decodeError) {
console.error("Error processing API error response data:", decodeError);
errorMessage = `Failed to process error response (Status: ${status})`;
publicMessage = `An error occurred while processing the API response (Status: ${status})`;
errorDetails = decodeError;
}
throw new UmayError(errorCode, errorMessage, {
status: status,
responseDetails: errorDetails,
publicMessage: publicMessage,
});
}
}