2023-11-14 15:26:43 +00:00
|
|
|
import type { DynamicModule, Provider } from '@nestjs/common';
|
2023-12-24 03:43:50 +00:00
|
|
|
import { Logger, Module } from '@nestjs/common';
|
2021-09-07 18:25:31 +00:00
|
|
|
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
|
2023-11-14 15:31:21 +00:00
|
|
|
import axios from 'axios';
|
2023-12-23 23:59:25 +00:00
|
|
|
import axiosRetry, { exponentialDelay } from 'axios-retry';
|
2023-11-14 15:31:21 +00:00
|
|
|
|
2023-07-05 22:37:57 +00:00
|
|
|
import { AXIOS_INSTANCE_TOKEN, HTTP_MODULE_ID, HTTP_MODULE_OPTIONS } from './http.constants';
|
2021-09-07 18:25:31 +00:00
|
|
|
import { HttpService } from './http.service';
|
2024-01-22 02:19:12 +00:00
|
|
|
import { isNetworkOrIdempotentRequestOrGatewayOrRateLimitError } from './http.util';
|
2023-11-14 15:31:21 +00:00
|
|
|
import type {
|
|
|
|
HttpModuleAsyncOptions,
|
|
|
|
HttpModuleOptions,
|
|
|
|
HttpModuleOptionsFactory,
|
|
|
|
} from './interfaces';
|
2021-09-07 18:25:31 +00:00
|
|
|
|
2021-12-23 22:13:08 +00:00
|
|
|
const createAxiosInstance = (config?: HttpModuleOptions) => {
|
2023-12-24 03:43:50 +00:00
|
|
|
const logger = new Logger(HttpService.name);
|
2023-11-14 15:31:21 +00:00
|
|
|
const axiosInstance = axios.create(config);
|
2023-12-23 23:59:25 +00:00
|
|
|
axiosRetry(axiosInstance, {
|
2024-03-24 05:12:24 +00:00
|
|
|
retries: 10,
|
2023-12-23 23:59:25 +00:00
|
|
|
// Default exponential backoff
|
|
|
|
retryDelay: exponentialDelay,
|
2024-01-22 02:19:12 +00:00
|
|
|
retryCondition: isNetworkOrIdempotentRequestOrGatewayOrRateLimitError,
|
2023-12-24 03:43:50 +00:00
|
|
|
onRetry(retryCount, error, requestConfig) {
|
|
|
|
logger.warn(
|
|
|
|
{
|
|
|
|
retryCount,
|
|
|
|
name: error.name,
|
|
|
|
message: error.message,
|
|
|
|
code: error.code,
|
|
|
|
method: requestConfig.method,
|
|
|
|
baseURL: requestConfig.baseURL,
|
|
|
|
url: requestConfig.url,
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
|
|
params: requestConfig.params,
|
|
|
|
data: error.response?.data,
|
|
|
|
},
|
|
|
|
'Retrying request after error',
|
|
|
|
);
|
|
|
|
},
|
2023-12-23 23:59:25 +00:00
|
|
|
...config,
|
|
|
|
});
|
2021-09-07 18:25:31 +00:00
|
|
|
return axiosInstance;
|
2023-07-05 22:37:57 +00:00
|
|
|
};
|
2021-09-07 18:25:31 +00:00
|
|
|
|
|
|
|
@Module({
|
|
|
|
providers: [
|
|
|
|
HttpService,
|
|
|
|
{
|
|
|
|
provide: AXIOS_INSTANCE_TOKEN,
|
2021-12-23 22:13:08 +00:00
|
|
|
useValue: createAxiosInstance(),
|
2021-09-07 18:25:31 +00:00
|
|
|
},
|
|
|
|
],
|
|
|
|
exports: [HttpService],
|
|
|
|
})
|
|
|
|
export class HttpModule {
|
|
|
|
static register(config: HttpModuleOptions): DynamicModule {
|
|
|
|
return {
|
|
|
|
module: HttpModule,
|
|
|
|
providers: [
|
|
|
|
{
|
|
|
|
provide: AXIOS_INSTANCE_TOKEN,
|
2021-12-23 18:09:52 +00:00
|
|
|
useValue: createAxiosInstance(config),
|
2021-09-07 18:25:31 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
provide: HTTP_MODULE_ID,
|
|
|
|
useValue: randomStringGenerator(),
|
|
|
|
},
|
|
|
|
],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
static registerAsync(options: HttpModuleAsyncOptions): DynamicModule {
|
|
|
|
return {
|
|
|
|
module: HttpModule,
|
2023-08-08 21:15:01 +00:00
|
|
|
...(options.imports && { imports: options.imports }),
|
2021-09-07 18:25:31 +00:00
|
|
|
providers: [
|
|
|
|
...this.createAsyncProviders(options),
|
|
|
|
{
|
|
|
|
provide: AXIOS_INSTANCE_TOKEN,
|
2021-12-23 18:09:52 +00:00
|
|
|
useFactory: (config: HttpModuleOptions) => createAxiosInstance(config),
|
2021-09-07 18:25:31 +00:00
|
|
|
inject: [HTTP_MODULE_OPTIONS],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
provide: HTTP_MODULE_ID,
|
|
|
|
useValue: randomStringGenerator(),
|
|
|
|
},
|
2023-11-14 15:31:21 +00:00
|
|
|
...(options.extraProviders ?? []),
|
2021-09-07 18:25:31 +00:00
|
|
|
],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-07-05 22:37:57 +00:00
|
|
|
private static createAsyncProviders(options: HttpModuleAsyncOptions): Provider[] {
|
2023-11-14 15:31:21 +00:00
|
|
|
if (!!options.useExisting || !!options.useFactory) {
|
2021-09-07 18:25:31 +00:00
|
|
|
return [this.createAsyncOptionsProvider(options)];
|
|
|
|
}
|
|
|
|
|
2023-07-05 22:37:57 +00:00
|
|
|
const providers = [this.createAsyncOptionsProvider(options)];
|
2021-09-07 18:25:31 +00:00
|
|
|
|
2023-11-14 15:31:21 +00:00
|
|
|
if (options.useClass) {
|
2021-09-07 18:25:31 +00:00
|
|
|
providers.push({
|
|
|
|
provide: options.useClass,
|
|
|
|
useClass: options.useClass,
|
2023-07-05 22:37:57 +00:00
|
|
|
});
|
2023-11-14 15:31:21 +00:00
|
|
|
}
|
2021-09-07 18:25:31 +00:00
|
|
|
|
|
|
|
return providers;
|
|
|
|
}
|
|
|
|
|
2023-07-05 22:37:57 +00:00
|
|
|
private static createAsyncOptionsProvider(options: HttpModuleAsyncOptions): Provider {
|
2021-09-07 18:25:31 +00:00
|
|
|
if (options.useFactory) {
|
|
|
|
return {
|
|
|
|
provide: HTTP_MODULE_OPTIONS,
|
|
|
|
useFactory: options.useFactory,
|
2023-11-14 15:31:21 +00:00
|
|
|
inject: options.inject ?? [],
|
2021-09-07 18:25:31 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
let inject;
|
2023-11-14 15:31:21 +00:00
|
|
|
if (options.useExisting) {
|
|
|
|
inject = [options.useExisting];
|
|
|
|
} else if (options.useClass) {
|
|
|
|
inject = [options.useClass];
|
|
|
|
}
|
2021-09-07 18:25:31 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
provide: HTTP_MODULE_OPTIONS,
|
|
|
|
useFactory: async (optionsFactory: HttpModuleOptionsFactory) =>
|
2023-11-14 15:31:21 +00:00
|
|
|
await optionsFactory.createHttpOptions(),
|
2023-08-08 21:15:01 +00:00
|
|
|
...(inject && { inject }),
|
2021-09-07 18:25:31 +00:00
|
|
|
};
|
|
|
|
}
|
2023-07-05 22:37:57 +00:00
|
|
|
}
|