🕐 커밋 시간: 2025. 11. 26. 14:59:11 📊 변경 통계: • 총 파일: 12개 • 추가: +47줄 • 삭제: -50줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/App/App.js ~ com.twin.app.shoptime/src/App/deepLinkHandler.js ~ com.twin.app.shoptime/src/actions/appDataActions.js ~ com.twin.app.shoptime/src/actions/billingActions.js ~ com.twin.app.shoptime/src/actions/brandActions.js ~ com.twin.app.shoptime/src/actions/cancelActions.js ~ com.twin.app.shoptime/src/actions/cardActions.js ~ com.twin.app.shoptime/src/actions/checkoutActions.js ~ com.twin.app.shoptime/src/actions/commonActions.js ~ com.twin.app.shoptime/src/actions/convertActions.js ~ com.twin.app.shoptime/src/actions/couponActions.js ~ com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.jsx 🔧 주요 변경 내용: • 핵심 비즈니스 로직 개선 • 소규모 기능 개선 • 코드 정리 및 최적화 • 모듈 구조 개선 Performance: 코드 최적화로 성능 개선 기대
236 lines
7.5 KiB
JavaScript
236 lines
7.5 KiB
JavaScript
import { URLS } from '../api/apiConfig';
|
|
import { TAxios } from '../api/TAxios';
|
|
import { types } from './actionTypes';
|
|
import { getReAuthenticationCode } from './deviceActions';
|
|
import { createDebugHelpers } from '../utils/debug';
|
|
|
|
// 디버그 헬퍼 설정
|
|
const DEBUG_MODE = false;
|
|
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
|
|
|
|
/**
|
|
* PDF를 이미지로 변환 (재시도 로직 포함)
|
|
* @param {string} pdfUrl - 변환할 PDF URL
|
|
* @param {function} callback - 성공/실패 후 실행할 콜백 (error, imageUrl)
|
|
* @param {number} maxRetries - 최대 재시도 횟수 (기본값: 5)
|
|
* @param {number} timeout - 타임아웃 (기본값: 60000ms = 60초)
|
|
*/
|
|
export const convertPdfToImage =
|
|
(pdfUrl, callback, maxRetries = 5, timeout = 60000) =>
|
|
(dispatch, getState) => {
|
|
let attempts = 0;
|
|
let timeoutId = null;
|
|
|
|
const attemptConversion = () => {
|
|
attempts++;
|
|
// dlog(`🔄 [EnergyLabel] Converting PDF attempt ${attempts}/${maxRetries + 1}:`, pdfUrl);
|
|
|
|
// 타임아웃 설정
|
|
timeoutId = setTimeout(() => {
|
|
clearTimeout(timeoutId);
|
|
const timeoutError = new Error(
|
|
`Conversion timeout after ${timeout}ms (attempt ${attempts})`
|
|
);
|
|
void dwarn(`⏱️ [EnergyLabel] Timeout on attempt ${attempts}:`, timeoutError.message);
|
|
|
|
// 재시도 가능한 경우
|
|
if (attempts < maxRetries + 1) {
|
|
// dlog(`🔄 [EnergyLabel] Retrying... (${attempts}/${maxRetries + 1})`);
|
|
attemptConversion();
|
|
} else {
|
|
// 최종 실패
|
|
void derror(`❌ [EnergyLabel] Final failure after ${attempts} attempts:`, pdfUrl);
|
|
dispatch({
|
|
type: types.CONVERT_PDF_TO_IMAGE_FAILURE,
|
|
payload: { pdfUrl, error: timeoutError },
|
|
});
|
|
callback && callback(timeoutError, null);
|
|
}
|
|
}, timeout);
|
|
|
|
dispatch({
|
|
type: types.CONVERT_PDF_TO_IMAGE,
|
|
payload: pdfUrl,
|
|
});
|
|
|
|
const onSuccess = (response) => {
|
|
if (timeoutId) {
|
|
clearTimeout(timeoutId);
|
|
timeoutId = null;
|
|
}
|
|
|
|
// retCode 체크 (프로젝트 API 규약: 200이어도 retCode로 성공/실패 구분)
|
|
const retCode = response.headers?.retcode || response.headers?.retCode;
|
|
|
|
if (retCode !== undefined && retCode !== 0 && retCode !== '0') {
|
|
const error = new Error(`API Error: retCode=${retCode}`);
|
|
void dwarn(`⚠️ [EnergyLabel] API returned error on attempt ${attempts}:`, retCode);
|
|
|
|
// retCode 에러도 재시도
|
|
if (attempts < maxRetries + 1) {
|
|
void dlog(`🔄 [EnergyLabel] Retrying due to API error... (${attempts}/${maxRetries + 1})`);
|
|
attemptConversion();
|
|
} else {
|
|
void derror(`❌ [EnergyLabel] Final failure after ${attempts} attempts (API error):`, pdfUrl);
|
|
dispatch({
|
|
type: types.CONVERT_PDF_TO_IMAGE_FAILURE,
|
|
payload: { pdfUrl, error },
|
|
});
|
|
callback && callback(error, null);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (response.data.type !== 'image/png') {
|
|
dispatch(getReAuthenticationCode());
|
|
attemptConversion();
|
|
return;
|
|
}
|
|
|
|
let imageUrl;
|
|
try {
|
|
if (response.data instanceof Blob) {
|
|
if (response.data.size === 0) {
|
|
throw new Error('Invalid image data (empty blob)');
|
|
}
|
|
imageUrl = URL.createObjectURL(response.data);
|
|
} else if (response.data instanceof ArrayBuffer) {
|
|
if (response.data.byteLength === 0) {
|
|
throw new Error('Invalid image data (empty buffer)');
|
|
}
|
|
const blob = new Blob([response.data], { type: 'image/png' });
|
|
imageUrl = URL.createObjectURL(blob);
|
|
} else {
|
|
const blob = new Blob([response.data], { type: 'image/png' });
|
|
if (blob.size === 0) {
|
|
throw new Error('Invalid image data (empty blob)');
|
|
}
|
|
imageUrl = URL.createObjectURL(blob);
|
|
}
|
|
|
|
void dlog(`✅ [EnergyLabel] Conversion successful on attempt ${attempts}:`, pdfUrl);
|
|
dispatch({
|
|
type: types.CONVERT_PDF_TO_IMAGE_SUCCESS,
|
|
payload: { pdfUrl, imageUrl },
|
|
});
|
|
|
|
callback && callback(null, imageUrl);
|
|
} catch (error) {
|
|
void derror(`❌ [EnergyLabel] Image creation failed on attempt ${attempts}:`, error);
|
|
|
|
// 이미지 생성 실패도 재시도
|
|
if (attempts < maxRetries + 1) {
|
|
void dlog(
|
|
`🔄 [EnergyLabel] Retrying due to image creation error... (${attempts}/${maxRetries + 1})`
|
|
);
|
|
attemptConversion();
|
|
} else {
|
|
void derror(
|
|
`❌ [EnergyLabel] Final failure after ${attempts} attempts (image error):`,
|
|
pdfUrl
|
|
);
|
|
dispatch({
|
|
type: types.CONVERT_PDF_TO_IMAGE_FAILURE,
|
|
payload: { pdfUrl, error },
|
|
});
|
|
callback && callback(error, null);
|
|
}
|
|
}
|
|
};
|
|
|
|
const onFail = (error) => {
|
|
if (timeoutId) {
|
|
clearTimeout(timeoutId);
|
|
timeoutId = null;
|
|
}
|
|
|
|
void dwarn(`⚠️ [EnergyLabel] Network error on attempt ${attempts}:`, error.message);
|
|
|
|
// 네트워크 에러도 재시도
|
|
if (attempts < maxRetries + 1) {
|
|
void dlog(`🔄 [EnergyLabel] Retrying due to network error... (${attempts}/${maxRetries + 1})`);
|
|
attemptConversion();
|
|
} else {
|
|
void derror(
|
|
`❌ [EnergyLabel] Final failure after ${attempts} attempts (network error):`,
|
|
pdfUrl
|
|
);
|
|
dispatch({
|
|
type: types.CONVERT_PDF_TO_IMAGE_FAILURE,
|
|
payload: { pdfUrl, error },
|
|
});
|
|
callback && callback(error, null);
|
|
}
|
|
};
|
|
|
|
TAxios(
|
|
dispatch,
|
|
getState,
|
|
'post',
|
|
URLS.CONVERT_IMG,
|
|
{},
|
|
{ pdfUrl },
|
|
onSuccess,
|
|
onFail,
|
|
false,
|
|
'blob'
|
|
);
|
|
};
|
|
|
|
attemptConversion();
|
|
};
|
|
|
|
/**
|
|
* 여러 PDF를 순차적으로 변환 (백그라운드)
|
|
* @param {Array<string>} pdfUrls - 변환할 PDF URL 배열
|
|
* @param {function} callback - 완료 후 실행할 콜백 (errors, results)
|
|
*/
|
|
export const convertMultiplePdfs = (pdfUrls, callback) => async (dispatch) => {
|
|
if (!pdfUrls || pdfUrls.length === 0) {
|
|
callback && callback(null, []);
|
|
return;
|
|
}
|
|
|
|
const results = [];
|
|
const errors = [];
|
|
|
|
for (let i = 0; i < pdfUrls.length; i++) {
|
|
const pdfUrl = pdfUrls[i];
|
|
|
|
await new Promise((resolve) => {
|
|
dispatch(
|
|
convertPdfToImage(pdfUrl, (error, imageUrl) => {
|
|
if (error) {
|
|
errors.push({ pdfUrl, error });
|
|
} else {
|
|
results.push({ pdfUrl, imageUrl });
|
|
}
|
|
resolve();
|
|
})
|
|
);
|
|
});
|
|
|
|
if (i < pdfUrls.length - 1) {
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
}
|
|
}
|
|
|
|
callback && callback(errors.length > 0 ? errors : null, results);
|
|
};
|
|
|
|
/**
|
|
* 변환된 이미지 데이터 초기화 (전체)
|
|
*/
|
|
export const clearConvertedImage = () => ({
|
|
type: types.CLEAR_CONVERTED_IMAGE,
|
|
});
|
|
|
|
/**
|
|
* 특정 PDF의 변환된 이미지 데이터 제거
|
|
* @param {string} pdfUrl - 제거할 PDF URL
|
|
*/
|
|
export const clearConvertedImageByUrl = (pdfUrl) => ({
|
|
type: types.CLEAR_CONVERTED_IMAGE_BY_URL,
|
|
payload: pdfUrl,
|
|
});
|