drmfileservice added
This commit is contained in:
parent
07c6b03e04
commit
3e1c503f01
|
|
@ -0,0 +1,288 @@
|
|||
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';
|
||||
|
||||
export class SecloreDRMFileService {
|
||||
private apiService: SecloreDRMApiService;
|
||||
private tenantId: string;
|
||||
private tenantSecret: string;
|
||||
private accessToken?: string;
|
||||
private refreshToken?: string;
|
||||
private tokenExpiry?: Date;
|
||||
private refreshPromise?: Promise<void>;
|
||||
private loginPromise?: Promise<void>;
|
||||
|
||||
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<void> {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs login and stores tokens
|
||||
* @param correlationId - Optional correlation ID for logging
|
||||
*/
|
||||
private async login(correlationId?: string): Promise<void> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to refresh the access token with concurrency protection
|
||||
* @param correlationId - Optional correlation ID for logging
|
||||
*/
|
||||
private async refreshAccessToken(correlationId?: string): Promise<void> {
|
||||
// If there's already a refresh in progress, wait for it
|
||||
if (this.refreshPromise) {
|
||||
await this.refreshPromise;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.refreshToken) {
|
||||
throw new Error('No refresh token available');
|
||||
}
|
||||
|
||||
// Start refresh and store the promise
|
||||
this.refreshPromise = this.performTokenRefresh(correlationId);
|
||||
try {
|
||||
await this.refreshPromise;
|
||||
} finally {
|
||||
this.refreshPromise = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the actual token refresh
|
||||
* @param correlationId - Optional correlation ID for logging
|
||||
*/
|
||||
private async performTokenRefresh(correlationId?: string): Promise<void> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears stored tokens and pending promises
|
||||
*/
|
||||
private clearTokens(): void {
|
||||
this.accessToken = undefined;
|
||||
this.refreshToken = undefined;
|
||||
this.tokenExpiry = undefined;
|
||||
this.refreshPromise = undefined;
|
||||
this.loginPromise = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<T>(
|
||||
apiCall: (accessToken: string) => Promise<T>,
|
||||
retryCount: number = this.defaultRetryCount,
|
||||
correlationId?: string
|
||||
): Promise<T> {
|
||||
let lastError: Error;
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protect file using external identifier with automatic authentication and retry
|
||||
*/
|
||||
async protectWithExternalRefId(
|
||||
protectRequest: IProtectWithExternalRefIdRequest,
|
||||
correlationId?: string,
|
||||
retryCount?: number
|
||||
): Promise<IProtectWithExternalRefIdResponse> {
|
||||
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<IProtectWithFileIdResponse> {
|
||||
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<IProtectWithHotFolderResponse> {
|
||||
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<IUnprotectResponse> {
|
||||
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<IFileUploadResponse> {
|
||||
return this.executeWithRetry(
|
||||
(accessToken) => this.apiService.uploadFile(fileBuffer, fileName, 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force logout (clears all tokens)
|
||||
*/
|
||||
logout(): void {
|
||||
this.clearTokens();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue