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} 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, });