n8n-nodes-sotoros-gotenberg 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -288,33 +288,70 @@ class Gotenberg {
288
288
  // Создаем FormData для отправки в Gotenberg - один запрос для всех элементов списка
289
289
  const formData = new form_data_1.default();
290
290
  let totalFilesCount = 0;
291
+ const processedFiles = [];
292
+ // Собираем информацию о доступных binary данных в aggregatedItem
293
+ const aggregatedBinaryKeys = Object.keys(aggregatedItem.binary || {});
294
+ const debugInfo = {
295
+ aggregatedItemBinaryKeys: aggregatedBinaryKeys,
296
+ listDataLength: listData.length,
297
+ binaryPropertyName,
298
+ listPropertyName: listPropertyName || '(root array)',
299
+ processingSteps: [],
300
+ };
291
301
  // Собираем все binary данные из всех элементов списка
292
302
  for (let listIndex = 0; listIndex < listData.length; listIndex++) {
293
303
  const listItem = listData[listIndex];
304
+ const stepInfo = {
305
+ listIndex,
306
+ itemType: typeof listItem,
307
+ isObject: typeof listItem === 'object' && listItem !== null,
308
+ itemKeys: listItem && typeof listItem === 'object' ? Object.keys(listItem) : [],
309
+ foundBinary: false,
310
+ source: 'none',
311
+ binaryKey: undefined,
312
+ };
294
313
  if (!listItem || typeof listItem !== 'object') {
314
+ stepInfo.error = 'Item is not an object or is null';
315
+ debugInfo.processingSteps.push(stepInfo);
295
316
  continue; // Пропускаем некорректные элементы
296
317
  }
297
318
  // Элемент списка может быть INodeExecutionData (с полями json и binary)
298
319
  // или обычным объектом с binary данными в разных местах
299
320
  let itemBinary;
300
321
  let binaryProperty;
322
+ let binaryKeyFromAggregated;
323
+ let source = 'unknown';
301
324
  // Вариант 1: элемент имеет структуру INodeExecutionData (json и binary)
302
325
  if ('binary' in listItem && listItem.binary && typeof listItem.binary === 'object') {
303
326
  itemBinary = listItem.binary;
304
- binaryProperty = itemBinary[binaryPropertyName];
327
+ if (binaryPropertyName in itemBinary) {
328
+ binaryProperty = itemBinary[binaryPropertyName];
329
+ source = 'listItem.binary';
330
+ stepInfo.source = source;
331
+ stepInfo.binaryKey = binaryPropertyName;
332
+ stepInfo.foundBinary = true;
333
+ }
305
334
  }
306
335
  // Вариант 2: binary данные находятся в свойстве элемента напрямую
307
- else if (binaryPropertyName in listItem) {
336
+ if (!binaryProperty && binaryPropertyName in listItem) {
308
337
  const binaryProp = listItem[binaryPropertyName];
309
338
  if (binaryProp && typeof binaryProp === 'object') {
310
339
  // Может быть одиночным объектом или массивом
311
- if (binaryProp.data) {
340
+ if (binaryProp.data || binaryProp.fileName || binaryProp.mimeType) {
312
341
  // Это одиночный binary объект
313
342
  binaryProperty = binaryProp;
343
+ source = 'listItem[binaryPropertyName]';
344
+ stepInfo.source = source;
345
+ stepInfo.binaryKey = binaryPropertyName;
346
+ stepInfo.foundBinary = true;
314
347
  }
315
348
  else if (Array.isArray(binaryProp)) {
316
349
  // Это массив binary объектов
317
350
  binaryProperty = binaryProp;
351
+ source = 'listItem[binaryPropertyName] (array)';
352
+ stepInfo.source = source;
353
+ stepInfo.binaryKey = binaryPropertyName;
354
+ stepInfo.foundBinary = true;
318
355
  }
319
356
  }
320
357
  }
@@ -322,24 +359,102 @@ class Gotenberg {
322
359
  // ищем их в aggregatedItem.binary по индексу
323
360
  // Для первого элемента (index 0) используем binaryPropertyName, для остальных - binaryPropertyName_${index}
324
361
  if (!binaryProperty && aggregatedItem.binary) {
325
- const binaryKey = listIndex === 0 ? binaryPropertyName : `${binaryPropertyName}_${listIndex}`;
326
- if (binaryKey in aggregatedItem.binary) {
327
- binaryProperty = aggregatedItem.binary[binaryKey];
362
+ binaryKeyFromAggregated = listIndex === 0 ? binaryPropertyName : `${binaryPropertyName}_${listIndex}`;
363
+ if (binaryKeyFromAggregated in aggregatedItem.binary) {
364
+ binaryProperty = aggregatedItem.binary[binaryKeyFromAggregated];
365
+ source = 'aggregatedItem.binary';
366
+ stepInfo.source = source;
367
+ stepInfo.binaryKey = binaryKeyFromAggregated;
368
+ stepInfo.foundBinary = true;
369
+ }
370
+ else {
371
+ // Пробуем альтернативные варианты именования
372
+ const alternativeKeys = [
373
+ `${binaryPropertyName}_${listIndex + 1}`, // data_1 для index 0
374
+ `${listIndex}`, // просто индекс
375
+ `file_${listIndex}`, // file_0, file_1
376
+ ];
377
+ for (const altKey of alternativeKeys) {
378
+ if (altKey in aggregatedItem.binary) {
379
+ binaryProperty = aggregatedItem.binary[altKey];
380
+ source = `aggregatedItem.binary (alternative key: ${altKey})`;
381
+ stepInfo.source = source;
382
+ stepInfo.binaryKey = altKey;
383
+ stepInfo.foundBinary = true;
384
+ binaryKeyFromAggregated = altKey;
385
+ break;
386
+ }
387
+ }
328
388
  }
329
389
  }
330
390
  if (!binaryProperty) {
391
+ stepInfo.error = `Binary property not found. Tried: listItem.binary["${binaryPropertyName}"], listItem["${binaryPropertyName}"], aggregatedItem.binary["${binaryPropertyName}${listIndex === 0 ? '' : '_' + listIndex}"]`;
392
+ debugInfo.processingSteps.push(stepInfo);
331
393
  continue; // Пропускаем элементы без binary данных
332
394
  }
333
395
  // Обрабатываем как одиночное значение, так и массив
334
396
  const binaryItems = Array.isArray(binaryProperty) ? binaryProperty : [binaryProperty];
397
+ stepInfo.binaryItemsCount = binaryItems.length;
335
398
  // Добавляем все файлы из этого элемента списка
336
399
  for (let i = 0; i < binaryItems.length; i++) {
337
400
  const binaryItem = binaryItems[i];
338
- if (!binaryItem || !binaryItem.data) {
401
+ if (!binaryItem) {
402
+ stepInfo.error = `Binary item ${i} is null or undefined`;
339
403
  continue; // Пропускаем файлы без данных
340
404
  }
341
- // Получаем buffer из binary данных (всегда base64 в n8n)
342
- const dataBuffer = Buffer.from(binaryItem.data, 'base64');
405
+ // Получаем buffer из binary данных через хелпер n8n
406
+ let dataBuffer;
407
+ let bufferError;
408
+ const fileInfo = {
409
+ listIndex,
410
+ fileIndex: i,
411
+ source,
412
+ binaryKey: binaryKeyFromAggregated || binaryPropertyName,
413
+ fileName: binaryItem.fileName || 'unknown',
414
+ mimeType: binaryItem.mimeType || 'unknown',
415
+ hasData: false,
416
+ fileSize: 0,
417
+ };
418
+ try {
419
+ // Если binary данные из aggregatedItem.binary, используем имя свойства напрямую
420
+ if (binaryKeyFromAggregated) {
421
+ dataBuffer = await this.helpers.getBinaryDataBuffer(0, binaryKeyFromAggregated);
422
+ fileInfo.source = `aggregatedItem.binary["${binaryKeyFromAggregated}"]`;
423
+ }
424
+ else {
425
+ // Используем хелпер getBinaryDataBuffer для правильной обработки binary данных
426
+ // Передаем itemIndex = 0 (aggregatedItem) и IBinaryData объект
427
+ dataBuffer = await this.helpers.getBinaryDataBuffer(0, binaryItem);
428
+ fileInfo.source = source;
429
+ }
430
+ }
431
+ catch (error) {
432
+ bufferError = error.message || String(error);
433
+ fileInfo.bufferError = bufferError;
434
+ // Если не получилось через хелпер, пробуем напрямую (для обратной совместимости)
435
+ if (binaryItem.data) {
436
+ try {
437
+ dataBuffer = Buffer.from(binaryItem.data, 'base64');
438
+ fileInfo.source = `${source} (direct base64)`;
439
+ }
440
+ catch (e) {
441
+ bufferError = `Failed to decode base64: ${e.message}`;
442
+ fileInfo.bufferError = bufferError;
443
+ }
444
+ }
445
+ else {
446
+ fileInfo.bufferError = 'No data field in binaryItem and getBinaryDataBuffer failed';
447
+ debugInfo.processingSteps.push(stepInfo);
448
+ continue; // Пропускаем файлы без данных
449
+ }
450
+ }
451
+ if (!dataBuffer || dataBuffer.length === 0) {
452
+ fileInfo.bufferError = 'Buffer is empty or null';
453
+ debugInfo.processingSteps.push(stepInfo);
454
+ continue; // Пропускаем пустые файлы
455
+ }
456
+ fileInfo.hasData = true;
457
+ fileInfo.fileSize = dataBuffer.length;
343
458
  // Используем fileName из метаданных элемента списка, если доступно
344
459
  const fileName = binaryItem.fileName ||
345
460
  (listItem && typeof listItem === 'object' && 'fileName' in listItem
@@ -348,14 +463,21 @@ class Gotenberg {
348
463
  (listItem && typeof listItem === 'object' && 'fileExtension' in listItem
349
464
  ? `${binaryPropertyName}_${listIndex}.${listItem.fileExtension}`
350
465
  : `${binaryPropertyName}_${listIndex}_${i}.${getFileExtensionFromMimeType(binaryItem.mimeType || '')}`);
466
+ fileInfo.fileName = fileName;
351
467
  formData.append('files', dataBuffer, {
352
468
  filename: fileName,
353
469
  contentType: binaryItem.mimeType || 'application/octet-stream',
354
470
  });
471
+ processedFiles.push(fileInfo);
355
472
  totalFilesCount++;
356
473
  }
474
+ stepInfo.processedFiles = processedFiles.filter((f) => f.listIndex === listIndex).length;
475
+ debugInfo.processingSteps.push(stepInfo);
357
476
  }
358
477
  if (totalFilesCount === 0) {
478
+ // Добавляем информацию о шагах обработки в debugInfo
479
+ debugInfo.processedFiles = processedFiles;
480
+ debugInfo.totalFilesCount = totalFilesCount;
359
481
  // Собираем информацию о структуре элементов списка
360
482
  const sampleItems = listData.slice(0, 3).map((item, index) => {
361
483
  const itemInfo = {
@@ -393,7 +515,7 @@ class Gotenberg {
393
515
  sampleBinaryData: {},
394
516
  };
395
517
  // Показываем структуру первых нескольких binary данных
396
- const binaryKeysToShow = Object.keys(aggregatedItem.binary || {}).slice(0, 3);
518
+ const binaryKeysToShow = Object.keys(aggregatedItem.binary || {}).slice(0, 5);
397
519
  for (const key of binaryKeysToShow) {
398
520
  const binaryData = aggregatedItem.binary[key];
399
521
  if (binaryData) {
@@ -402,6 +524,8 @@ class Gotenberg {
402
524
  dataLength: binaryData.data ? binaryData.data.length : 0,
403
525
  mimeType: binaryData.mimeType,
404
526
  fileName: binaryData.fileName,
527
+ hasId: !!binaryData.id,
528
+ id: binaryData.id,
405
529
  };
406
530
  }
407
531
  }
@@ -428,6 +552,7 @@ class Gotenberg {
428
552
  return keys;
429
553
  })(),
430
554
  },
555
+ debugInfo,
431
556
  });
432
557
  const structureInfo = JSON.stringify(formattedStructure, null, 2);
433
558
  // Проверяем, есть ли binary данные в aggregatedItem.binary
@@ -437,6 +562,7 @@ class Gotenberg {
437
562
  expectedBinaryKeys.push(i === 0 ? binaryPropertyName : `${binaryPropertyName}_${i}`);
438
563
  }
439
564
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), `No valid binary files found in any items. Please make sure binary data exists.\n\n` +
565
+ `Processing steps:\n${JSON.stringify(debugInfo.processingSteps, null, 2)}\n\n` +
440
566
  `Input data structure:\n${structureInfo}\n\n` +
441
567
  `Binary data search locations:\n` +
442
568
  `1. In list item: item.binary["${binaryPropertyName}"] or item["${binaryPropertyName}"]\n` +
@@ -447,7 +573,7 @@ class Gotenberg {
447
573
  `Tips:\n` +
448
574
  `- Check that binaryPropertyName "${binaryPropertyName}" matches the property name\n` +
449
575
  `- Verify that binary data exists in aggregatedItem.binary with keys: "${binaryPropertyName}", "${binaryPropertyName}_1", "${binaryPropertyName}_2", etc.\n` +
450
- `- Make sure binary data has a "data" field with base64 content`);
576
+ `- Make sure binary data has a "data" field with base64 content or uses n8n binary storage`);
451
577
  }
452
578
  // Добавляем опции в зависимости от операции
453
579
  if (operation === 'office' || operation === 'html' || operation === 'markdown') {
@@ -494,13 +620,63 @@ class Gotenberg {
494
620
  }
495
621
  // Отправляем запрос в Gotenberg
496
622
  const url = `${gotenbergUrl.replace(/\/$/, '')}${endpoint}`;
497
- const response = await this.helpers.httpRequest({
498
- method: 'POST',
499
- url,
500
- body: formData,
501
- returnFullResponse: true,
502
- headers: formData.getHeaders(),
503
- });
623
+ let response;
624
+ try {
625
+ response = await this.helpers.httpRequest({
626
+ method: 'POST',
627
+ url,
628
+ body: formData,
629
+ returnFullResponse: true,
630
+ headers: formData.getHeaders(),
631
+ });
632
+ }
633
+ catch (error) {
634
+ // Улучшенная обработка ошибок подключения
635
+ if (error.code === 'ECONNREFUSED') {
636
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Cannot connect to Gotenberg server at ${url}. ` +
637
+ `Connection refused. Please check:\n` +
638
+ `- Is Gotenberg server running?\n` +
639
+ `- Is the URL correct? (current: ${gotenbergUrl})\n` +
640
+ `- Is the port correct? (current endpoint: ${url})\n` +
641
+ `- If using IPv6 (::1), try using 127.0.0.1 or localhost instead`);
642
+ }
643
+ else if (error.code === 'ENOTFOUND' || error.code === 'EAI_AGAIN') {
644
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Cannot resolve Gotenberg server hostname. ` +
645
+ `Please check that the URL is correct: ${gotenbergUrl}`);
646
+ }
647
+ else if (error.code === 'ETIMEDOUT' || error.code === 'ECONNABORTED') {
648
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Connection to Gotenberg server timed out at ${url}. ` +
649
+ `Please check that the server is accessible and not overloaded.`);
650
+ }
651
+ else if (error.response) {
652
+ // Ошибка от сервера (HTTP ошибка)
653
+ const statusCode = error.response.status || error.response.statusCode;
654
+ let errorText;
655
+ if (error.response.data) {
656
+ if (Buffer.isBuffer(error.response.data)) {
657
+ errorText = error.response.data.toString('utf-8');
658
+ }
659
+ else if (typeof error.response.data === 'string') {
660
+ errorText = error.response.data;
661
+ }
662
+ else {
663
+ errorText = JSON.stringify(error.response.data);
664
+ }
665
+ }
666
+ else {
667
+ errorText = error.message || 'Unknown error';
668
+ }
669
+ throw new n8n_workflow_2.NodeApiError(this.getNode(), {
670
+ message: `Gotenberg API error: ${statusCode}`,
671
+ description: errorText,
672
+ });
673
+ }
674
+ else {
675
+ // Другие ошибки
676
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Error connecting to Gotenberg server: ${error.message || String(error)}. ` +
677
+ `URL: ${url}`);
678
+ }
679
+ }
504
680
  // Проверяем статус ответа
505
681
  if (response.statusCode && response.statusCode >= 400) {
506
682
  let errorText;
@@ -551,9 +727,14 @@ class Gotenberg {
551
727
  }
552
728
  // Проверяем, что получили валидный PDF
553
729
  if (pdfBuffer.length < 4 || pdfBuffer.toString('ascii', 0, 4) !== '%PDF') {
554
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid PDF received from Gotenberg. The response does not appear to be a valid PDF file.');
730
+ const pdfHeader = pdfBuffer.length > 0 ? pdfBuffer.toString('ascii', 0, Math.min(20, pdfBuffer.length)) : 'empty';
731
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid PDF received from Gotenberg. The response does not appear to be a valid PDF file.\n` +
732
+ `PDF buffer size: ${pdfBuffer.length} bytes\n` +
733
+ `First 20 bytes: ${pdfHeader}\n` +
734
+ `Processed files info:\n${JSON.stringify(processedFiles, null, 2)}\n` +
735
+ `Debug info:\n${JSON.stringify(debugInfo, null, 2)}`);
555
736
  }
556
- // Создаем один выходной элемент с результатом
737
+ // Создаем один выходной элемент с результатом и отладочной информацией
557
738
  const newItem = {
558
739
  json: {
559
740
  ...aggregatedItem.json,
@@ -561,6 +742,27 @@ class Gotenberg {
561
742
  operation,
562
743
  fileCount: totalFilesCount,
563
744
  processedItems: listData.length,
745
+ debug: {
746
+ processedFiles: processedFiles.map((f) => ({
747
+ source: f.source,
748
+ binaryKey: f.binaryKey,
749
+ fileName: f.fileName,
750
+ fileSize: f.fileSize,
751
+ mimeType: f.mimeType,
752
+ listIndex: f.listIndex,
753
+ fileIndex: f.fileIndex,
754
+ })),
755
+ processingSteps: debugInfo.processingSteps,
756
+ aggregatedBinaryKeys: debugInfo.aggregatedItemBinaryKeys,
757
+ configuration: {
758
+ binaryPropertyName,
759
+ listPropertyName: listPropertyName || '(root array)',
760
+ },
761
+ },
762
+ pdfInfo: {
763
+ size: pdfBuffer.length,
764
+ isValid: pdfBuffer.length >= 4 && pdfBuffer.toString('ascii', 0, 4) === '%PDF',
765
+ },
564
766
  },
565
767
  binary: {
566
768
  ...aggregatedItem.binary,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-sotoros-gotenberg",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "n8n custom node for Gotenberg integration with binary data support",
5
5
  "keywords": [
6
6
  "n8n-community-node",