vwo-fme-node-sdk
Version:
VWO Node/JavaScript SDK for Feature Management and Experimentation
229 lines (203 loc) • 9.13 kB
text/typescript
/**
* Copyright 2024-2025 Wingify Software Pvt. Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Deferred } from '../../../utils/PromiseUtil';
import { IRetryConfig } from '../client/NetworkClient';
import { NetworkClientInterface } from '../client/NetworkClientInterface';
import { RequestHandler } from '../handlers/RequestHandler';
import { GlobalRequestModel } from '../models/GlobalRequestModel';
import { RequestModel } from '../models/RequestModel';
import { ResponseModel } from '../models/ResponseModel';
import { Constants } from '../../../constants';
import { isBoolean, isNumber } from '../../../utils/DataTypeUtil';
import { LogManager } from '../../logger/core/LogManager';
import { ErrorLogMessagesEnum } from '../../../enums/log-messages';
import { buildMessage } from '../../../utils/LogMessageUtil';
export class NetworkManager {
private config: GlobalRequestModel; // Holds the global configuration for network requests
private client: NetworkClientInterface; // Interface for the network client handling the actual HTTP requests
private static instance: NetworkManager; // Singleton instance of NetworkManager
private retryConfig: IRetryConfig;
/**
* Validates the retry configuration parameters
* @param {IRetryConfig} retryConfig - The retry configuration to validate
* @returns {IRetryConfig} The validated retry configuration with corrected values
*/
private validateRetryConfig(retryConfig: IRetryConfig): IRetryConfig {
const validatedConfig: IRetryConfig = { ...retryConfig };
let isInvalidConfig = false;
// Validate shouldRetry: should be a boolean value
if (!isBoolean(validatedConfig.shouldRetry)) {
validatedConfig.shouldRetry = Constants.DEFAULT_RETRY_CONFIG.shouldRetry;
isInvalidConfig = true;
}
// Validate maxRetries: should be a non-negative integer and should not be less than 1
if (
!isNumber(validatedConfig.maxRetries) ||
!Number.isInteger(validatedConfig.maxRetries) ||
validatedConfig.maxRetries < 1
) {
validatedConfig.maxRetries = Constants.DEFAULT_RETRY_CONFIG.maxRetries;
isInvalidConfig = true;
}
// Validate initialDelay: should be a non-negative integer and should not be less than 1
if (
!isNumber(validatedConfig.initialDelay) ||
!Number.isInteger(validatedConfig.initialDelay) ||
validatedConfig.initialDelay < 1
) {
validatedConfig.initialDelay = Constants.DEFAULT_RETRY_CONFIG.initialDelay;
isInvalidConfig = true;
}
// Validate backoffMultiplier: should be a non-negative integer and should not be less than 2
if (
!isNumber(validatedConfig.backoffMultiplier) ||
!Number.isInteger(validatedConfig.backoffMultiplier) ||
validatedConfig.backoffMultiplier < 2
) {
validatedConfig.backoffMultiplier = Constants.DEFAULT_RETRY_CONFIG.backoffMultiplier;
isInvalidConfig = true;
}
if (isInvalidConfig) {
LogManager.Instance.error(
buildMessage(ErrorLogMessagesEnum.RETRY_CONFIG_INVALID, {
retryConfig: JSON.stringify(validatedConfig),
}),
);
}
return isInvalidConfig ? Constants.DEFAULT_RETRY_CONFIG : validatedConfig;
}
/**
* Attaches a network client to the manager, or uses a default if none provided.
* @param {NetworkClientInterface} client - The client to attach, optional.
* @param {IRetryConfig} retryConfig - The retry configuration, optional.
*/
attachClient(client?: NetworkClientInterface, retryConfig?: IRetryConfig): void {
// Only set retry configuration if it's not already initialized or if a new config is provided
if (!this.retryConfig || retryConfig) {
// Define default retry configuration
const defaultRetryConfig: IRetryConfig = Constants.DEFAULT_RETRY_CONFIG;
// Merge provided retryConfig with defaults, giving priority to provided values
const mergedConfig = {
...defaultRetryConfig,
...(retryConfig || {}),
};
// Validate the merged configuration
this.retryConfig = this.validateRetryConfig(mergedConfig);
}
// if env is undefined, we are in browser
if ((typeof process.env as any) === 'undefined') {
// if XMLHttpRequest is undefined, we are in serverless
if (typeof XMLHttpRequest === 'undefined') {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { NetworkServerLessClient } = require('../client/NetworkServerLessClient');
this.client = client || new NetworkServerLessClient();
} else {
// if XMLHttpRequest is defined, we are in browser
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { NetworkBrowserClient } = require('../client/NetworkBrowserClient');
this.client = client || new NetworkBrowserClient(); // Use provided client or default to NetworkClient
}
} else {
// if env is defined, we are in node
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { NetworkClient } = require('../client/NetworkClient');
this.client = client || new NetworkClient(); // Use provided client or default to NetworkClient
}
this.config = new GlobalRequestModel(null, null, null, null); // Initialize with default config
}
/**
* Retrieves the current retry configuration.
* @returns {IRetryConfig} A copy of the current retry configuration.
*/
getRetryConfig(): IRetryConfig {
return { ...this.retryConfig };
}
/**
* Singleton accessor for the NetworkManager instance.
* @returns {NetworkManager} The singleton instance.
*/
static get Instance(): NetworkManager {
this.instance = this.instance || new NetworkManager(); // Create instance if it doesn't exist
return this.instance;
}
/**
* Sets the global configuration for network requests.
* @param {GlobalRequestModel} config - The configuration to set.
*/
setConfig(config: GlobalRequestModel): void {
this.config = config; // Set the global request configuration
}
/**
* Retrieves the current global configuration.
* @returns {GlobalRequestModel} The current configuration.
*/
getConfig(): GlobalRequestModel {
return this.config; // Return the global request configuration
}
/**
* Creates a network request model by merging specific request data with global config.
* @param {RequestModel} request - The specific request data.
* @returns {RequestModel} The merged request model.
*/
createRequest(request: RequestModel): RequestModel {
const options: RequestModel = new RequestHandler().createRequest(request, this.config); // Merge and create request
return options;
}
/**
* Performs a GET request using the provided request model.
* @param {RequestModel} request - The request model.
* @returns {Promise<ResponseModel>} A promise that resolves to the response model.
*/
get(request: RequestModel): Promise<ResponseModel> {
const deferred = new Deferred(); // Create a new deferred promise
const networkOptions: RequestModel = this.createRequest(request); // Create network request options
if (!networkOptions.getUrl()) {
deferred.reject(new Error('no url found')); // Reject if no URL is found
} else {
this.client
.GET(networkOptions)
.then((response: ResponseModel) => {
deferred.resolve(response); // Resolve with the response
})
.catch((errorResponse: ResponseModel) => {
deferred.reject(errorResponse); // Reject with the error response
});
}
return deferred.promise; // Return the promise
}
/**
* Performs a POST request using the provided request model.
* @param {RequestModel} request - The request model.
* @returns {Promise<ResponseModel>} A promise that resolves to the response model.
*/
post(request: RequestModel): Promise<ResponseModel> {
const deferred = new Deferred(); // Create a new deferred promise
const networkOptions: RequestModel = this.createRequest(request); // Create network request options
if (!networkOptions.getUrl()) {
deferred.reject(new Error('no url found')); // Reject if no URL is found
} else {
this.client
.POST(networkOptions)
.then((response: ResponseModel) => {
deferred.resolve(response); // Resolve with the response
})
.catch((error: ResponseModel) => {
deferred.reject(error); // Reject with the error
});
}
return deferred.promise; // Return the promise
}
}