diff --git a/src/pages/root/steps/presubmit-step/index.tsx b/src/pages/root/steps/presubmit-step/index.tsx index 9e91ef6..440cd02 100644 --- a/src/pages/root/steps/presubmit-step/index.tsx +++ b/src/pages/root/steps/presubmit-step/index.tsx @@ -42,18 +42,38 @@ export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => { setIsErrorUploadModal(false); uploadFile.resetUploadError(); uploadCover.resetUploadError(); + console.info("[App2Client][Presubmit] Пользователь закрыл модалку ошибок загрузки"); }; useEffect(() => { if (uploadFile.uploadError || uploadCover.uploadError) { + console.error("[App2Client][Presubmit] Обнаружены ошибки загрузки", { + fileError: uploadFile.uploadError, + coverError: uploadCover.uploadError, + }); setIsErrorUploadModal(true); } }, [uploadFile.uploadError, uploadCover.uploadError]); const handleSubmit = async () => { try { + console.info("[App2Client][Presubmit] Начинаем отправку контента", { + name: rootStore.name, + author: rootStore.author, + fileName: rootStore.file?.name, + fileSize: rootStore.file?.size, + fileType: rootStore.file?.type, + allowCover: rootStore.allowCover, + hasCover: Boolean(rootStore.cover), + allowDownload: rootStore.allowDwnld, + hashtags: rootStore.hashtags, + royaltyCount: rootStore.royalty.length, + allowResale: rootStore.allowResale, + }); + let coverUploadResult = { content_id_v1: "" }; + console.info("[App2Client][Presubmit] Загружаем основной файл через tus"); const fileUploadResult = await uploadFile.mutateAsync({ file: rootStore.file as File, metadata: { @@ -70,12 +90,37 @@ export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => { throw new Error("Tus upload did not return encrypted cid"); } + console.info("[App2Client][Presubmit] Основной файл загружен", { + encryptedCid: fileUploadResult.encryptedCid, + uploadId: fileUploadResult.uploadId, + state: fileUploadResult.state, + sizeBytes: fileUploadResult.sizeBytes, + }); + if (rootStore.allowCover && rootStore.cover) { + console.info("[App2Client][Presubmit] Начинаем загрузку обложки", { + fileName: rootStore.cover.name, + fileSize: rootStore.cover.size, + fileType: rootStore.cover.type, + }); coverUploadResult = await uploadCover.mutateAsync( rootStore.cover as File, ); + + console.info("[App2Client][Presubmit] Обложка загружена", { + contentIdV1: coverUploadResult.content_id_v1, + }); + } else { + console.info("[App2Client][Presubmit] Обложка не загружается", { + allowCover: rootStore.allowCover, + hasCoverFile: Boolean(rootStore.cover), + }); } + console.info("[App2Client][Presubmit] Отправляем метаданные контента", { + downloadable: rootStore.allowDwnld, + coverIncluded: Boolean(coverUploadResult.content_id_v1), + }); const createContentResponse = await createContent.mutateAsync({ title: rootStore.name, @@ -112,6 +157,10 @@ export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => { if (createContentResponse.data) { if (createContentResponse.data.address != "free") { + console.info("[App2Client][Presubmit] Отправляем транзакцию через TonConnect", { + address: createContentResponse.data.address, + amount: createContentResponse.data.amount, + }); const transactionResponse = await tonConnectUI.sendTransaction({ validUntil: Math.floor(Date.now() / 1000) + 120, messages: [ @@ -123,19 +172,29 @@ export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => { ], }); if (transactionResponse.boc) { + console.info("[App2Client][Presubmit] Транзакция успешно отправлена", { + bocLength: transactionResponse.boc.length, + }); WebApp.close(); } else { console.error("Transaction failed:", transactionResponse); + console.error("[App2Client][Presubmit] Транзакция не отправлена", { + response: transactionResponse, + }); } } } + console.info("[App2Client][Presubmit] Завершаем процесс и закрываем WebApp"); WebApp.close(); // @ts-expect-error Type issues } catch (error: never) { console.error("An error occurred during the submission process:", error); + console.error("[App2Client][Presubmit] Ошибка во время отправки", { + error, + }); if (error?.status === 400) { alert( diff --git a/src/shared/services/content/index.ts b/src/shared/services/content/index.ts index 183c01a..fd18224 100644 --- a/src/shared/services/content/index.ts +++ b/src/shared/services/content/index.ts @@ -20,16 +20,44 @@ type UseCreateNewContentPayload = { export const useCreateNewContent = () => { return useMutation( ["create-new-content"], - (payload: UseCreateNewContentPayload) => { - return request.post<{ - address: string; - amount: string; - payload: string; - }>("/blockchain.sendNewContentMessage", payload, { - headers: { - Authorization: localStorage.getItem('auth_v1_token') ?? "" - } + async (payload: UseCreateNewContentPayload) => { + console.info("[App2Client][Content] Отправляем контент на создание", { + title: payload.title, + hasImage: Boolean(payload.image), + contentCid: payload.content, + hashtags: payload.hashtags, + authorsCount: payload.authors.length, + royaltyCount: payload.royaltyParams.length, + downloadable: payload.downloadable, + allowResale: payload.allowResale, }); + + try { + const response = await request.post<{ + address: string; + amount: string; + payload: string; + }>("/blockchain.sendNewContentMessage", payload, { + headers: { + Authorization: localStorage.getItem('auth_v1_token') ?? "" + } + }); + + console.info("[App2Client][Content] Сервер вернул данные для транзакции", { + title: payload.title, + hasAddress: Boolean(response.data.address), + amount: response.data.amount, + payloadLength: response.data.payload?.length ?? 0, + }); + + return response; + } catch (error) { + console.error("[App2Client][Content] Ошибка при создании контента", { + title: payload.title, + error, + }); + throw error; + } }, ); }; diff --git a/src/shared/services/file/index.ts b/src/shared/services/file/index.ts index 9110ba2..158c6b9 100644 --- a/src/shared/services/file/index.ts +++ b/src/shared/services/file/index.ts @@ -94,8 +94,16 @@ const pollUploadStatus = async ( signal?: AbortSignal, ): Promise => { const startedAt = Date.now(); + let attempt = 0; + + console.info("[App2Client][TusUpload] Запускаем polling статуса", { + uploadId, + intervalMs: TUS_STATUS_POLL_INTERVAL_MS, + timeoutMs: TUS_STATUS_POLL_TIMEOUT_MS, + }); while (true) { + attempt += 1; if (signal?.aborted) { throw new DOMException("Tus status polling aborted", "AbortError"); } @@ -108,25 +116,58 @@ const pollUploadStatus = async ( }, ); + console.info("[App2Client][TusUpload] Получен статус tus", { + uploadId, + attempt, + state: data.state, + hasEncryptedCid: Boolean(data.encrypted_cid), + }); + if (data.state === "failed") { throw new Error(data.error || "Tus upload failed on server"); } if (data.state === "pinned" && data.encrypted_cid) { + console.info("[App2Client][TusUpload] Статус tus завершен", { + uploadId, + state: data.state, + encryptedCid: data.encrypted_cid, + sizeBytes: data.size_bytes, + attempts: attempt, + durationMs: Date.now() - startedAt, + }); return data; } } catch (error) { const maybeAxios = error as { response?: { status?: number } }; if (maybeAxios?.response?.status === 404) { // Hook has not recorded the upload session yet; continue polling. + console.info("[App2Client][TusUpload] Статус tus еще не готов (404)", { + uploadId, + attempt, + }); } else if (error instanceof DOMException && error.name === "AbortError") { + console.warn("[App2Client][TusUpload] Поллинг tus прерван", { + uploadId, + attempt, + }); throw error; } else { + console.error("[App2Client][TusUpload] Ошибка во время polling", { + uploadId, + attempt, + error, + }); throw normalizeError(error, "Tus status polling failed"); } } if (Date.now() - startedAt > TUS_STATUS_POLL_TIMEOUT_MS) { + console.error("[App2Client][TusUpload] Поллинг tus превысил таймаут", { + uploadId, + attempts: attempt, + durationMs: Date.now() - startedAt, + }); throw new Error("Timed out waiting for tus upload finalization"); } @@ -153,6 +194,7 @@ export const useTusUpload = () => { const [isUploading, setIsUploading] = useState(false); const [uploadError, setUploadError] = useState(null); const activeUploadRef = useRef(null); + const lastLoggedProgressRef = useRef(-10); const mutation = useMutation( ["upload-file", "tus"], @@ -161,15 +203,16 @@ export const useTusUpload = () => { throw new Error("File is required for tus upload"); } - if (!TUS_ENDPOINT) { - throw new Error("Tus endpoint is not configured"); - } - setIsUploading(true); setUploadProgress(0); setUploadError(null); + let lastError: Error | null = null; try { + if (!TUS_ENDPOINT) { + throw new Error("Tus endpoint is not configured"); + } + const result = await new Promise((resolve, reject) => { const token = localStorage.getItem("auth_v1_token") ?? ""; const normalizedMeta = normalizeMetadata(metadata); @@ -178,6 +221,13 @@ export const useTusUpload = () => { headers.Authorization = token; } + console.info("[App2Client][TusUpload] Начинаем загрузку", { + fileName: file.name, + fileSize: file.size, + fileType: file.type, + metadata: normalizedMeta, + }); + const upload = new Upload(file, { endpoint: TUS_ENDPOINT, metadata: { @@ -191,6 +241,7 @@ export const useTusUpload = () => { uploadDataDuringCreation: true, onError: (err) => { const normalized = normalizeError(err, "Tus upload failed"); + lastError = normalized; setUploadError(normalized); setIsUploading(false); setUploadProgress(0); @@ -202,6 +253,16 @@ export const useTusUpload = () => { ? Math.floor((bytesUploaded / bytesTotal) * 100) : 0; setUploadProgress(Math.min(99, Math.max(0, percent))); + + if (percent >= lastLoggedProgressRef.current + 5 || percent === 100) { + lastLoggedProgressRef.current = percent; + console.info("[App2Client][TusUpload] Прогресс загрузки", { + fileName: file.name, + bytesUploaded, + bytesTotal, + percent, + }); + } }, onSuccess: () => { (async () => { @@ -213,6 +274,10 @@ export const useTusUpload = () => { } const uploadId = extractUploadId(uploadUrl); + console.info("[App2Client][TusUpload] Загрузка завершена на tus, начинаем финализацию", { + fileName: file.name, + uploadId, + }); const status = await pollUploadStatus(uploadId, signal); const encryptedCid = status.encrypted_cid; if (!encryptedCid) { @@ -223,6 +288,14 @@ export const useTusUpload = () => { setIsUploading(false); activeUploadRef.current = null; + console.info("[App2Client][TusUpload] Финализация tus завершена", { + fileName: file.name, + uploadId, + encryptedCid, + finalState: status.state, + sizeBytes: status.size_bytes, + }); + resolve({ kind: "tus", uploadId, @@ -235,6 +308,11 @@ export const useTusUpload = () => { statusError, "Failed to finalize tus upload", ); + lastError = normalized; + console.error("[App2Client][TusUpload] Ошибка финализации tus", { + fileName: file.name, + error: normalized, + }); setUploadError(normalized); setIsUploading(false); activeUploadRef.current = null; @@ -245,6 +323,11 @@ export const useTusUpload = () => { }); activeUploadRef.current = upload; + lastLoggedProgressRef.current = 0; + + console.info("[App2Client][TusUpload] Запрашиваем предыдущие загрузки для возобновления", { + fileName: file.name, + }); if (signal) { signal.addEventListener( @@ -255,6 +338,10 @@ export const useTusUpload = () => { "Tus upload aborted", "AbortError", ); + console.warn("[App2Client][TusUpload] Загрузка прервана внешним сигналом", { + fileName: file.name, + }); + lastError = abortError; setUploadError(abortError); setIsUploading(false); activeUploadRef.current = null; @@ -267,16 +354,32 @@ export const useTusUpload = () => { upload .findPreviousUploads() .then((previousUploads) => { + console.info("[App2Client][TusUpload] Результат поиска предыдущих загрузок", { + fileName: file.name, + count: previousUploads.length, + }); if (previousUploads.length > 0) { upload.resumeFromPreviousUpload(previousUploads[0]); + console.info("[App2Client][TusUpload] Возобновляем загрузку", { + fileName: file.name, + }); } upload.start(); + console.info("[App2Client][TusUpload] Стартуем загрузку", { + fileName: file.name, + chunkSize: upload.options.chunkSize, + }); }) .catch((resumeError) => { const normalized = normalizeError( resumeError, "Failed to resume tus upload", ); + lastError = normalized; + console.error("[App2Client][TusUpload] Не удалось возобновить загрузку", { + fileName: file.name, + error: normalized, + }); setUploadError(normalized); setIsUploading(false); activeUploadRef.current = null; @@ -285,9 +388,25 @@ export const useTusUpload = () => { }); return result; + } catch (error) { + const normalized = normalizeError(error, "Tus upload failed"); + if (!lastError) { + lastError = normalized; + console.error("[App2Client][TusUpload] Ошибка во время загрузки", { + fileName: file.name, + error: normalized, + }); + setUploadError(normalized); + } + setIsUploading(false); + throw normalized; } finally { activeUploadRef.current = null; setIsUploading(false); + console.info("[App2Client][TusUpload] Завершили работу загрузчика", { + fileName: file?.name, + hasError: Boolean(lastError), + }); } }, ); @@ -300,6 +419,9 @@ export const useTusUpload = () => { activeUploadRef.current = null; setIsUploading(false); setUploadProgress(0); + console.warn("[App2Client][TusUpload] Принудительно остановили загрузку", { + reason: "manual", + }); } }; @@ -317,11 +439,21 @@ export const useLegacyUploadFile = () => { const [uploadProgress, setUploadProgress] = useState(0); const [isUploading, setIsUploading] = useState(false); const [uploadError, setUploadError] = useState(null); + const lastLoggedProgressRef = useRef(-10); const mutation = useMutation(["upload-file", "legacy"], async (file: File) => { setIsUploading(true); setUploadProgress(0); setUploadError(null); + lastLoggedProgressRef.current = 0; + let lastError: Error | null = null; + + console.info("[App2Client][LegacyUpload] Начинаем загрузку", { + fileName: file.name, + fileSize: file.size, + fileType: file.type, + isChunked: file.size > MAX_CHUNK_SIZE, + }); try { if (file.size <= MAX_CHUNK_SIZE) { @@ -352,11 +484,35 @@ export const useLegacyUploadFile = () => { (progressEvent.loaded * 100) / total, ); setUploadProgress(Math.min(99, percentCompleted)); + + if ( + percentCompleted >= lastLoggedProgressRef.current + 5 || + percentCompleted === 100 + ) { + lastLoggedProgressRef.current = percentCompleted; + console.info("[App2Client][LegacyUpload] Прогресс загрузки", { + fileName: file.name, + percent: percentCompleted, + loaded: progressEvent.loaded, + total, + }); + } }, }); setUploadProgress(100); + console.info("[App2Client][LegacyUpload] Загрузка завершена", { + fileName: file.name, + response: { + hasContentId: Boolean( + response.data.content_id_v1 || response.data.content_id, + ), + hasSha256: Boolean(response.data.content_sha256), + uploadId: response.data.upload_id, + }, + }); + return { content_sha256: response.data.content_sha256 || "", content_id_v1: @@ -392,6 +548,15 @@ export const useLegacyUploadFile = () => { headers["X-Upload-ID"] = uploadId; } + console.info("[App2Client][LegacyUpload] Отправляем chunk", { + fileName: file.name, + chunkStart: offset, + chunkEnd, + chunkSize: chunk.size, + isLastChunk, + uploadId, + }); + const response = await request.post<{ upload_id?: string; current_size?: number; @@ -408,16 +573,42 @@ export const useLegacyUploadFile = () => { (overallProgress / file.size) * 100, ); setUploadProgress(Math.min(99, percentCompleted)); + + if ( + percentCompleted >= lastLoggedProgressRef.current + 5 || + percentCompleted === 100 + ) { + lastLoggedProgressRef.current = percentCompleted; + console.info("[App2Client][LegacyUpload] Прогресс загрузки", { + fileName: file.name, + percent: percentCompleted, + loaded: overallProgress, + total: file.size, + }); + } }, }); if (!uploadId && response.data.upload_id) { uploadId = response.data.upload_id; + console.info("[App2Client][LegacyUpload] Получили upload_id", { + fileName: file.name, + uploadId, + }); } if (response.data.content_id) { setUploadProgress(100); + console.info("[App2Client][LegacyUpload] Сервер вернул итоговые данные", { + fileName: file.name, + uploadId, + hasContentId: Boolean( + response.data.content_id_v1 || response.data.content_id, + ), + hasSha256: Boolean(response.data.content_sha256), + }); + return { content_sha256: response.data.content_sha256 || "", content_id_v1: @@ -428,9 +619,18 @@ export const useLegacyUploadFile = () => { if (response.data.current_size !== undefined) { offset = response.data.current_size; + console.info("[App2Client][LegacyUpload] Продолжаем загрузку", { + fileName: file.name, + nextOffset: offset, + }); } else { const error = new Error("Missing current_size in response"); setUploadError(error); + lastError = error; + console.error("[App2Client][LegacyUpload] Сервер не вернул current_size", { + fileName: file.name, + response, + }); throw error; } } @@ -439,6 +639,10 @@ export const useLegacyUploadFile = () => { "All chunks uploaded but server did not return content_id", ); setUploadError(error); + lastError = error; + console.error("[App2Client][LegacyUpload] Все чанки отправлены, но content_id отсутствует", { + fileName: file.name, + }); throw error; } catch (error) { const normalized = normalizeError( @@ -447,9 +651,18 @@ export const useLegacyUploadFile = () => { ); setUploadError(normalized); setIsUploading(false); + lastError = normalized; + console.error("[App2Client][LegacyUpload] Ошибка во время загрузки", { + fileName: file.name, + error: normalized, + }); throw normalized; } finally { setIsUploading(false); + console.info("[App2Client][LegacyUpload] Завершили работу загрузчика", { + fileName: file.name, + hasError: Boolean(lastError), + }); } });