diff --git a/nodes/SecloreProtect/SecloreProtect.node.ts b/nodes/SecloreProtect/SecloreProtect.node.ts index c703716..2c19533 100644 --- a/nodes/SecloreProtect/SecloreProtect.node.ts +++ b/nodes/SecloreProtect/SecloreProtect.node.ts @@ -1,19 +1,16 @@ import { - IExecuteFunctions, - INodeExecutionData, INodeType, INodeTypeDescription, - NodeOperationError, - NodeOutput, } from 'n8n-workflow'; -import { SecloreDRMFileService } from './Services/SecloreDRMFileService'; +import { protectWithHotFolder } from './operations/protectWithHotFolder'; +import { unprotect } from './operations/unprotect'; export class SecloreProtect implements INodeType { description: INodeTypeDescription = { displayName: 'Seclore Protect', name: 'secloreProtect', - icon: 'file:seclore.svg', + icon: 'file:../../icons/seclore.svg', usableAsTool: true, // TODO: make it false/ don't allow it to be used as a tool group: ['transform'], version: 1, @@ -100,20 +97,6 @@ export class SecloreProtect implements INodeType { }, }, }, - { - displayName: 'Output Binary Property', - name: 'outputBinaryPropertyName', - type: 'string', - default: 'data', - required: true, - description: 'Name of the binary property where the protected file will be stored', - displayOptions: { - show: { - resource: ['protection'], - operation: ['protectWithHotFolder'], - }, - }, - }, { displayName: 'Correlation ID', name: 'correlationId', @@ -143,27 +126,12 @@ export class SecloreProtect implements INodeType { }, // Unprotect operation parameters { - displayName: 'File ID', - name: 'fileId', - type: 'string', - required: true, - default: '', - placeholder: 'e.g., file-12345', - description: 'The ID of the file to unprotect', - displayOptions: { - show: { - resource: ['protection'], - operation: ['unprotect'], - }, - }, - }, - { - displayName: 'Output Binary Property', - name: 'outputBinaryPropertyName', + displayName: 'Input Binary Property', + name: 'binaryPropertyName', type: 'string', default: 'data', required: true, - description: 'Name of the binary property where the unprotected file will be stored', + description: 'Name of the binary property that contains the protected file to unprotect', displayOptions: { show: { resource: ['protection'], @@ -203,219 +171,9 @@ export class SecloreProtect implements INodeType { customOperations = { protection: { - protectWithHotFolder: this.protectWithHotFolder, - unprotect: this.unprotect, + protectWithHotFolder, + unprotect, }, }; - async protectWithHotFolder(this: IExecuteFunctions): Promise { - const items = this.getInputData(); - const returnData: INodeExecutionData[] = []; - - // Get 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 - const fileService = new SecloreDRMFileService(this, baseUrl, tenantId, tenantSecret); - - for (let i = 0; i < items.length; i++) { - console.log('Data'); - console.log(items[i]); - try { - // Get parameters for this item - const hotfolderId = this.getNodeParameter('hotfolderId', i) as string; - const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string; - const outputBinaryPropertyName = this.getNodeParameter( - 'outputBinaryPropertyName', - i, - ) as string; - const correlationId = this.getNodeParameter('correlationId', i) as string; - const retryCount = this.getNodeParameter('retryCount', i) as number; - - // Validate required parameters - if (!hotfolderId) { - throw new NodeOperationError(this.getNode(), 'HotFolder ID is required', { - itemIndex: i, - }); - } - - console.log('assertBinaryData'); - // Get input binary data - const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName); - - console.log('getBinaryDataBuffer'); - const fileBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName); - - console.log('fileBuffer', fileBuffer); - console.log('binaryData.fileName', binaryData.fileName); - - // Upload the file first - const uploadResult = await fileService.uploadFile( - new Uint8Array(fileBuffer), - binaryData.fileName || 'file', - correlationId || undefined, - retryCount, - ); - - console.log('File upload response', uploadResult); - - // Protect the uploaded file with HotFolder - const protectResult = await fileService.protectWithHotFolder( - { - hotfolderId, - fileStorageId: uploadResult.fileStorageId, - }, - correlationId || undefined, - retryCount, - ); - - console.log('File protect response', protectResult); - - // Download the protected file - const protectedFileData = await fileService.downloadFile( - protectResult.fileStorageId, - correlationId || undefined, - retryCount, - ); - - console.log('Protected file data', protectedFileData); - - // Create output binary data - const outputBinaryData = await this.helpers.prepareBinaryData( - Buffer.from(protectedFileData), - binaryData.fileName || 'protected_file', - binaryData.mimeType, - ); - - // Create return item with binary data and metadata - const returnItem: INodeExecutionData = { - json: { - success: true, - originalFileStorageId: uploadResult.fileStorageId, - protectedFileStorageId: protectResult.fileStorageId, - secloreFileId: protectResult.secloreFileId, - hotfolderId, - fileName: binaryData.fileName, - fileSize: protectedFileData.length, - correlationId: correlationId || null, - }, - binary: { - [outputBinaryPropertyName]: outputBinaryData, - }, - }; - - returnData.push(returnItem); - } catch (error) { - // Handle errors gracefully - if (this.continueOnFail()) { - const returnItem: INodeExecutionData = { - json: { - success: false, - error: error.message, - itemIndex: i, - }, - }; - returnData.push(returnItem); - } else { - throw new NodeOperationError(this.getNode(), error.message, { - itemIndex: i, - }); - } - } - } - - return [returnData]; - } - - async unprotect(this: IExecuteFunctions): Promise { - const items = this.getInputData(); - const returnData: INodeExecutionData[] = []; - - // Get 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 - const fileService = new SecloreDRMFileService(this, baseUrl, tenantId, tenantSecret); - - for (let i = 0; i < items.length; i++) { - console.log('Data'); - console.log(items[i]); - try { - // Get parameters for this item - const fileId = this.getNodeParameter('fileId', i) as string; - const outputBinaryPropertyName = this.getNodeParameter( - 'outputBinaryPropertyName', - i, - ) as string; - const correlationId = this.getNodeParameter('correlationId', i) as string; - const retryCount = this.getNodeParameter('retryCount', i) as number; - - // Validate required parameters - if (!fileId) { - throw new NodeOperationError(this.getNode(), 'File ID is required', { - itemIndex: i, - }); - } - - console.log('Unprotecting file with ID:', fileId); - - // Unprotect the file using the file ID - // Note: You'll need to implement unprotectFile in your SecloreDRMFileService - // For now, using downloadFile as placeholder - replace with actual unprotect method - const unprotectedFileData = await fileService.downloadFile( - fileId, - correlationId || undefined, - retryCount, - ); - - console.log('Unprotected file data', unprotectedFileData); - - // Create output binary data - const outputBinaryData = await this.helpers.prepareBinaryData( - Buffer.from(unprotectedFileData), - 'unprotected_file', - 'application/octet-stream', - ); - - // Create return item with binary data and metadata - const returnItem: INodeExecutionData = { - json: { - success: true, - fileId, - fileSize: unprotectedFileData.length, - correlationId: correlationId || null, - }, - binary: { - [outputBinaryPropertyName]: outputBinaryData, - }, - }; - - returnData.push(returnItem); - } catch (error) { - // Handle errors gracefully - if (this.continueOnFail()) { - const returnItem: INodeExecutionData = { - json: { - success: false, - error: error.message, - itemIndex: i, - }, - }; - returnData.push(returnItem); - } else { - throw new NodeOperationError(this.getNode(), error.message, { - itemIndex: i, - }); - } - } - } - - return [returnData]; - } } diff --git a/nodes/SecloreProtect/operations/protectWithHotFolder.ts b/nodes/SecloreProtect/operations/protectWithHotFolder.ts new file mode 100644 index 0000000..ceff624 --- /dev/null +++ b/nodes/SecloreProtect/operations/protectWithHotFolder.ts @@ -0,0 +1,153 @@ +import { + IExecuteFunctions, + INodeExecutionData, + NodeOperationError, + NodeOutput, + LoggerProxy as Logger, +} from 'n8n-workflow'; + +import { SecloreDRMFileService } from '../Services/SecloreDRMFileService'; + +export async function protectWithHotFolder(this: IExecuteFunctions): Promise { + 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 }); + + // Get 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 + 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] }); + try { + // Get parameters for this item + 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 retryCount = this.getNodeParameter('retryCount', i) as number; + + // Validate required parameters + if (!hotfolderId) { + throw new NodeOperationError(this.getNode(), 'HotFolder ID is required', { + itemIndex: i, + }); + } + + Logger.debug('Asserting binary data', { binaryPropertyName, itemIndex: i }); + // Get input binary data + const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName); + + Logger.debug('Getting binary data buffer', { binaryPropertyName, itemIndex: i }); + const fileBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName); + + Logger.debug('Binary data retrieved', { + fileName: binaryData.fileName, + fileSize: fileBuffer.length, + mimeType: binaryData.mimeType, + itemIndex: i + }); + + // Upload the file first + const uploadResult = await fileService.uploadFile( + new Uint8Array(fileBuffer), + binaryData.fileName || 'file', + correlationId || undefined, + retryCount, + ); + + Logger.info('File uploaded successfully', { fileStorageId: uploadResult.fileStorageId, fileName: binaryData.fileName }); + Logger.debug('File upload response', { uploadResult }); + + // Protect the uploaded file with HotFolder + const protectResult = await fileService.protectWithHotFolder( + { + hotfolderId, + fileStorageId: uploadResult.fileStorageId, + }, + correlationId || undefined, + retryCount, + ); + + Logger.info('File protected successfully', { + originalFileStorageId: uploadResult.fileStorageId, + protectedFileStorageId: protectResult.fileStorageId, + secloreFileId: protectResult.secloreFileId, + hotfolderId, + fileName: binaryData.fileName + }); + Logger.debug('File protect response', { protectResult }); + + // Download the protected file + const protectedFileData = await fileService.downloadFile( + protectResult.fileStorageId, + correlationId || undefined, + retryCount, + ); + + Logger.info('Protected file downloaded successfully', { + fileStorageId: protectResult.fileStorageId, + fileSize: protectedFileData.length, + fileName: binaryData.fileName + }); + + // Create output binary data + const outputBinaryData = await this.helpers.prepareBinaryData( + Buffer.from(protectedFileData), + binaryData.fileName || 'protected_file', + binaryData.mimeType, + ); + + // Create return item with binary data and metadata + const returnItem: INodeExecutionData = { + json: { + success: true, + originalFileStorageId: uploadResult.fileStorageId, + protectedFileStorageId: protectResult.fileStorageId, + secloreFileId: protectResult.secloreFileId, + hotfolderId, + fileName: binaryData.fileName, + fileSize: protectedFileData.length, + correlationId: correlationId || null, + }, + binary: { + data: outputBinaryData, + }, + }; + + returnData.push(returnItem); + } catch (error) { + // Handle errors gracefully + Logger.error('Protect with HotFolder operation failed', { error, itemIndex: i }); + if (this.continueOnFail()) { + const returnItem: INodeExecutionData = { + json: { + success: false, + error: error.message, + itemIndex: i, + }, + }; + returnData.push(returnItem); + } else { + throw new NodeOperationError(this.getNode(), error.message, { + itemIndex: i, + }); + } + } + } + + Logger.info('Seclore Protect with HotFolder operation completed', { + processedItems: returnData.length, + successfulItems: returnData.filter(item => item.json.success).length + }); + + return [returnData]; +} diff --git a/nodes/SecloreProtect/operations/unprotect.ts b/nodes/SecloreProtect/operations/unprotect.ts new file mode 100644 index 0000000..a0d4031 --- /dev/null +++ b/nodes/SecloreProtect/operations/unprotect.ts @@ -0,0 +1,197 @@ +import { + IExecuteFunctions, + INodeExecutionData, + NodeOperationError, + NodeOutput, + LoggerProxy as Logger, +} from 'n8n-workflow'; + +import { SecloreDRMFileService } from '../Services/SecloreDRMFileService'; + +/** + * Uploads a file, unprotects it, and downloads the unprotected version + * @param fileService - The SecloreDRMFileService instance + * @param fileBuffer - The file buffer to upload + * @param fileName - The name of the file + * @param correlationId - Optional correlation ID for tracking + * @param retryCount - Number of retries for operations + * @returns Promise containing the unprotected file data and metadata + */ +async function unprotectFile( + context: IExecuteFunctions, + fileService: SecloreDRMFileService, + fileBuffer: Buffer, + fileName: string, + correlationId?: string, + retryCount: number = 3, +): Promise<{ + unprotectedFileData: Uint8Array; + originalFileStorageId: string; + unprotectedFileStorageId: string; + fileName: string; + fileSize: number; +}> { + // Upload the protected file + const uploadResult = await fileService.uploadFile( + new Uint8Array(fileBuffer), + fileName, + correlationId, + retryCount, + ); + + Logger.info('File uploaded successfully', { fileStorageId: uploadResult.fileStorageId, fileName }); + Logger.debug('File upload response', {uploadResult}); + + // Unprotect the uploaded file + Logger.debug('Unprotecting file', { fileStorageId: uploadResult.fileStorageId, fileName }); + const unprotectResult = await fileService.unprotect( + { + fileStorageId: uploadResult.fileStorageId, + }, + correlationId, + retryCount, + ); + + Logger.info('File unprotected successfully', { originalFileStorageId: uploadResult.fileStorageId, unprotectedFileStorageId: unprotectResult.fileStorageId, fileName }); + + Logger.info('Downloading unprotected file', { fileStorageId: unprotectResult.fileStorageId, fileName }); + // Download the unprotected file + const unprotectedFileData = await fileService.downloadFile( + unprotectResult.fileStorageId, + correlationId, + retryCount, + ); + + Logger.info('Unprotected file downloaded successfully', { fileStorageId: unprotectResult.fileStorageId, fileSize: unprotectedFileData.length, fileName }); + + return { + unprotectedFileData, + originalFileStorageId: uploadResult.fileStorageId, + unprotectedFileStorageId: unprotectResult.fileStorageId, + fileName, + fileSize: unprotectedFileData.length, + }; +} + +export async function unprotect(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: INodeExecutionData[] = []; + + // Initialize logger with the current execution context + Logger.init(this.logger); + + Logger.info('Seclore Unprotect operation started', { itemCount: items.length }); + + // Get 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 + 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] }); + try { + // Get parameters for this item + const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string; + const correlationId = this.getNodeParameter('correlationId', i) as string; + const retryCount = this.getNodeParameter('retryCount', i) as number; + + Logger.debug('Asserting binary data', { binaryPropertyName, itemIndex: i }); + // Get input binary data + const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName); + + Logger.debug('Getting binary data buffer', { binaryPropertyName, itemIndex: i }); + const fileBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName); + + Logger.debug('Binary data retrieved', { + fileName: binaryData.fileName, + fileSize: fileBuffer.length, + mimeType: binaryData.mimeType, + itemIndex: i + }); + + // Use the combined upload, unprotect, and download function + try { + Logger.debug('Starting unprotect file operation', { + fileName: binaryData.fileName, + correlationId, + retryCount, + itemIndex: i + }); + + const result = await unprotectFile( + this, + fileService, + fileBuffer, + binaryData.fileName || 'protected_file', + correlationId || undefined, + retryCount, + ); + + Logger.info('Unprotect file operation completed successfully', { + fileName: result.fileName, + originalFileStorageId: result.originalFileStorageId, + unprotectedFileStorageId: result.unprotectedFileStorageId, + fileSize: result.fileSize, + itemIndex: i + }); + + // Create output binary data + const outputBinaryData = await this.helpers.prepareBinaryData( + Buffer.from(result.unprotectedFileData), + binaryData.fileName || 'unprotected_file', + binaryData.mimeType, + ); + + // Create return item with binary data and metadata + const returnItem: INodeExecutionData = { + json: { + success: true, + originalFileStorageId: result.originalFileStorageId, + unprotectedFileStorageId: result.unprotectedFileStorageId, + fileName: result.fileName, + fileSize: result.fileSize, + correlationId: correlationId || null, + }, + binary: { + data: outputBinaryData, + }, + }; + + returnData.push(returnItem); + } catch (unprotectError) { + Logger.error('Unprotect file operation failed with error', { unprotectError }); + + // Re-throw the error to be handled by the outer catch block + throw unprotectError; + } + } catch (error) { + // Handle errors gracefully + Logger.error('Unprotect file operation failed with error', { error }); + if (this.continueOnFail()) { + const returnItem: INodeExecutionData = { + json: { + success: false, + error: error.message, + itemIndex: i, + }, + }; + returnData.push(returnItem); + } else { + throw new NodeOperationError(this.getNode(), error.message, { + itemIndex: i, + }); + } + } + } + + Logger.info('Seclore Unprotect operation completed', { + processedItems: returnData.length, + successfulItems: returnData.filter(item => item.json.success).length + }); + + return [returnData]; +} diff --git a/nodes/SecloreProtect/seclore.svg b/nodes/SecloreProtect/seclore.svg deleted file mode 100644 index 58541ba..0000000 --- a/nodes/SecloreProtect/seclore.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - \ No newline at end of file