Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
"dist"
],
"dependencies": {
"axios": "^0.21.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-xml-parser": "^1.1.6"
Expand Down
89 changes: 54 additions & 35 deletions src/dymo_utils.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import axios from "axios";
import XMLParser from "react-xml-parser";
import { createFetchInstance, isAbortError } from "./fetchUtils";

import {
WS_PROTOCOL,
Expand All @@ -26,10 +26,10 @@ async function storeDymoRequestParams() {
continue loop2;
}
try {
const response = await axios.get(
dymoUrlBuilder(WS_PROTOCOL, hostList[currentHostIndex], currentPort, WS_SVC_PATH, "status")
);
const [successRequestHost, successRequestPort] = response.config.url.split("/")[2].split(":");
const url = dymoUrlBuilder(WS_PROTOCOL, hostList[currentHostIndex], currentPort, WS_SVC_PATH, "status");
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const [successRequestHost, successRequestPort] = url.split("/")[2].split(":");
localStore("dymo-ws-request-params", {activeHost: successRequestHost, activePort: successRequestPort});
break loop1;
} catch (error) {}
Expand All @@ -42,48 +42,69 @@ export async function dymoRequestBuilder({
wsPath = WS_SVC_PATH,
wsAction,
method,
cancelToken,
axiosOtherParams = {},
signal,
data,
headers = {},
}) {
if (!localRetrieve("dymo-ws-request-params")) {
await storeDymoRequestParams();
}
const {activeHost, activePort} = localRetrieve("dymo-ws-request-params");

const dymoAxiosInstance = axios.create();
dymoAxiosInstance.interceptors.response.use(
function (response) {
return response;
},
async function (error) {
if (axios.isCancel(error) || error?.response?.status === 500) {
return Promise.reject(error);
const dymoFetchInstance = createFetchInstance();

// Add response interceptor for retry logic
dymoFetchInstance.interceptors.response.push({
onFulfilled: (response) => response,
onRejected: async (error) => {
if (isAbortError(error) || (error.response && error.response.status === 500)) {
throw error;
}

await storeDymoRequestParams();
if (!localRetrieve("dymo-ws-request-params")) {
return Promise.reject(error);
throw error;
}

try {
const {activeHost, activePort} = localRetrieve("dymo-ws-request-params");
const response = await axios.request({
url: dymoUrlBuilder(wsProtocol, activeHost, activePort, wsPath, wsAction),
method,
cancelToken,
...axiosOtherParams,
});
return Promise.resolve(response);
} catch (error) {
return Promise.reject(error);
const response = await fetch(
dymoUrlBuilder(wsProtocol, activeHost, activePort, wsPath, wsAction),
{
method,
signal,
body: data,
headers
}
);

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

return response;
} catch (retryError) {
throw retryError;
}
}
);
const request = await dymoAxiosInstance.request({
});
const response = await dymoFetchInstance.request({
url: dymoUrlBuilder(wsProtocol, activeHost, activePort, wsPath, wsAction),
method,
cancelToken,
...axiosOtherParams,
signal,
data,
headers
});
return request;

// Parse response based on content type
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
response.data = await response.json();
} else {
response.data = await response.text();
}

return response;
}

export function dymoUrlBuilder(wsProtocol, wsHost, wsPort, wsPath, wsAction) {
Expand Down Expand Up @@ -126,10 +147,8 @@ export function printLabel(printerName, labelXml, labelSetXml) {
return dymoRequestBuilder({
method: "POST",
wsAction: "printLabel",
axiosOtherParams: {
data: `printerName=${encodeURIComponent(printerName)}&printParamsXml=&labelXml=${encodeURIComponent(
labelXml
)}&labelSetXml=${labelSetXml || ""}`,
},
data: `printerName=${encodeURIComponent(printerName)}&printParamsXml=&labelXml=${encodeURIComponent(
labelXml
)}&labelSetXml=${labelSetXml || ""}`,
});
}
108 changes: 108 additions & 0 deletions src/fetchUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
class FetchError extends Error {
constructor(message, response, isAborted = false) {
super(message);
this.name = 'FetchError';
this.response = response;
this.isAborted = isAborted;
}
}

export function isAbortError(error) {
return error instanceof FetchError && error.isAborted;
}

export async function fetchWithRetry(url, options = {}, retryConfig = {}) {
const { maxRetries = 0, retryCondition, onRetry } = retryConfig;
let lastError;

for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, options);

if (!response.ok && (!retryCondition || retryCondition(response))) {
throw new FetchError(`HTTP error! status: ${response.status}`, response);
}

return response;
} catch (error) {
if (error.name === 'AbortError') {
throw new FetchError('Request aborted', null, true);
}

lastError = error;

if (attempt < maxRetries && (!retryCondition || retryCondition(error.response))) {
if (onRetry) {
await onRetry(error, attempt);
}
continue;
}

throw error;
}
}

throw lastError;
}

export function createFetchInstance() {
const interceptors = {
response: []
};

return {
interceptors,

async request(config) {
const { url, method = 'GET', data, headers = {}, signal, ...otherOptions } = config;

const options = {
method,
headers: { ...headers },
signal,
...otherOptions
};

if (data) {
if (typeof data === 'string') {
options.body = data;
if (!options.headers['Content-Type']) {
options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
}
} else if (data instanceof FormData) {
options.body = data;
} else {
options.body = JSON.stringify(data);
if (!options.headers['Content-Type']) {
options.headers['Content-Type'] = 'application/json';
}
}
}

let response;
try {
response = await fetch(url, options);
} catch (error) {
if (error.name === 'AbortError') {
throw new FetchError('Request aborted', null, true);
}
throw error;
}

// Run response interceptors
for (const interceptor of this.interceptors.response) {
try {
response = await interceptor.onFulfilled(response, config);
} catch (error) {
if (interceptor.onRejected) {
response = await interceptor.onRejected(error, config);
} else {
throw error;
}
}
}

return response;
}
};
}
58 changes: 29 additions & 29 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {useState, useEffect, useRef} from "react";
import axios from "axios";
import { isAbortError } from "./fetchUtils";

import {useData} from "./hooks";
import {
Expand All @@ -16,27 +16,27 @@ export const printLabel = innerPrintSingleLabel;

export function useDymoCheckService(port) {
const [status, setStatus] = useState("initial");
const tokenSource = useRef();
const abortControllerRef = useRef();

useEffect(() => {
if (tokenSource.current) {
tokenSource.current.cancel();
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
tokenSource.current = axios.CancelToken.source();
abortControllerRef.current = new AbortController();
setStatus("loading");
dymoRequestBuilder({method: "GET", wsAction: "status", cancelToken: tokenSource.current.token})
dymoRequestBuilder({method: "GET", wsAction: "status", signal: abortControllerRef.current.signal})
.then(() => {
tokenSource.current = null;
abortControllerRef.current = null;
setStatus("success");
})
.catch((error) => {
if (!axios.isCancel(error)) {
if (!isAbortError(error)) {
setStatus("error");
}
});
return () => {
if (tokenSource.current) {
tokenSource.current.cancel();
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
}, [port]);
Expand All @@ -46,34 +46,34 @@ export function useDymoCheckService(port) {

export function useDymoFetchPrinters(statusDymoService, modelPrinter = "LabelWriterPrinter", port) {
const [data, setData] = useData({statusFetchPrinters: "initial", printers: [], error: null});
const tokenSource = useRef();
const abortControllerRef = useRef();

useEffect(() => {
if (statusDymoService === "success") {
if (tokenSource.current) {
tokenSource.current.cancel();
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
tokenSource.current = axios.CancelToken.source();
abortControllerRef.current = new AbortController();
setData({statusFetchPrinters: "loading"});

dymoRequestBuilder({method: "GET", wsAction: "getPrinters", cancelToken: tokenSource.current.token})
dymoRequestBuilder({method: "GET", wsAction: "getPrinters", signal: abortControllerRef.current.signal})
.then((response) => {
tokenSource.current = null;
abortControllerRef.current = null;
setData({
statusFetchPrinters: "success",
printers: getDymoPrintersFromXml(response.data, modelPrinter),
error: null,
});
})
.catch((error) => {
if (!axios.isCancel(error)) {
if (!isAbortError(error)) {
setData({statusFetchPrinters: "error", printers: [], error: error});
}
});
}
return () => {
if (tokenSource.current) {
tokenSource.current.cancel();
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
}, [modelPrinter, port, setData, statusDymoService]);
Expand All @@ -83,35 +83,35 @@ export function useDymoFetchPrinters(statusDymoService, modelPrinter = "LabelWri

export function useDymoOpenLabel(statusDymoService, labelXML, port) {
const [data, setData] = useData({statusOpenLabel: "initial", label: null, error: null});
const tokenSource = useRef();
const abortControllerRef = useRef();

useEffect(() => {
if (statusDymoService === "success") {
if (tokenSource.current) {
tokenSource.current.cancel();
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
tokenSource.current = axios.CancelToken.source();
abortControllerRef.current = new AbortController();
setData({statusOpenLabel: "loading"});
dymoRequestBuilder({
method: "POST",
wsAction: "renderLabel",
cancelToken: tokenSource.current.token,
axiosOtherParams: {data: `labelXml=${encodeURIComponent(labelXML)}&renderParamsXml=&printerName=`},
signal: abortControllerRef.current.signal,
data: `labelXml=${encodeURIComponent(labelXML)}&renderParamsXml=&printerName=`,
headers: {"Access-Control-Request-Private-Network": true, "Access-Control-Allow-Origin": "*"},
})
.then((response) => {
tokenSource.current = null;
abortControllerRef.current = null;
setData({statusOpenLabel: "success", label: response.data, error: null});
})
.catch((error) => {
if (!axios.isCancel(error)) {
if (!isAbortError(error)) {
setData({statusOpenLabel: "error", label: null, error: error});
}
});
}
return () => {
if (tokenSource.current) {
tokenSource.current.cancel();
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
}, [statusDymoService, labelXML, setData, port]);
Expand Down
Loading