diff --git a/nodes/SecloreProtect/SecloreProtect.node.ts b/nodes/SecloreProtect/SecloreProtect.node.ts index 2c19533..57f9116 100644 --- a/nodes/SecloreProtect/SecloreProtect.node.ts +++ b/nodes/SecloreProtect/SecloreProtect.node.ts @@ -97,20 +97,6 @@ export class SecloreProtect implements INodeType { }, }, }, - { - displayName: 'Correlation ID', - name: 'correlationId', - type: 'string', - default: '', - placeholder: 'e.g., req-12345', - description: 'Optional correlation ID for request tracking and logging', - displayOptions: { - show: { - resource: ['protection'], - operation: ['protectWithHotFolder'], - }, - }, - }, { displayName: 'Retry Count', name: 'retryCount', @@ -139,20 +125,6 @@ export class SecloreProtect implements INodeType { }, }, }, - { - displayName: 'Correlation ID', - name: 'correlationId', - type: 'string', - default: '', - placeholder: 'e.g., req-12345', - description: 'Optional correlation ID for request tracking and logging', - displayOptions: { - show: { - resource: ['protection'], - operation: ['unprotect'], - }, - }, - }, { displayName: 'Retry Count', name: 'retryCount', diff --git a/nodes/SecloreProtect/Services/SecloreDRMApiService.ts b/nodes/SecloreProtect/Services/SecloreDRMApiService.ts index 94285b8..75e0b92 100644 --- a/nodes/SecloreProtect/Services/SecloreDRMApiService.ts +++ b/nodes/SecloreProtect/Services/SecloreDRMApiService.ts @@ -13,14 +13,10 @@ import { import { IUnprotectRequest, IUnprotectResponse } from './Interfaces/UnprotectInterfaces'; export class SecloreDRMApiService { - private baseUrl: string; - constructor( private context: IExecuteFunctions, - baseUrl: string, - ) { - this.baseUrl = baseUrl; - } + private baseUrl: string, + ) { } /** * Common error handler for HTTP responses diff --git a/nodes/SecloreProtect/operations/protectWithHotFolder.ts b/nodes/SecloreProtect/operations/protectWithHotFolder.ts index ceff624..9644cf4 100644 --- a/nodes/SecloreProtect/operations/protectWithHotFolder.ts +++ b/nodes/SecloreProtect/operations/protectWithHotFolder.ts @@ -5,34 +5,182 @@ import { NodeOutput, LoggerProxy as Logger, } from 'n8n-workflow'; - +import crypto from 'node:crypto'; import { SecloreDRMFileService } from '../Services/SecloreDRMFileService'; +/** + * Deletes a file from storage with error handling (does not throw errors) + * @param fileService - The SecloreDRMFileService instance + * @param fileStorageId - The file storage ID to delete + * @param correlationId - Optional correlation ID for tracking + * @param retryCount - Number of retries for operations + */ +async function deleteFile( + fileService: SecloreDRMFileService, + fileStorageId: string, + correlationId?: string, + retryCount: number = 3, +): Promise { + const who = "protectWithHotFolder::deleteFile:: "; + try { + Logger.debug(who + 'Attempting to delete file', { fileStorageId, correlationId, retryCount }); + + await fileService.deleteFile( + fileStorageId, + correlationId, + retryCount, + ); + + Logger.debug(who + 'File deleted successfully', { fileStorageId, correlationId }); + } catch (error) { + // Log error but don't throw - this is for cleanup operations + Logger.error(who + 'File deletion failed, continuing operation', { + error, + fileStorageId, + correlationId, + message: 'This is a cleanup operation, continuing despite deletion failure' + }); + } +} + +/** + * Uploads a file, protects it with hot folder, and downloads the protected version + * @param fileService - The SecloreDRMFileService instance + * @param fileBuffer - The file buffer to upload + * @param fileName - The name of the file + * @param hotfolderId - The hot folder ID to use for protection + * @param correlationId - Optional correlation ID for tracking + * @param retryCount - Number of retries for operations + * @returns Promise containing the protected file data and metadata + */ +async function protectFileWithHotFolder( + fileService: SecloreDRMFileService, + fileBuffer: Buffer, + fileName: string, + hotfolderId: string, + correlationId?: string, + retryCount: number = 3, +): Promise<{ + protectedFileData: Uint8Array; + originalFileStorageId: string; + protectedFileStorageId: string; + secloreFileId: string; + fileName: string; + fileSize: number; +}> { + const who = "protectWithHotFolder::protectFileWithHotFolder:: "; + var originalFileStorageId: string = ''; + const protectedFileName = `${fileName}.html`; + try { + Logger.debug(who + 'Starting protect file with hot folder operation', { fileName, fileSize: fileBuffer.length, hotfolderId, correlationId, retryCount }); + + // Upload the file first + Logger.debug(who + 'Uploading file', { fileName, fileSize: fileBuffer.length, correlationId }); + const uploadResult = await fileService.uploadFile( + new Uint8Array(fileBuffer), + fileName, + correlationId, + retryCount, + ); + + Logger.debug(who + 'File uploaded successfully', { fileStorageId: uploadResult.fileStorageId, fileName, correlationId }); + + originalFileStorageId = uploadResult.fileStorageId; + + // Protect the uploaded file with HotFolder + Logger.debug(who + 'Protecting file with hot folder', { fileStorageId: uploadResult.fileStorageId, hotfolderId, fileName, correlationId }); + const protectResult = await fileService.protectWithHotFolder( + { + hotfolderId, + fileStorageId: uploadResult.fileStorageId, + }, + correlationId, + retryCount, + ); + + Logger.debug(who + 'File protected successfully', { + originalFileStorageId: uploadResult.fileStorageId, + protectedFileStorageId: protectResult.fileStorageId, + secloreFileId: protectResult.secloreFileId, + hotfolderId, + fileName, + correlationId + }); + + // Download the protected file + Logger.debug(who + 'Downloading protected file', { fileStorageId: protectResult.fileStorageId, fileName, correlationId }); + const protectedFileData = await fileService.downloadFile( + protectResult.fileStorageId, + correlationId, + retryCount, + ); + + Logger.debug(who + 'Protected file downloaded successfully', { + fileStorageId: protectResult.fileStorageId, + fileSize: protectedFileData.length, + fileName: protectedFileName, + correlationId + }); + + const result = { + protectedFileData, + originalFileStorageId: uploadResult.fileStorageId, + protectedFileStorageId: protectResult.fileStorageId, + secloreFileId: protectResult.secloreFileId, + fileName: protectedFileName, + fileSize: protectedFileData.length, + }; + + Logger.debug(who + 'Protect file with hot folder operation completed successfully', { + fileName: protectedFileName, + originalFileStorageId: result.originalFileStorageId, + protectedFileStorageId: result.protectedFileStorageId, + secloreFileId: result.secloreFileId, + fileSize: result.fileSize, + hotfolderId, + correlationId + }); + + return result; + } catch (error) { + Logger.error(who + 'Protect file with hot folder operation failed', { error, fileName, hotfolderId, correlationId }); + throw error; + } finally { + if (originalFileStorageId !== '') { + await deleteFile(fileService, originalFileStorageId, correlationId, retryCount); + } + } +} + export async function protectWithHotFolder(this: IExecuteFunctions): Promise { + const who = "protectWithHotFolder::protectWithHotFolder:: "; const items = this.getInputData(); const returnData: INodeExecutionData[] = []; // Initialize logger with the current execution context Logger.init(this.logger); - Logger.info('Seclore Protect with HotFolder operation started', { itemCount: items.length }); + Logger.debug(who + 'Seclore Protect with HotFolder operation started', { itemCount: items.length }); // Get credentials + Logger.debug(who + 'Getting credentials', {}); const credentials = await this.getCredentials('secloreProtectApi'); const baseUrl = credentials.baseUrl as string; const tenantId = credentials.tenantId as string; const tenantSecret = credentials.tenantSecret as string; // Initialize the file service + Logger.debug(who + 'Initializing file service', { baseUrl, tenantId }); const fileService = new SecloreDRMFileService(this, baseUrl, tenantId, tenantSecret); for (let i = 0; i < items.length; i++) { - Logger.debug('Processing item', { itemIndex: i, itemData: items[i] }); + Logger.debug(who + 'Processing item', { itemIndex: i }); try { // Get parameters for this item + Logger.debug(who + 'Getting node parameters', { itemIndex: i }); const hotfolderId = this.getNodeParameter('hotfolderId', i) as string; const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string; - const correlationId = this.getNodeParameter('correlationId', i) as string; + const correlationId = crypto.randomUUID().toString(); const retryCount = this.getNodeParameter('retryCount', i) as number; // Validate required parameters @@ -42,92 +190,95 @@ export async function protectWithHotFolder(this: IExecuteFunctions): Promise item.json.success).length }); diff --git a/nodes/SecloreProtect/operations/unprotect.ts b/nodes/SecloreProtect/operations/unprotect.ts index 9c9d5b5..a304028 100644 --- a/nodes/SecloreProtect/operations/unprotect.ts +++ b/nodes/SecloreProtect/operations/unprotect.ts @@ -5,7 +5,7 @@ import { NodeOutput, LoggerProxy as Logger, } from 'n8n-workflow'; - +import crypto from 'node:crypto'; import { SecloreDRMFileService } from '../Services/SecloreDRMFileService'; /** @@ -53,7 +53,6 @@ async function deleteFile( * @returns Promise containing the unprotected file data and metadata */ async function unprotectFile( - context: IExecuteFunctions, fileService: SecloreDRMFileService, fileBuffer: Buffer, fileName: string, @@ -182,7 +181,7 @@ export async function unprotect(this: IExecuteFunctions): Promise { // Get parameters for this item Logger.debug(who + 'Getting node parameters', { itemIndex: i }); const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string; - const correlationId = this.getNodeParameter('correlationId', i) as string; + const correlationId = crypto.randomUUID().toString(); const retryCount = this.getNodeParameter('retryCount', i) as number; Logger.debug(who + 'Asserting binary data', { binaryPropertyName, itemIndex: i }); @@ -211,11 +210,10 @@ export async function unprotect(this: IExecuteFunctions): Promise { }); const result = await unprotectFile( - this, fileService, fileBuffer, binaryData.fileName || 'protected_file', - correlationId || undefined, + correlationId, retryCount, ); @@ -249,7 +247,7 @@ export async function unprotect(this: IExecuteFunctions): Promise { unprotectedFileStorageId: result.unprotectedFileStorageId, fileName: result.fileName, fileSize: result.fileSize, - correlationId: correlationId || null, + correlationId: correlationId, }, binary: { data: outputBinaryData,