Skip to content

Commit c638f1d

Browse files
Merge pull request #529 from contentful/feat/ext-6172/create-default-options
feat: export createDefaultOptions [EXT-6172]
2 parents e342423 + 708ed3f commit c638f1d

File tree

3 files changed

+114
-100
lines changed

3 files changed

+114
-100
lines changed

src/create-default-options.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { CreateHttpClientParams, DefaultOptions } from './types'
2+
import { AxiosRequestHeaders } from 'axios'
3+
import qs from 'qs'
4+
5+
// Matches 'sub.host:port' or 'host:port' and extracts hostname and port
6+
// Also enforces toplevel domain specified, no spaces and no protocol
7+
const HOST_REGEX = /^(?!\w+:\/\/)([^\s:]+\.?[^\s:]+)(?::(\d+))?(?!:)$/
8+
9+
/**
10+
* Create default options
11+
* @private
12+
* @param {CreateHttpClientParams} options - Initialization parameters for the HTTP client
13+
* @return {DefaultOptions} options to pass to axios
14+
*/
15+
export default function createDefaultOptions(options: CreateHttpClientParams): DefaultOptions {
16+
const defaultConfig = {
17+
insecure: false as const,
18+
retryOnError: true as const,
19+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
20+
logHandler: (level: string, data: any): void => {
21+
if (level === 'error' && data) {
22+
const title = [data.name, data.message].filter((a) => a).join(' - ')
23+
console.error(`[error] ${title}`)
24+
console.error(data)
25+
return
26+
}
27+
console.log(`[${level}] ${data}`)
28+
},
29+
// Passed to axios
30+
headers: {} as AxiosRequestHeaders,
31+
httpAgent: false as const,
32+
httpsAgent: false as const,
33+
timeout: 30000,
34+
throttle: 0,
35+
basePath: '',
36+
adapter: undefined,
37+
maxContentLength: 1073741824, // 1GB
38+
maxBodyLength: 1073741824, // 1GB
39+
}
40+
const config = {
41+
...defaultConfig,
42+
...options,
43+
}
44+
45+
if (!config.accessToken) {
46+
const missingAccessTokenError = new TypeError('Expected parameter accessToken')
47+
config.logHandler('error', missingAccessTokenError)
48+
throw missingAccessTokenError
49+
}
50+
51+
// Construct axios baseURL option
52+
const protocol = config.insecure ? 'http' : 'https'
53+
const space = config.space ? `${config.space}/` : ''
54+
let hostname = config.defaultHostname
55+
let port: number | string = config.insecure ? 80 : 443
56+
if (config.host && HOST_REGEX.test(config.host)) {
57+
const parsed = config.host.split(':')
58+
if (parsed.length === 2) {
59+
;[hostname, port] = parsed
60+
} else {
61+
hostname = parsed[0]
62+
}
63+
}
64+
65+
// Ensure that basePath does start but not end with a slash
66+
if (config.basePath) {
67+
config.basePath = `/${config.basePath.split('/').filter(Boolean).join('/')}`
68+
}
69+
70+
const baseURL =
71+
options.baseURL || `${protocol}://${hostname}:${port}${config.basePath}/spaces/${space}`
72+
73+
if (!config.headers.Authorization && typeof config.accessToken !== 'function') {
74+
config.headers.Authorization = 'Bearer ' + config.accessToken
75+
}
76+
77+
const axiosOptions: DefaultOptions = {
78+
// Axios
79+
baseURL,
80+
headers: config.headers,
81+
httpAgent: config.httpAgent,
82+
httpsAgent: config.httpsAgent,
83+
proxy: config.proxy,
84+
timeout: config.timeout,
85+
adapter: config.adapter,
86+
maxContentLength: config.maxContentLength,
87+
maxBodyLength: config.maxBodyLength,
88+
paramsSerializer: {
89+
serialize: (params) => {
90+
return qs.stringify(params)
91+
},
92+
},
93+
// Contentful
94+
logHandler: config.logHandler,
95+
responseLogger: config.responseLogger,
96+
requestLogger: config.requestLogger,
97+
retryOnError: config.retryOnError,
98+
}
99+
return axiosOptions
100+
}

src/create-http-client.ts

Lines changed: 12 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
1-
import { AxiosRequestHeaders } from 'axios'
21
import type { AxiosStatic } from 'axios'
32
import copy from 'fast-copy'
4-
import qs from 'qs'
53

64
import asyncToken from './async-token.js'
75
import rateLimitRetry from './rate-limit.js'
86
import rateLimitThrottle from './rate-limit-throttle.js'
9-
import type { AxiosInstance, CreateHttpClientParams, DefaultOptions } from './types.js'
10-
11-
// Matches 'sub.host:port' or 'host:port' and extracts hostname and port
12-
// Also enforces toplevel domain specified, no spaces and no protocol
13-
const HOST_REGEX = /^(?!\w+:\/\/)([^\s:]+\.?[^\s:]+)(?::(\d+))?(?!:)$/
7+
import type { AxiosInstance, CreateHttpClientParams } from './types.js'
8+
import createDefaultOptions from './create-default-options.js'
149

