Skip to content

Commit 7e7d0c5

Browse files
Merge pull request #83 from contentstack/development
DX | 15-09-2025 | Release
2 parents 74a8968 + 8776fa9 commit 7e7d0c5

File tree

5 files changed

+55
-12
lines changed

5 files changed

+55
-12
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@contentstack/datasync-manager",
33
"author": "Contentstack LLC <[email protected]>",
4-
"version": "2.1.1",
4+
"version": "2.1.2",
55
"description": "The primary module of Contentstack DataSync. Syncs Contentstack data with your server using Contentstack Sync API",
66
"main": "dist/index.js",
77
"dependencies": {

src/api.ts

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ import { readFileSync } from './util/fs'
1313

1414
const debug = Debug('api')
1515
let MAX_RETRY_LIMIT
16+
let RETRY_DELAY_BASE = 200 // Default base delay in milliseconds
17+
let TIMEOUT = 30000 // Default timeout in milliseconds
1618
let Contentstack
1719

1820
/**
1921
* @description Initialize sync utilities API requests
2022
* @param {Object} contentstack - Contentstack configuration details
21-
*/
23+
*/
2224
export const init = (contentstack) => {
2325
const packageInfo: any = JSON.parse(readFileSync(join(__dirname, '..', 'package.json')))
2426
Contentstack = contentstack
@@ -35,6 +37,14 @@ export const init = (contentstack) => {
3537
if (Contentstack.MAX_RETRY_LIMIT) {
3638
MAX_RETRY_LIMIT = Contentstack.MAX_RETRY_LIMIT
3739
}
40+
41+
if (Contentstack.RETRY_DELAY_BASE) {
42+
RETRY_DELAY_BASE = Contentstack.RETRY_DELAY_BASE
43+
}
44+
45+
if (Contentstack.TIMEOUT) {
46+
TIMEOUT = Contentstack.TIMEOUT
47+
}
3848
}
3949

4050
/**
@@ -60,13 +70,14 @@ export const get = (req, RETRY = 1) => {
6070
path: sanitizeUrl(encodeURI(req.path)),
6171
port: Contentstack.port,
6272
protocol: Contentstack.protocol,
73+
timeout: TIMEOUT, // Configurable timeout to prevent socket hang ups
6374
}
6475

6576
try {
6677
debug(`${options.method.toUpperCase()}: ${options.path}`)
6778
let timeDelay
6879
let body = ''
69-
request(options, (response) => {
80+
const httpRequest = request(options, (response) => {
7081

7182
response
7283
.setEncoding('utf-8')
@@ -76,8 +87,8 @@ export const get = (req, RETRY = 1) => {
7687
if (response.statusCode >= 200 && response.statusCode <= 399) {
7788
return resolve(JSON.parse(body))
7889
} else if (response.statusCode === 429) {
79-
timeDelay = Math.pow(Math.SQRT2, RETRY) * 200
80-
debug(`API rate limit exceeded. Retrying ${options.path} with ${timeDelay} sec delay`)
90+
timeDelay = Math.pow(Math.SQRT2, RETRY) * RETRY_DELAY_BASE
91+
debug(`API rate limit exceeded. Retrying ${options.path} with ${timeDelay} ms delay`)
8192

8293
return setTimeout(() => {
8394
return get(req, RETRY)
@@ -86,8 +97,8 @@ export const get = (req, RETRY = 1) => {
8697
}, timeDelay)
8798
} else if (response.statusCode >= 500) {
8899
// retry, with delay
89-
timeDelay = Math.pow(Math.SQRT2, RETRY) * 200
90-
debug(`Retrying ${options.path} with ${timeDelay} sec delay`)
100+
timeDelay = Math.pow(Math.SQRT2, RETRY) * RETRY_DELAY_BASE
101+
debug(`Retrying ${options.path} with ${timeDelay} ms delay`)
91102
RETRY++
92103

93104
return setTimeout(() => {
@@ -102,8 +113,35 @@ export const get = (req, RETRY = 1) => {
102113
}
103114
})
104115
})
105-
.on('error', reject)
106-
.end()
116+
117+
// Set socket timeout to handle socket hang ups
118+
httpRequest.setTimeout(options.timeout, () => {
119+
debug(`Request timeout for ${options.path || 'unknown'}`)
120+
httpRequest.destroy()
121+
reject(new Error('Request timeout'))
122+
})
123+
124+
// Enhanced error handling for socket hang ups and connection resets
125+
httpRequest.on('error', (error: any) => {
126+
debug(`Request error for ${options.path || 'unknown'}: ${error?.message || 'Unknown error'} (${error?.code || 'NO_CODE'})`)
127+
128+
// Handle socket hang up and connection reset errors with retry
129+
if ((error?.code === 'ECONNRESET' || error?.message?.includes('socket hang up')) && RETRY <= MAX_RETRY_LIMIT) {
130+
timeDelay = Math.pow(Math.SQRT2, RETRY) * RETRY_DELAY_BASE
131+
debug(`Socket hang up detected. Retrying ${options.path || 'unknown'} with ${timeDelay} ms delay (attempt ${RETRY}/${MAX_RETRY_LIMIT})`)
132+
RETRY++
133+
134+
return setTimeout(() => {
135+
return get(req, RETRY)
136+
.then(resolve)
137+
.catch(reject)
138+
}, timeDelay)
139+
}
140+
141+
return reject(error)
142+
})
143+
144+
httpRequest.end()
107145
} catch (error) {
108146
return reject(error)
109147
}

src/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export const config = {
3333
},
3434
contentstack: {
3535
MAX_RETRY_LIMIT: 6,
36+
TIMEOUT: 30000, // 30 seconds - can be overridden by user config
37+
RETRY_DELAY_BASE: 200, // Base delay for retry logic - can be overridden by user config
3638
actions: {
3739
delete: 'delete',
3840
publish: 'publish',

src/core/inet.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ export const checkNetConnectivity = () => {
8080
}
8181

8282
export const netConnectivityIssues = (error) => {
83-
if (error.code === 'ENOTFOUND' || error.code === 'ETIMEDOUT') {
83+
// Include socket hang up and connection reset errors as network connectivity issues
84+
const networkErrorCodes = ['ENOTFOUND', 'ETIMEDOUT', 'ECONNRESET', 'EPIPE', 'EHOSTUNREACH']
85+
86+
if (networkErrorCodes.includes(error.code) || error.message?.includes('socket hang up')) {
8487
return true
8588
}
8689

0 commit comments

Comments
 (0)