Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ example/**/*

dist/
dist/*
.DS_store
10 changes: 1 addition & 9 deletions .talismanrc
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
fileignoreconfig:
- filename: .github/workflows/secrets-scan.yml
ignore_detectors:
- filecontent
- filename: .github/workflows/check-version-bump.yml
ignore_detectors:
- filecontent
- filename: package-lock.json
checksum: 1525b038bc7500db7a4b489db28529cd0b2ada15afc7110d818fe13657303612
- filename: .husky/pre-commit
checksum: 1b9367d219802de2e3a8af9c5c698e0c255c00af89339d73bdbb8acf5275079f
checksum: 009fb6f2f26eda48369458fddb51a255ebbb4c73bd66d247d428c3a0faf2ec1b
version: ""
2 changes: 2 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ module.exports = {
// The test environment that will be used for testing
testEnvironment: 'node',

setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],

// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
testPathIgnorePatterns: [
Expand Down
10 changes: 10 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* eslint-env jest */
// marked ships ESM; Jest/ts-jest need a stub for tests that load the app entry.
jest.mock('marked', () => ({
__esModule: true,
default: {
parse: jest.fn(() => ''),
setOptions: jest.fn(),
use: jest.fn(),
},
}))
93 changes: 47 additions & 46 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
{
"name": "@contentstack/datasync-manager",
"author": "Contentstack LLC <support@contentstack.com>",
"version": "2.4.0-beta.2",
"version": "2.4.0-beta.3",
"description": "The primary module of Contentstack DataSync. Syncs Contentstack data with your server using Contentstack Sync API",
"main": "dist/index.js",
"dependencies": {
"@braintree/sanitize-url": "^7.1.2",
"debug": "^4.4.3",
"dns-socket": "^4.2.2",
"lodash": "^4.18.1",
"marked": "^17.0.5",
"marked": "^17.0.6",
"write-file-atomic": "7.0.1"
},
"devDependencies": {
"@semantic-release/commit-analyzer": "^9.0.2",
"@semantic-release/git": "^10.0.1",
"@semantic-release/npm": "^12.0.1",
"@semantic-release/npm": "^12.0.2",
"@semantic-release/release-notes-generator": "^10.0.3",
"@types/debug": "0.0.31",
"@types/jest": "23.3.14",
"@types/lodash": "4.17.15",
"@types/lodash": "4.17.24",
"@types/marked": "^4.3.2",
"@types/mkdirp": "0.5.2",
"@types/nock": "9.3.1",
Expand Down Expand Up @@ -48,6 +48,9 @@
"start": "dist",
"tslint": "npx tslint -c tslint.json 'src/**/*.ts' --fix",
"test": "PLUGIN_PATH=./test/dummy jest --colors --coverage --verbose",
"mock:sync-api": "node scripts/sync-api-mock-server/server.js",
"mock:run-all-scenarios": "node scripts/sync-api-mock-server/run-all-scenarios.js",
"demo:mock": "node scripts/demo-with-mock.js",
"lint": "eslint",
"semantic-release": "semantic-release",
"husky-check": "npx husky && chmod +x .husky/pre-commit"
Expand Down
5 changes: 4 additions & 1 deletion src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,10 @@ export const get = (req, RETRY = 1) => {
httpRequest.setTimeout(options.timeout, () => {
debug(MESSAGES.API.REQUEST_TIMEOUT(options.path))
httpRequest.destroy()
reject(new Error('Request timeout'))
const timeoutError = Object.assign(new Error('Request timeout'), {
code: 'ETIMEDOUT',
}) as Error & { code: string }
reject(timeoutError)
})

// Enhanced error handling for network and connection errors
Expand Down
20 changes: 12 additions & 8 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,16 @@ const check = async () => {
const sync = async () => {
try {
debug(MESSAGES.SYNC_CORE.SYNC_STARTED);
const tokenObject = await getToken();
let tokenObject: IToken
try {
tokenObject = await getToken() as IToken
} catch (tokenError) {
// check() sets SQ before sync(); fire() is never entered — release the gate
flag.SQ = false
throw tokenError
}
debug(MESSAGES.SYNC_CORE.SYNC_TOKEN_OBJECT, tokenObject);
const token: IToken = (tokenObject as IToken)
const token: IToken = tokenObject
const request: any = {
qs: {
environment: process.env.SYNC_ENV || Contentstack.environment || 'development',
Expand Down Expand Up @@ -251,7 +258,7 @@ export const unlock = (refire?: boolean) => {
.catch(flag.requestCache.reject)
}
}
check()
return check()
}

/**
Expand Down Expand Up @@ -373,18 +380,15 @@ const fire = (req: IApiRequest) => {
if (parsedError.error_code === 141) {
logger.error(MESSAGES.SYNC_CORE.OUTDATED_SYNC_TOKEN)
logger.info(MESSAGES.SYNC_CORE.SYNC_TOKEN_RENEWAL)
// Reset flag so next webhook notification can trigger a fresh sync
flag.SQ = false
// Reset sync_token so next sync starts fresh with init=true
Contentstack.sync_token = undefined
}
} catch (parseError) {
// Not a JSON error or not Error 141, continue with normal handling
}

if (netConnectivityIssues(error)) {
flag.SQ = false
}
// Any failed sync API attempt must release the gate so the next notify/poke can run
flag.SQ = false

return reject(error)
})
Expand Down
21 changes: 17 additions & 4 deletions src/core/inet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,23 @@ export const checkNetConnectivity = () => {
}

export const netConnectivityIssues = (error) => {
// Include socket hang up and connection reset errors as network connectivity issues
const networkErrorCodes = ['ENOTFOUND', 'ETIMEDOUT', 'ECONNRESET', 'EPIPE', 'EHOSTUNREACH']

if (networkErrorCodes.includes(error.code) || error.message?.includes('socket hang up')) {
// Align with retryable codes in api.ts plus client-side timeout (no .code on Request timeout)
const networkErrorCodes = [
'ENOTFOUND',
'ETIMEDOUT',
'ECONNRESET',
'EPIPE',
'EHOSTUNREACH',
'ECONNREFUSED',
'ENETUNREACH',
'EAI_AGAIN',
]
const msg = typeof error?.message === 'string' ? error.message : ''

if (networkErrorCodes.includes(error?.code)) {
return true
}
if (msg.includes('socket hang up') || msg.includes('Request timeout')) {
return true
}

Expand Down
2 changes: 2 additions & 0 deletions test/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,5 @@ describe('test api - get()', () => {
// })

})

// Socket timeout + ETIMEDOUT: covered by test/core/inet.ts, scripts/sync-api-mock-server (MOCK_SCENARIO=hang).
3 changes: 2 additions & 1 deletion test/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('check lock-unlock', () => {
})
test('lock-unlock', () => {
expect(lock()).toBeUndefined()
expect(unlock(true)).toBeUndefined()
// unlock(true) would run check() -> sync() and requires init(); use unlock(false) to only exercise the gate
expect(unlock(false)).toBeUndefined()
})
})
Loading
Loading