1510
function copyHttpClientParams(options: CreateHttpClientParams): CreateHttpClientParams {
1611
const copiedOptions = copy(options)
@@ -31,89 +26,7 @@ export default function createHttpClient(
3126
axios: AxiosStatic,
3227
options: CreateHttpClientParams,
3328
): AxiosInstance {
34-
const defaultConfig = {
35-
insecure: false as const,
36-
retryOnError: true as const,
37-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
38-
logHandler: (level: string, data: any): void => {
39-
if (level === 'error' && data) {
40-
const title = [data.name, data.message].filter((a) => a).join(' - ')
41-
console.error(`[error] ${title}`)
42-
console.error(data)
43-
return
44-
}
45-
console.log(`[${level}] ${data}`)
46-
},
47-
// Passed to axios
48-
headers: {} as AxiosRequestHeaders,
49-
httpAgent: false as const,
50-
httpsAgent: false as const,
51-
timeout: 30000,
52-
throttle: 0,
53-
basePath: '',
54-
adapter: undefined,
55-
maxContentLength: 1073741824, // 1GB
56-
maxBodyLength: 1073741824, // 1GB
57-
}
58-
const config = {
59-
...defaultConfig,
60-
...options,
61-
}
62-
63-
if (!config.accessToken) {
64-
const missingAccessTokenError = new TypeError('Expected parameter accessToken')
65-
config.logHandler('error', missingAccessTokenError)
66-
throw missingAccessTokenError
67-
}
68-
69-
// Construct axios baseURL option
70-
const protocol = config.insecure ? 'http' : 'https'
71-
const space = config.space ? `${config.space}/` : ''
72-
let hostname = config.defaultHostname
73-
let port: number | string = config.insecure ? 80 : 443
74-
if (config.host && HOST_REGEX.test(config.host)) {
75-
const parsed = config.host.split(':')
76-
if (parsed.length === 2) {
77-
;[hostname, port] = parsed
78-
} else {
79-
hostname = parsed[0]
80-
}
81-
}
82-
83-
// Ensure that basePath does start but not end with a slash
84-
if (config.basePath) {
85-
config.basePath = `/${config.basePath.split('/').filter(Boolean).join('/')}`
86-
}
87-
88-
const baseURL =
89-
options.baseURL || `${protocol}://${hostname}:${port}${config.basePath}/spaces/${space}`
90-
91-
if (!config.headers.Authorization && typeof config.accessToken !== 'function') {
92-
config.headers.Authorization = 'Bearer ' + config.accessToken
93-
}
94-
95-
const axiosOptions: DefaultOptions = {
96-
// Axios
97-
baseURL,
98-
headers: config.headers,
99-
httpAgent: config.httpAgent,
100-
httpsAgent: config.httpsAgent,
101-
proxy: config.proxy,
102-
timeout: config.timeout,
103-
adapter: config.adapter,
104-
maxContentLength: config.maxContentLength,
105-
maxBodyLength: config.maxBodyLength,
106-
paramsSerializer: {
107-
serialize: (params) => {
108-
return qs.stringify(params)
109-
},
110-
},
111-
// Contentful
112-
logHandler: config.logHandler,
113-
responseLogger: config.responseLogger,
114-
requestLogger: config.requestLogger,
115-
retryOnError: config.retryOnError,
116-
}
29+
const axiosOptions = createDefaultOptions(options)
11730

11831
const instance = axios.create(axiosOptions) as AxiosInstance
11932
instance.httpClientParams = options
@@ -142,21 +55,21 @@ export default function createHttpClient(
14255
* Please note that the order of interceptors is important
14356
*/
14457

145-
if (config.onBeforeRequest) {
146-
instance.interceptors.request.use(config.onBeforeRequest)
58+
if (options.onBeforeRequest) {
59+
instance.interceptors.request.use(options.onBeforeRequest)
14760
}
14861

149-
if (typeof config.accessToken === 'function') {
150-
asyncToken(instance, config.accessToken)
62+
if (typeof options.accessToken === 'function') {
63+
asyncToken(instance, options.accessToken)
15164
}
15265

153-
if (config.throttle) {
154-
rateLimitThrottle(instance, config.throttle)
66+
if (options.throttle) {
67+
rateLimitThrottle(instance, options.throttle)
15568
}
156-
rateLimitRetry(instance, config.retryLimit)
69+
rateLimitRetry(instance, options.retryLimit)
15770

158-
if (config.onError) {
159-
instance.interceptors.response.use((response) => response, config.onError)
71+
if (options.onError) {
72+
instance.interceptors.response.use((response) => response, options.onError)
16073
}
16174

16275
return instance

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ export { default as freezeSys } from './freeze-sys.js'
55
export { default as getUserAgentHeader } from './get-user-agent.js'
66
export { default as toPlainObject } from './to-plain-object.js'
77
export { default as errorHandler } from './error-handler.js'
8+
export { default as createDefaultOptions } from './create-default-options.js'
89

9-
export type { AxiosInstance, CreateHttpClientParams } from './types.js'
10+
export type { AxiosInstance, CreateHttpClientParams, DefaultOptions } from './types.js'

0 commit comments

Comments
 (0)