diff --git a/.gitignore b/.gitignore
index 2a61503..b64847f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
dist
node_modules
package-lock.json
-.n8n/.env
\ No newline at end of file
+.n8n
\ No newline at end of file
diff --git a/credentials/SecloreProtect.credentials.ts b/credentials/SecloreProtect.credentials.ts
deleted file mode 100644
index f2c60bc..0000000
--- a/credentials/SecloreProtect.credentials.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import {
- ICredentialTestRequest,
- ICredentialType,
- INodeProperties,
-} from 'n8n-workflow';
-
-export class SecloreProtectApi implements ICredentialType {
- name = 'secloreProtectApi';
- displayName = 'Seclore Protect API';
- documentationUrl = 'https://docs.seclore.com/';
- properties: INodeProperties[] = [
- {
- displayName: 'Base URL',
- name: 'baseUrl',
- type: 'string',
- default: 'https://api.seclore.com',
- placeholder: 'https://api.seclore.com',
- description: 'The base URL of your Seclore API instance',
- required: true,
- },
- {
- displayName: 'Tenant ID',
- name: 'tenantId',
- type: 'string',
- default: '',
- placeholder: 'your-tenant-id',
- description: 'Your Seclore tenant ID',
- required: true,
- },
- {
- displayName: 'Tenant Secret',
- name: 'tenantSecret',
- type: 'string',
- typeOptions: {
- password: true,
- },
- default: '',
- description: 'Your Seclore tenant secret',
- required: true,
- },
- ];
-
- // Optional: Add credential test
- test: ICredentialTestRequest = {
- request: {
- baseURL: '={{$credentials.baseUrl}}',
- url: '/seclore/drm/1.0/auth/login',
- method: 'POST',
- body: {
- tenantId: '={{$credentials.tenantId}}',
- tenantSecret: '={{$credentials.tenantSecret}}',
- },
- headers: {
- 'Content-Type': 'application/json',
- },
- },
- };
-}
diff --git a/credentials/SecloreProtectApi.credentials.ts b/credentials/SecloreProtectApi.credentials.ts
new file mode 100644
index 0000000..f034503
--- /dev/null
+++ b/credentials/SecloreProtectApi.credentials.ts
@@ -0,0 +1,58 @@
+import { ICredentialTestRequest, ICredentialType, INodeProperties, Icon } from 'n8n-workflow';
+
+export class SecloreProtectApi implements ICredentialType {
+ name = 'secloreProtectApi';
+ displayName = 'Seclore Protect API';
+ documentationUrl = 'https://docs.seclore.com/';
+ icon: Icon = {
+ light: 'file:../icons/SecloreProtect.light.svg',
+ dark: 'file:../icons/SecloreProtect.dark.svg',
+ };
+ properties: INodeProperties[] = [
+ {
+ displayName: 'Base URL',
+ name: 'baseUrl',
+ type: 'string',
+ default: 'https://api.seclore.com',
+ placeholder: 'https://api.seclore.com',
+ description: 'The base URL of your Seclore API instance',
+ required: true,
+ },
+ {
+ displayName: 'Tenant ID',
+ name: 'tenantId',
+ type: 'string',
+ default: '',
+ placeholder: 'your-tenant-id',
+ description: 'Your Seclore tenant ID',
+ required: true,
+ },
+ {
+ displayName: 'Tenant Secret',
+ name: 'tenantSecret',
+ type: 'string',
+ typeOptions: {
+ password: true,
+ },
+ default: '',
+ description: 'Your Seclore tenant secret',
+ required: true,
+ },
+ ];
+
+ // Optional: Add credential test
+ test: ICredentialTestRequest = {
+ request: {
+ baseURL: '={{$credentials.baseUrl}}',
+ url: '/seclore/drm/1.0/auth/login',
+ method: 'POST',
+ body: {
+ tenantId: '={{$credentials.tenantId}}',
+ tenantSecret: '={{$credentials.tenantSecret}}',
+ },
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ },
+ };
+}
diff --git a/nodes/SecloreProtect/SecloreProtect.svg b/icons/SecloreProtect.dark.svg
similarity index 100%
rename from nodes/SecloreProtect/SecloreProtect.svg
rename to icons/SecloreProtect.dark.svg
diff --git a/icons/SecloreProtect.light.svg b/icons/SecloreProtect.light.svg
new file mode 100644
index 0000000..58541ba
--- /dev/null
+++ b/icons/SecloreProtect.light.svg
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/icons/github.dark.svg b/icons/github.dark.svg
deleted file mode 100644
index 0366b08..0000000
--- a/icons/github.dark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/icons/github.svg b/icons/github.svg
deleted file mode 100644
index fe1ac05..0000000
--- a/icons/github.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/nodes/SecloreProtect/SecloreProtect.node.ts b/nodes/SecloreProtect/SecloreProtect.node.ts
index 5ea9301..e51750b 100644
--- a/nodes/SecloreProtect/SecloreProtect.node.ts
+++ b/nodes/SecloreProtect/SecloreProtect.node.ts
@@ -12,7 +12,8 @@ export class SecloreProtect implements INodeType {
description: INodeTypeDescription = {
displayName: 'Seclore Protect',
name: 'secloreProtect',
- icon: 'file:SecloreProtect.svg',
+ icon: 'file:../../icons/SecloreProtect.light.svg',
+ usableAsTool: true, // TODO: make it false/ don't allow it to be used as a tool
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"]}}',
@@ -39,7 +40,7 @@ export class SecloreProtect implements INodeType {
name: 'Protect File with HotFolder',
value: 'protectWithHotFolder',
description: 'Protect a file using HotFolder ID configuration',
- action: 'Protect file with HotFolder',
+ action: 'Protect file with hotfolder',
},
],
default: 'protectWithHotFolder',
@@ -129,6 +130,8 @@ export class SecloreProtect implements INodeType {
const operation = this.getNodeParameter('operation', 0) as string;
for (let i = 0; i < items.length; i++) {
+ console.log('Data');
+ console.log(items[i]);
try {
if (operation === 'protectWithHotFolder') {
// Get parameters for this item
@@ -148,10 +151,16 @@ export class SecloreProtect implements INodeType {
});
}
+ 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),
@@ -160,6 +169,8 @@ export class SecloreProtect implements INodeType {
retryCount,
);
+ console.log('File upload response', uploadResult);
+
// Protect the uploaded file with HotFolder
const protectResult = await fileService.protectWithHotFolder(
{
@@ -170,6 +181,8 @@ export class SecloreProtect implements INodeType {
retryCount,
);
+ console.log('File protect response', protectResult);
+
// Download the protected file
const protectedFileData = await fileService.downloadFile(
protectResult.fileStorageId,
@@ -177,6 +190,8 @@ export class SecloreProtect implements INodeType {
retryCount,
);
+ console.log('Protected file data', protectedFileData);
+
// Create output binary data
const outputBinaryData = await this.helpers.prepareBinaryData(
Buffer.from(protectedFileData),
diff --git a/nodes/SecloreProtect/Services/SecloreDRMApiService.ts b/nodes/SecloreProtect/Services/SecloreDRMApiService.ts
index 9cfa37d..f8713d7 100644
--- a/nodes/SecloreProtect/Services/SecloreDRMApiService.ts
+++ b/nodes/SecloreProtect/Services/SecloreDRMApiService.ts
@@ -1,367 +1,387 @@
-import { IExecuteFunctions, IHttpRequestOptions } from 'n8n-workflow';
-import { ILoginRequest, ILoginResponse, IRefreshTokenRequest } from './Interfaces/LoginInterfaces';
+import { IExecuteFunctions, IHttpRequestOptions, NodeApiError } from 'n8n-workflow';
import { IErrorResponse } from './Interfaces/ErrorInterfaces';
-import { IProtectWithExternalRefIdRequest, IProtectWithExternalRefIdResponse, IProtectWithFileIdRequest, IProtectWithFileIdResponse, IProtectWithHotFolderRequest, IProtectWithHotFolderResponse } from './Interfaces/ProtectInterfaces';
-import FormData from 'form-data';
-import { IUnprotectRequest, IUnprotectResponse } from './Interfaces/UnprotectInterfaces';
import { IFileUploadResponse } from './Interfaces/FileStorageInterfaces';
+import { ILoginRequest, ILoginResponse, IRefreshTokenRequest } from './Interfaces/LoginInterfaces';
+import {
+ IProtectWithExternalRefIdRequest,
+ IProtectWithExternalRefIdResponse,
+ IProtectWithFileIdRequest,
+ IProtectWithFileIdResponse,
+ IProtectWithHotFolderRequest,
+ IProtectWithHotFolderResponse,
+} from './Interfaces/ProtectInterfaces';
+import { IUnprotectRequest, IUnprotectResponse } from './Interfaces/UnprotectInterfaces';
export class SecloreDRMApiService {
- private baseUrl: string;
+ private baseUrl: string;
- constructor(private context: IExecuteFunctions, baseUrl: string) {
- this.baseUrl = baseUrl;
- }
+ constructor(
+ private context: IExecuteFunctions,
+ baseUrl: string,
+ ) {
+ this.baseUrl = baseUrl;
+ }
- /**
- * Common error handler for HTTP responses
- * @param error - The error object from httpRequest
- * @param customMessages - Optional custom error messages for specific status codes
- */
- private handleHttpError(error: any, customMessages?: { [statusCode: number]: string }): never {
- const statusCode = error.statusCode;
- const errorResponse = error.response?.body as IErrorResponse;
-
- if (customMessages && customMessages[statusCode]) {
- throw new Error(`${customMessages[statusCode]}: ${errorResponse?.errorMessage || 'Unknown error'}`);
- }
-
- // Default error handling
- switch (statusCode) {
- case 400:
- throw new Error(`Bad Request: ${errorResponse?.errorMessage || 'Invalid request data'}`);
- case 401:
- throw new Error(`Unauthorized: ${errorResponse?.errorMessage || 'Invalid credentials'}`);
- case 413:
- throw new Error(`Payload Too Large: ${errorResponse?.errorMessage || 'File size exceeds limit'}`);
- case 500:
- throw new Error(`Server Error: ${errorResponse?.errorMessage || 'Internal server error'}`);
- default:
- throw error;
- }
- }
+ /**
+ * Common error handler for HTTP responses
+ * @param error - The error object from httpRequest
+ * @param customMessages - Optional custom error messages for specific status codes
+ */
+ private handleHttpError(
+ error: NodeApiError,
+ customMessages?: { [statusCode: number]: string },
+ ): never {
+ const statusCode: number = parseInt(error.httpCode ?? '0');
+ const errorResponse = error.errorResponse as unknown as IErrorResponse;
- /**
- * Login Endpoint to generate Access Token and Refresh Token for JWT Authorization.
- * Upon successful login, all the existing previous tokens for that tenant will be invalidated.
- *
- * @param tenantId - The tenant ID
- * @param tenantSecret - The tenant secret
- * @param correlationId - Optional request ID for logging purpose
- * @returns Promise - Access token and refresh token
- * @throws Error on authentication failure or server error
- */
- async login(tenantId: string, tenantSecret: string, correlationId?: string): Promise {
- const requestBody: ILoginRequest = {
- tenantId,
- tenantSecret,
- };
+ if (customMessages && customMessages[statusCode]) {
+ throw new Error(
+ `${customMessages[statusCode]}: ${errorResponse?.errorMessage || 'Unknown error'}`,
+ );
+ }
- const headers: { [key: string]: string } = {
- 'Content-Type': 'application/json',
- };
+ // Default error handling
+ switch (statusCode) {
+ case 400:
+ throw new Error(`Bad Request: ${errorResponse?.errorMessage || 'Invalid request data'}`);
+ case 401:
+ throw new Error(`Unauthorized: ${errorResponse?.errorMessage || 'Invalid credentials'}`);
+ case 413:
+ throw new Error(
+ `Payload Too Large: ${errorResponse?.errorMessage || 'File size exceeds limit'}`,
+ );
+ case 500:
+ throw new Error(`Server Error: ${errorResponse?.errorMessage || 'Internal server error'}`);
+ default:
+ throw error;
+ }
+ }
- // Add correlation ID if provided
- if (correlationId) {
- headers['X-SECLORE-CORRELATION-ID'] = correlationId;
- }
+ /**
+ * Login Endpoint to generate Access Token and Refresh Token for JWT Authorization.
+ * Upon successful login, all the existing previous tokens for that tenant will be invalidated.
+ *
+ * @param tenantId - The tenant ID
+ * @param tenantSecret - The tenant secret
+ * @param correlationId - Optional request ID for logging purpose
+ * @returns Promise - Access token and refresh token
+ * @throws Error on authentication failure or server error
+ */
+ async login(
+ tenantId: string,
+ tenantSecret: string,
+ correlationId?: string,
+ ): Promise {
+ const requestBody: ILoginRequest = {
+ tenantId,
+ tenantSecret,
+ };
- const options: IHttpRequestOptions = {
- method: 'POST',
- url: `${this.baseUrl}/seclore/drm/1.0/auth/login`,
- headers,
- body: requestBody,
- json: true,
- };
+ const headers: { [key: string]: string } = {
+ 'Content-Type': 'application/json',
+ };
- try {
- const response = await this.context.helpers.httpRequest(options);
- return response as ILoginResponse;
- } catch (error: any) {
- this.handleHttpError(error);
- }
- }
+ // Add correlation ID if provided
+ if (correlationId) {
+ headers['X-SECLORE-CORRELATION-ID'] = correlationId;
+ }
- /**
- * Endpoint for generating new Access Token and Refresh Token using an existing valid Refresh Token.
- * Upon successful response, all the previous existing Access Tokens and Refresh Tokens of that tenant will be invalidated.
- *
- * @param refreshToken - The existing valid refresh token
- * @param correlationId - Optional request ID for logging purpose
- * @returns Promise - New access token and refresh token
- * @throws Error on authentication failure or server error
- */
- async refreshToken(refreshToken: string, correlationId?: string): Promise {
- const requestBody: IRefreshTokenRequest = {
- refreshToken,
- };
+ const options: IHttpRequestOptions = {
+ method: 'POST',
+ url: `${this.baseUrl}/seclore/drm/1.0/auth/login`,
+ headers,
+ body: requestBody,
+ json: true,
+ };
- const headers: { [key: string]: string } = {
- 'Content-Type': 'application/json',
- };
+ try {
+ const response = await this.context.helpers.httpRequest(options);
+ return response as ILoginResponse;
+ } catch (error: unknown) {
+ this.handleHttpError(error as NodeApiError);
+ }
+ }
- // Add correlation ID if provided
- if (correlationId) {
- headers['X-SECLORE-CORRELATION-ID'] = correlationId;
- }
+ /**
+ * Endpoint for generating new Access Token and Refresh Token using an existing valid Refresh Token.
+ * Upon successful response, all the previous existing Access Tokens and Refresh Tokens of that tenant will be invalidated.
+ *
+ * @param refreshToken - The existing valid refresh token
+ * @param correlationId - Optional request ID for logging purpose
+ * @returns Promise - New access token and refresh token
+ * @throws Error on authentication failure or server error
+ */
+ async refreshToken(refreshToken: string, correlationId?: string): Promise {
+ const requestBody: IRefreshTokenRequest = {
+ refreshToken,
+ };
- const options: IHttpRequestOptions = {
- method: 'POST',
- url: `${this.baseUrl}/seclore/drm/1.0/auth/refresh`,
- headers,
- body: requestBody,
- json: true,
- };
+ const headers: { [key: string]: string } = {
+ 'Content-Type': 'application/json',
+ };
- try {
- const response = await this.context.helpers.httpRequest(options);
- return response as ILoginResponse;
- } catch (error: any) {
- this.handleHttpError(error, { 401: 'Unauthorized' });
- }
- }
+ // Add correlation ID if provided
+ if (correlationId) {
+ headers['X-SECLORE-CORRELATION-ID'] = correlationId;
+ }
- /**
- * Protect file using external identifier of protected File and HotFolder with PS configured against the logged in Tenant in application.
- *
- * @param protectRequest - The protection request details
- * @param accessToken - JWT access token for authorization
- * @param correlationId - Optional request ID for logging purpose
- * @returns Promise - File storage ID and Seclore file ID
- * @throws Error on bad request, authentication failure or server error
- */
- async protectWithExternalRefId(
- protectRequest: IProtectWithExternalRefIdRequest,
- accessToken: string,
- correlationId?: string
- ): Promise {
- const headers: { [key: string]: string } = {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${accessToken}`,
- };
+ const options: IHttpRequestOptions = {
+ method: 'POST',
+ url: `${this.baseUrl}/seclore/drm/1.0/auth/refresh`,
+ headers,
+ body: requestBody,
+ json: true,
+ };
- // Add correlation ID if provided
- if (correlationId) {
- headers['X-SECLORE-CORRELATION-ID'] = correlationId;
- }
+ try {
+ const response = await this.context.helpers.httpRequest(options);
+ return response as ILoginResponse;
+ } catch (error: unknown) {
+ this.handleHttpError(error as NodeApiError, { 401: 'Unauthorized' });
+ }
+ }
- const options: IHttpRequestOptions = {
- method: 'POST',
- url: `${this.baseUrl}/seclore/drm/1.0/protect/externalref`,
- headers,
- body: protectRequest,
- json: true,
- };
+ /**
+ * Protect file using external identifier of protected File and HotFolder with PS configured against the logged in Tenant in application.
+ *
+ * @param protectRequest - The protection request details
+ * @param accessToken - JWT access token for authorization
+ * @param correlationId - Optional request ID for logging purpose
+ * @returns Promise - File storage ID and Seclore file ID
+ * @throws Error on bad request, authentication failure or server error
+ */
+ async protectWithExternalRefId(
+ protectRequest: IProtectWithExternalRefIdRequest,
+ accessToken: string,
+ correlationId?: string,
+ ): Promise {
+ const headers: { [key: string]: string } = {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${accessToken}`,
+ };
- try {
- const response = await this.context.helpers.httpRequest(options);
- return response as IProtectWithExternalRefIdResponse;
- } catch (error: any) {
- this.handleHttpError(error);
- }
- }
+ // Add correlation ID if provided
+ if (correlationId) {
+ headers['X-SECLORE-CORRELATION-ID'] = correlationId;
+ }
- /**
- * Protects file using File ID of already protected file with PS configured against the logged in Tenant in application.
- *
- * @param protectRequest - The protection request details with existing protected file ID
- * @param accessToken - JWT access token for authorization
- * @param correlationId - Optional request ID for logging purpose
- * @returns Promise - File storage ID and Seclore file ID
- * @throws Error on bad request, authentication failure or server error
- */
- async protectWithFileId(
- protectRequest: IProtectWithFileIdRequest,
- accessToken: string,
- correlationId?: string
- ): Promise {
- const headers: { [key: string]: string } = {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${accessToken}`,
- };
+ const options: IHttpRequestOptions = {
+ method: 'POST',
+ url: `${this.baseUrl}/seclore/drm/1.0/protect/externalref`,
+ headers,
+ body: protectRequest,
+ json: true,
+ };
- // Add correlation ID if provided
- if (correlationId) {
- headers['X-SECLORE-CORRELATION-ID'] = correlationId;
- }
+ try {
+ const response = await this.context.helpers.httpRequest(options);
+ return response as IProtectWithExternalRefIdResponse;
+ } catch (error: unknown) {
+ this.handleHttpError(error as NodeApiError);
+ }
+ }
- const options: IHttpRequestOptions = {
- method: 'POST',
- url: `${this.baseUrl}/seclore/drm/1.0/protect/fileid`,
- headers,
- body: protectRequest,
- json: true,
- };
+ /**
+ * Protects file using File ID of already protected file with PS configured against the logged in Tenant in application.
+ *
+ * @param protectRequest - The protection request details with existing protected file ID
+ * @param accessToken - JWT access token for authorization
+ * @param correlationId - Optional request ID for logging purpose
+ * @returns Promise - File storage ID and Seclore file ID
+ * @throws Error on bad request, authentication failure or server error
+ */
+ async protectWithFileId(
+ protectRequest: IProtectWithFileIdRequest,
+ accessToken: string,
+ correlationId?: string,
+ ): Promise {
+ const headers: { [key: string]: string } = {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${accessToken}`,
+ };
- try {
- const response = await this.context.helpers.httpRequest(options);
- return response as IProtectWithFileIdResponse;
- } catch (error: any) {
- this.handleHttpError(error);
- }
- }
+ // Add correlation ID if provided
+ if (correlationId) {
+ headers['X-SECLORE-CORRELATION-ID'] = correlationId;
+ }
- /**
- * Protects file using HotFolder ID with PS configured against the logged in Tenant in application.
- *
- * @param protectRequest - The protection request details with hotfolder ID
- * @param accessToken - JWT access token for authorization
- * @param correlationId - Optional request ID for logging purpose
- * @returns Promise - File storage ID and Seclore file ID
- * @throws Error on bad request, authentication failure or server error
- */
- async protectWithHotFolder(
- protectRequest: IProtectWithHotFolderRequest,
- accessToken: string,
- correlationId?: string
- ): Promise {
- const headers: { [key: string]: string } = {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${accessToken}`,
- };
+ const options: IHttpRequestOptions = {
+ method: 'POST',
+ url: `${this.baseUrl}/seclore/drm/1.0/protect/fileid`,
+ headers,
+ body: protectRequest,
+ json: true,
+ };
- // Add correlation ID if provided
- if (correlationId) {
- headers['X-SECLORE-CORRELATION-ID'] = correlationId;
- }
+ try {
+ const response = await this.context.helpers.httpRequest(options);
+ return response as IProtectWithFileIdResponse;
+ } catch (error: unknown) {
+ this.handleHttpError(error as NodeApiError);
+ }
+ }
- const options: IHttpRequestOptions = {
- method: 'POST',
- url: `${this.baseUrl}/seclore/drm/1.0/protect/hf`,
- headers,
- body: protectRequest,
- json: true,
- };
+ /**
+ * Protects file using HotFolder ID with PS configured against the logged in Tenant in application.
+ *
+ * @param protectRequest - The protection request details with hotfolder ID
+ * @param accessToken - JWT access token for authorization
+ * @param correlationId - Optional request ID for logging purpose
+ * @returns Promise - File storage ID and Seclore file ID
+ * @throws Error on bad request, authentication failure or server error
+ */
+ async protectWithHotFolder(
+ protectRequest: IProtectWithHotFolderRequest,
+ accessToken: string,
+ correlationId?: string,
+ ): Promise {
+ const headers: { [key: string]: string } = {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${accessToken}`,
+ };
- try {
- const response = await this.context.helpers.httpRequest(options);
- return response as IProtectWithHotFolderResponse;
- } catch (error: any) {
- this.handleHttpError(error);
- }
- }
+ // Add correlation ID if provided
+ if (correlationId) {
+ headers['X-SECLORE-CORRELATION-ID'] = correlationId;
+ }
- /**
- * Unprotects file with PS configured against the logged in Tenant in application.
- *
- * @param unprotectRequest - The unprotect request details with file storage ID
- * @param accessToken - JWT access token for authorization
- * @param correlationId - Optional request ID for logging purpose
- * @returns Promise - File storage ID of unprotected file
- * @throws Error on bad request, authentication failure or server error
- */
- async unprotect(
- unprotectRequest: IUnprotectRequest,
- accessToken: string,
- correlationId?: string
- ): Promise {
- const headers: { [key: string]: string } = {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${accessToken}`,
- };
+ const options: IHttpRequestOptions = {
+ method: 'POST',
+ url: `${this.baseUrl}/seclore/drm/1.0/protect/hf`,
+ headers,
+ body: protectRequest,
+ json: true,
+ };
- // Add correlation ID if provided
- if (correlationId) {
- headers['X-SECLORE-CORRELATION-ID'] = correlationId;
- }
+ try {
+ const response = await this.context.helpers.httpRequest(options);
+ return response as IProtectWithHotFolderResponse;
+ } catch (error: unknown) {
+ this.handleHttpError(error as NodeApiError);
+ }
+ }
- const options: IHttpRequestOptions = {
- method: 'POST',
- url: `${this.baseUrl}/seclore/drm/1.0/unprotect`,
- headers,
- body: unprotectRequest,
- json: true,
- };
+ /**
+ * Unprotects file with PS configured against the logged in Tenant in application.
+ *
+ * @param unprotectRequest - The unprotect request details with file storage ID
+ * @param accessToken - JWT access token for authorization
+ * @param correlationId - Optional request ID for logging purpose
+ * @returns Promise - File storage ID of unprotected file
+ * @throws Error on bad request, authentication failure or server error
+ */
+ async unprotect(
+ unprotectRequest: IUnprotectRequest,
+ accessToken: string,
+ correlationId?: string,
+ ): Promise {
+ const headers: { [key: string]: string } = {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${accessToken}`,
+ };
- try {
- const response = await this.context.helpers.httpRequest(options);
- return response as IUnprotectResponse;
- } catch (error: any) {
- this.handleHttpError(error);
- }
- }
+ // Add correlation ID if provided
+ if (correlationId) {
+ headers['X-SECLORE-CORRELATION-ID'] = correlationId;
+ }
- /**
- * Adds a new file to the file storage for currently logged in Tenant.
- *
- * @param fileBuffer - The file buffer data
- * @param fileName - The name of the file
- * @param accessToken - JWT access token for authorization
- * @param correlationId - Optional request ID for logging purpose
- * @returns Promise - File storage details including file ID and metadata
- * @throws Error on authentication failure, payload too large, or server error
- */
- async uploadFile(
- fileBuffer: Uint8Array,
- fileName: string,
- accessToken: string,
- correlationId?: string
- ): Promise {
- const headers: { [key: string]: string } = {
- 'Authorization': `Bearer ${accessToken}`,
- };
+ const options: IHttpRequestOptions = {
+ method: 'POST',
+ url: `${this.baseUrl}/seclore/drm/1.0/unprotect`,
+ headers,
+ body: unprotectRequest,
+ json: true,
+ };
- // Add correlation ID if provided
- if (correlationId) {
- headers['X-SECLORE-CORRELATION-ID'] = correlationId;
- }
+ try {
+ const response = await this.context.helpers.httpRequest(options);
+ return response as IUnprotectResponse;
+ } catch (error: unknown) {
+ this.handleHttpError(error as NodeApiError);
+ }
+ }
- // Create FormData for multipart/form-data upload
- const formData = new FormData();
- formData.append('file', fileBuffer, fileName);
+ /**
+ * Adds a new file to the file storage for currently logged in Tenant.
+ *
+ * @param fileBuffer - The file buffer data
+ * @param fileName - The name of the file
+ * @param accessToken - JWT access token for authorization
+ * @param correlationId - Optional request ID for logging purpose
+ * @returns Promise - File storage details including file ID and metadata
+ * @throws Error on authentication failure, payload too large, or server error
+ */
+ async uploadFile(
+ fileBuffer: Uint8Array,
+ fileName: string,
+ accessToken: string,
+ correlationId?: string,
+ ): Promise {
+ const headers: { [key: string]: string } = {
+ Authorization: `Bearer ${accessToken}`,
+ };
- const options: IHttpRequestOptions = {
- method: 'POST',
- url: `${this.baseUrl}/seclore/drm/filestorage/1.0/upload`,
- headers,
- body: formData,
- };
+ // Add correlation ID if provided
+ if (correlationId) {
+ headers['X-SECLORE-CORRELATION-ID'] = correlationId;
+ }
- try {
- const response = await this.context.helpers.httpRequest(options);
- return response as IFileUploadResponse;
- } catch (error: any) {
- this.handleHttpError(error);
- }
- }
+ // Create FormData for multipart/form-data upload
+ const formData = new FormData();
+ const file = new Blob([fileBuffer], { type: 'application/octet-stream' });
+ formData.append('file', file, fileName);
- /**
- * Downloads file with fileStorageId from file storage of currently logged in Tenant.
- * NOTE: Files whose fileStorageId has 'DL_' prefix will be deleted from the file storage after download.
- *
- * @param fileStorageId - Storage ID of the file to be retrieved
- * @param accessToken - JWT access token for authorization
- * @param correlationId - Optional request ID for logging purpose
- * @returns Promise - The downloaded file data
- * @throws Error on authentication failure or server error
- */
- async downloadFile(
- fileStorageId: string,
- accessToken: string,
- correlationId?: string
- ): Promise {
- const headers: { [key: string]: string } = {
- 'Authorization': `Bearer ${accessToken}`,
- };
+ const options: IHttpRequestOptions = {
+ method: 'POST',
+ url: `${this.baseUrl}/seclore/drm/filestorage/1.0/upload`,
+ headers,
+ body: formData,
+ };
- // Add correlation ID if provided
- if (correlationId) {
- headers['X-SECLORE-CORRELATION-ID'] = correlationId;
- }
+ try {
+ const response = await this.context.helpers.httpRequest(options);
+ return response as IFileUploadResponse;
+ } catch (error: unknown) {
+ this.handleHttpError(error as NodeApiError);
+ }
+ }
- const options: IHttpRequestOptions = {
- method: 'GET',
- url: `${this.baseUrl}/seclore/drm/filestorage/1.0/download/${fileStorageId}`,
- headers,
- encoding: 'arraybuffer',
- };
+ /**
+ * Downloads file with fileStorageId from file storage of currently logged in Tenant.
+ * NOTE: Files whose fileStorageId has 'DL_' prefix will be deleted from the file storage after download.
+ *
+ * @param fileStorageId - Storage ID of the file to be retrieved
+ * @param accessToken - JWT access token for authorization
+ * @param correlationId - Optional request ID for logging purpose
+ * @returns Promise - The downloaded file data
+ * @throws Error on authentication failure or server error
+ */
+ async downloadFile(
+ fileStorageId: string,
+ accessToken: string,
+ correlationId?: string,
+ ): Promise {
+ const headers: { [key: string]: string } = {
+ Authorization: `Bearer ${accessToken}`,
+ };
- try {
- const response = await this.context.helpers.httpRequest(options);
- return new Uint8Array(response as ArrayBuffer);
- } catch (error: any) {
- this.handleHttpError(error);
- }
- }
+ // Add correlation ID if provided
+ if (correlationId) {
+ headers['X-SECLORE-CORRELATION-ID'] = correlationId;
+ }
+ const options: IHttpRequestOptions = {
+ method: 'GET',
+ url: `${this.baseUrl}/seclore/drm/filestorage/1.0/download/${fileStorageId}`,
+ headers,
+ encoding: 'arraybuffer',
+ };
+
+ try {
+ const response = await this.context.helpers.httpRequest(options);
+ return new Uint8Array(response as ArrayBuffer);
+ } catch (error: unknown) {
+ this.handleHttpError(error as NodeApiError);
+ }
+ }
}
diff --git a/nodes/SecloreProtect/Services/SecloreDRMFileService.ts b/nodes/SecloreProtect/Services/SecloreDRMFileService.ts
index ae078cc..7a8192d 100644
--- a/nodes/SecloreProtect/Services/SecloreDRMFileService.ts
+++ b/nodes/SecloreProtect/Services/SecloreDRMFileService.ts
@@ -1,298 +1,303 @@
import { IExecuteFunctions } from 'n8n-workflow';
-import { SecloreDRMApiService } from './SecloreDRMApiService';
-import { IProtectWithExternalRefIdRequest, IProtectWithExternalRefIdResponse, IProtectWithFileIdRequest, IProtectWithFileIdResponse, IProtectWithHotFolderRequest, IProtectWithHotFolderResponse } from './Interfaces/ProtectInterfaces';
-import { IUnprotectRequest, IUnprotectResponse } from './Interfaces/UnprotectInterfaces';
import { IFileUploadResponse } from './Interfaces/FileStorageInterfaces';
+import {
+ IProtectWithExternalRefIdRequest,
+ IProtectWithExternalRefIdResponse,
+ IProtectWithFileIdRequest,
+ IProtectWithFileIdResponse,
+ IProtectWithHotFolderRequest,
+ IProtectWithHotFolderResponse,
+} from './Interfaces/ProtectInterfaces';
+import { IUnprotectRequest, IUnprotectResponse } from './Interfaces/UnprotectInterfaces';
+import { SecloreDRMApiService } from './SecloreDRMApiService';
export class SecloreDRMFileService {
- private apiService: SecloreDRMApiService;
- private tenantId: string;
- private tenantSecret: string;
- private accessToken?: string;
- private refreshToken?: string;
- private tokenExpiry?: Date;
- private refreshPromise?: Promise;
- private loginPromise?: Promise;
+ private apiService: SecloreDRMApiService;
+ private tenantId: string;
+ private tenantSecret: string;
+ private accessToken?: string;
+ private refreshToken?: string;
+ private tokenExpiry?: Date;
+ private refreshPromise?: Promise;
+ private loginPromise?: Promise;
- constructor(
- context: IExecuteFunctions,
- baseUrl: string,
- tenantId: string,
- tenantSecret: string,
- private defaultRetryCount: number = 3
- ) {
- this.apiService = new SecloreDRMApiService(context, baseUrl);
- this.tenantId = tenantId;
- this.tenantSecret = tenantSecret;
- }
+ constructor(
+ context: IExecuteFunctions,
+ baseUrl: string,
+ tenantId: string,
+ tenantSecret: string,
+ private defaultRetryCount: number = 3,
+ ) {
+ this.apiService = new SecloreDRMApiService(context, baseUrl);
+ this.tenantId = tenantId;
+ this.tenantSecret = tenantSecret;
+ }
- /**
- * Ensures we have a valid access token, logging in if necessary
- * @param correlationId - Optional correlation ID for logging
- */
- private async ensureAuthenticated(correlationId?: string): Promise {
- // If we don't have a token or it's expired, login
- if (!this.accessToken || (this.tokenExpiry && new Date() >= this.tokenExpiry)) {
- // If there's already a login in progress, wait for it
- if (this.loginPromise) {
- await this.loginPromise;
- return;
- }
-
- // Start login and store the promise
- this.loginPromise = this.login(correlationId);
- try {
- await this.loginPromise;
- } finally {
- this.loginPromise = undefined;
- }
- }
- }
+ /**
+ * Ensures we have a valid access token, logging in if necessary
+ * @param correlationId - Optional correlation ID for logging
+ */
+ private async ensureAuthenticated(correlationId?: string): Promise {
+ // If we don't have a token or it's expired, login
+ if (!this.accessToken || (this.tokenExpiry && new Date() >= this.tokenExpiry)) {
+ // If there's already a login in progress, wait for it
+ if (this.loginPromise) {
+ await this.loginPromise;
+ return;
+ }
- /**
- * Performs login and stores tokens
- * @param correlationId - Optional correlation ID for logging
- */
- private async login(correlationId?: string): Promise {
- try {
- const loginResponse = await this.apiService.login(
- this.tenantId,
- this.tenantSecret,
- correlationId
- );
-
- this.accessToken = loginResponse.accessToken;
- this.refreshToken = loginResponse.refreshToken;
-
- // Set token expiry to 50 minutes from now (assuming 1 hour token life)
- this.tokenExpiry = new Date(Date.now() + 50 * 60 * 1000);
- } catch (error) {
- this.clearTokens();
- throw error;
- }
- }
+ // Start login and store the promise
+ this.loginPromise = this.login(correlationId);
+ try {
+ await this.loginPromise;
+ } finally {
+ this.loginPromise = undefined;
+ }
+ }
+ }
- /**
- * Attempts to refresh the access token with concurrency protection
- * @param correlationId - Optional correlation ID for logging
- */
- private async refreshAccessToken(correlationId?: string): Promise {
- // If there's already a refresh in progress, wait for it
- if (this.refreshPromise) {
- await this.refreshPromise;
- return;
- }
+ /**
+ * Performs login and stores tokens
+ * @param correlationId - Optional correlation ID for logging
+ */
+ private async login(correlationId?: string): Promise {
+ try {
+ const loginResponse = await this.apiService.login(
+ this.tenantId,
+ this.tenantSecret,
+ correlationId,
+ );
- if (!this.refreshToken) {
- throw new Error('No refresh token available');
- }
+ this.accessToken = loginResponse.accessToken;
+ this.refreshToken = loginResponse.refreshToken;
- // Start refresh and store the promise
- this.refreshPromise = this.performTokenRefresh(correlationId);
- try {
- await this.refreshPromise;
- } finally {
- this.refreshPromise = undefined;
- }
- }
+ // Set token expiry to 50 minutes from now (assuming 1 hour token life)
+ this.tokenExpiry = new Date(Date.now() + 50 * 60 * 1000);
+ } catch (error) {
+ this.clearTokens();
+ throw error;
+ }
+ }
- /**
- * Performs the actual token refresh
- * @param correlationId - Optional correlation ID for logging
- */
- private async performTokenRefresh(correlationId?: string): Promise {
- try {
- const refreshResponse = await this.apiService.refreshToken(
- this.refreshToken!,
- correlationId
- );
-
- this.accessToken = refreshResponse.accessToken;
- this.refreshToken = refreshResponse.refreshToken;
-
- // Set token expiry to 50 minutes from now
- this.tokenExpiry = new Date(Date.now() + 50 * 60 * 1000);
- } catch (error) {
- this.clearTokens();
- throw error;
- }
- }
+ /**
+ * Attempts to refresh the access token with concurrency protection
+ * @param correlationId - Optional correlation ID for logging
+ */
+ private async refreshAccessToken(correlationId?: string): Promise {
+ // If there's already a refresh in progress, wait for it
+ if (this.refreshPromise) {
+ await this.refreshPromise;
+ return;
+ }
- /**
- * Clears stored tokens and pending promises
- */
- private clearTokens(): void {
- this.accessToken = undefined;
- this.refreshToken = undefined;
- this.tokenExpiry = undefined;
- this.refreshPromise = undefined;
- this.loginPromise = undefined;
- }
+ if (!this.refreshToken) {
+ throw new Error('No refresh token available');
+ }
- /**
- * Executes an API call with automatic authentication and retry logic
- * @param apiCall - The API call function to execute
- * @param retryCount - Number of retries (defaults to class default)
- * @param correlationId - Optional correlation ID for logging
- */
- private async executeWithRetry(
- apiCall: (accessToken: string) => Promise,
- retryCount: number = this.defaultRetryCount,
- correlationId?: string
- ): Promise {
- let lastError: Error;
+ // Start refresh and store the promise
+ this.refreshPromise = this.performTokenRefresh(correlationId);
+ try {
+ await this.refreshPromise;
+ } finally {
+ this.refreshPromise = undefined;
+ }
+ }
- for (let attempt = 0; attempt <= retryCount; attempt++) {
- try {
- // Ensure we have a valid token
- await this.ensureAuthenticated(correlationId);
-
- if (!this.accessToken) {
- throw new Error('Failed to obtain access token');
- }
+ /**
+ * Performs the actual token refresh
+ * @param correlationId - Optional correlation ID for logging
+ */
+ private async performTokenRefresh(correlationId?: string): Promise {
+ try {
+ const refreshResponse = await this.apiService.refreshToken(this.refreshToken!, correlationId);
- // Execute the API call
- return await apiCall(this.accessToken);
-
- } catch (error: any) {
- lastError = error;
-
- // If it's an authentication error and we have retries left, try to refresh token
- if (error.message.includes('Unauthorized') && attempt < retryCount) {
- try {
- await this.refreshAccessToken(correlationId);
- continue; // Retry with new token
- } catch (refreshError) {
- // If refresh fails, clear tokens and try full login on next attempt
- this.clearTokens();
- }
- }
-
- // If it's the last attempt or not an auth error, throw
- if (attempt === retryCount) {
- throw lastError;
- }
-
- // Wait before retry (exponential backoff)
- await new Promise(resolve => {
- const delay = Math.pow(2, attempt) * 1000;
- // Use a simple delay implementation
- const start = Date.now();
- while (Date.now() - start < delay) {
- // Busy wait for delay
- }
- resolve(undefined);
- });
- }
- }
+ this.accessToken = refreshResponse.accessToken;
+ this.refreshToken = refreshResponse.refreshToken;
- throw lastError!;
- }
+ // Set token expiry to 50 minutes from now
+ this.tokenExpiry = new Date(Date.now() + 50 * 60 * 1000);
+ } catch (error) {
+ this.clearTokens();
+ throw error;
+ }
+ }
- /**
- * Protect file using external identifier with automatic authentication and retry
- */
- async protectWithExternalRefId(
- protectRequest: IProtectWithExternalRefIdRequest,
- correlationId?: string,
- retryCount?: number
- ): Promise {
- return this.executeWithRetry(
- (accessToken) => this.apiService.protectWithExternalRefId(protectRequest, accessToken, correlationId),
- retryCount,
- correlationId
- );
- }
+ /**
+ * Clears stored tokens and pending promises
+ */
+ private clearTokens(): void {
+ this.accessToken = undefined;
+ this.refreshToken = undefined;
+ this.tokenExpiry = undefined;
+ this.refreshPromise = undefined;
+ this.loginPromise = undefined;
+ }
- /**
- * Protect file using existing protected file ID with automatic authentication and retry
- */
- async protectWithFileId(
- protectRequest: IProtectWithFileIdRequest,
- correlationId?: string,
- retryCount?: number
- ): Promise {
- return this.executeWithRetry(
- (accessToken) => this.apiService.protectWithFileId(protectRequest, accessToken, correlationId),
- retryCount,
- correlationId
- );
- }
+ /**
+ * Executes an API call with automatic authentication and retry logic
+ * @param apiCall - The API call function to execute
+ * @param retryCount - Number of retries (defaults to class default)
+ * @param correlationId - Optional correlation ID for logging
+ */
+ private async executeWithRetry(
+ apiCall: (accessToken: string) => Promise,
+ retryCount: number = this.defaultRetryCount,
+ correlationId?: string,
+ ): Promise {
+ let lastError: Error;
- /**
- * Protect file using HotFolder ID with automatic authentication and retry
- */
- async protectWithHotFolder(
- protectRequest: IProtectWithHotFolderRequest,
- correlationId?: string,
- retryCount?: number
- ): Promise {
- return this.executeWithRetry(
- (accessToken) => this.apiService.protectWithHotFolder(protectRequest, accessToken, correlationId),
- retryCount,
- correlationId
- );
- }
+ for (let attempt = 0; attempt <= retryCount; attempt++) {
+ try {
+ // Ensure we have a valid token
+ await this.ensureAuthenticated(correlationId);
- /**
- * Unprotect file with automatic authentication and retry
- */
- async unprotect(
- unprotectRequest: IUnprotectRequest,
- correlationId?: string,
- retryCount?: number
- ): Promise {
- return this.executeWithRetry(
- (accessToken) => this.apiService.unprotect(unprotectRequest, accessToken, correlationId),
- retryCount,
- correlationId
- );
- }
+ if (!this.accessToken) {
+ throw new Error('Failed to obtain access token');
+ }
- /**
- * Upload file with automatic authentication and retry
- */
- async uploadFile(
- fileBuffer: Uint8Array,
- fileName: string,
- correlationId?: string,
- retryCount?: number
- ): Promise {
- return this.executeWithRetry(
- (accessToken) => this.apiService.uploadFile(fileBuffer, fileName, accessToken, correlationId),
- retryCount,
- correlationId
- );
- }
+ // Execute the API call
+ return await apiCall(this.accessToken);
+ } catch (error: unknown) {
+ lastError = error as Error;
+ // If it's an authentication error and we have retries left, try to refresh token
+ if ((error as Error).message.includes('Unauthorized') && attempt < retryCount) {
+ try {
+ await this.refreshAccessToken(correlationId);
+ continue; // Retry with new token
+ } catch (error: unknown) {
+ console.error(error);
+ // If refresh fails, clear tokens and try full login on next attempt
+ this.clearTokens();
+ }
+ }
- /**
- * Download file with automatic authentication and retry
- * NOTE: Files whose fileStorageId has 'DL_' prefix will be deleted from the file storage after download.
- */
- async downloadFile(
- fileStorageId: string,
- correlationId?: string,
- retryCount?: number
- ): Promise {
- return this.executeWithRetry(
- (accessToken) => this.apiService.downloadFile(fileStorageId, accessToken, correlationId),
- retryCount,
- correlationId
- );
- }
+ // If it's the last attempt or not an auth error, throw
+ if (attempt === retryCount) {
+ throw lastError;
+ }
- /**
- * Get current access token (for debugging/monitoring)
- */
- getAccessToken(): string | undefined {
- return this.accessToken;
- }
+ // Wait before retry (exponential backoff)
+ await new Promise((resolve) => {
+ const delay = Math.pow(2, attempt) * 1000;
+ // Use a simple delay implementation
+ const start = Date.now();
+ while (Date.now() - start < delay) {
+ // Busy wait for delay
+ }
+ resolve(undefined);
+ });
+ }
+ }
- /**
- * Check if currently authenticated
- */
- isAuthenticated(): boolean {
- return !!this.accessToken && (!this.tokenExpiry || new Date() < this.tokenExpiry);
- }
+ throw lastError!;
+ }
+ /**
+ * Protect file using external identifier with automatic authentication and retry
+ */
+ async protectWithExternalRefId(
+ protectRequest: IProtectWithExternalRefIdRequest,
+ correlationId?: string,
+ retryCount?: number,
+ ): Promise {
+ return this.executeWithRetry(
+ (accessToken) =>
+ this.apiService.protectWithExternalRefId(protectRequest, accessToken, correlationId),
+ retryCount,
+ correlationId,
+ );
+ }
+
+ /**
+ * Protect file using existing protected file ID with automatic authentication and retry
+ */
+ async protectWithFileId(
+ protectRequest: IProtectWithFileIdRequest,
+ correlationId?: string,
+ retryCount?: number,
+ ): Promise {
+ return this.executeWithRetry(
+ (accessToken) =>
+ this.apiService.protectWithFileId(protectRequest, accessToken, correlationId),
+ retryCount,
+ correlationId,
+ );
+ }
+
+ /**
+ * Protect file using HotFolder ID with automatic authentication and retry
+ */
+ async protectWithHotFolder(
+ protectRequest: IProtectWithHotFolderRequest,
+ correlationId?: string,
+ retryCount?: number,
+ ): Promise {
+ return this.executeWithRetry(
+ (accessToken) =>
+ this.apiService.protectWithHotFolder(protectRequest, accessToken, correlationId),
+ retryCount,
+ correlationId,
+ );
+ }
+
+ /**
+ * Unprotect file with automatic authentication and retry
+ */
+ async unprotect(
+ unprotectRequest: IUnprotectRequest,
+ correlationId?: string,
+ retryCount?: number,
+ ): Promise {
+ return this.executeWithRetry(
+ (accessToken) => this.apiService.unprotect(unprotectRequest, accessToken, correlationId),
+ retryCount,
+ correlationId,
+ );
+ }
+
+ /**
+ * Upload file with automatic authentication and retry
+ */
+ async uploadFile(
+ fileBuffer: Uint8Array,
+ fileName: string,
+ correlationId?: string,
+ retryCount?: number,
+ ): Promise {
+ return this.executeWithRetry(
+ (accessToken) => this.apiService.uploadFile(fileBuffer, fileName, accessToken, correlationId),
+ retryCount,
+ correlationId,
+ );
+ }
+
+ /**
+ * Download file with automatic authentication and retry
+ * NOTE: Files whose fileStorageId has 'DL_' prefix will be deleted from the file storage after download.
+ */
+ async downloadFile(
+ fileStorageId: string,
+ correlationId?: string,
+ retryCount?: number,
+ ): Promise {
+ return this.executeWithRetry(
+ (accessToken) => this.apiService.downloadFile(fileStorageId, accessToken, correlationId),
+ retryCount,
+ correlationId,
+ );
+ }
+
+ /**
+ * Get current access token (for debugging/monitoring)
+ */
+ getAccessToken(): string | undefined {
+ return this.accessToken;
+ }
+
+ /**
+ * Check if currently authenticated
+ */
+ isAuthenticated(): boolean {
+ return !!this.accessToken && (!this.tokenExpiry || new Date() < this.tokenExpiry);
+ }
}