-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathcontentstack-management-sdk.ts
More file actions
163 lines (149 loc) · 5.81 KB
/
contentstack-management-sdk.ts
File metadata and controls
163 lines (149 loc) · 5.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import { client, ContentstackClient, ContentstackConfig } from '@contentstack/management';
import authHandler from './auth-handler';
import { Agent } from 'node:https';
import configHandler, { default as configStore } from './config-handler';
import { getProxyConfigForHost, resolveRequestHost, clearProxyEnv } from './proxy-helper';
import dotenv from 'dotenv';
dotenv.config();
class ManagementSDKInitiator {
private analyticsInfo: string;
constructor() {}
init(context) {
this.analyticsInfo = context?.analyticsInfo;
}
async createAPIClient(config): Promise<ContentstackClient> {
// Resolve host so NO_PROXY applies even when config.host is omitted (e.g. from region.cma)
const host = resolveRequestHost(config);
// NO_PROXY has priority over HTTP_PROXY/HTTPS_PROXY and config-set proxy
const proxyConfig = getProxyConfigForHost(host);
// When bypassing, clear proxy env immediately so SDK never see it (they may read at init or first request).
if (!proxyConfig) {
clearProxyEnv();
}
const option: ContentstackConfig = {
host: config.host,
maxContentLength: config.maxContentLength || 100000000,
maxBodyLength: config.maxBodyLength || 1000000000,
maxRequests: 10,
retryLimit: 3,
timeout: proxyConfig ? 10000 : 60000, // 10s timeout with proxy, 60s without
delayMs: config.delayMs,
httpsAgent: new Agent({
maxSockets: 100,
maxFreeSockets: 10,
keepAlive: true,
timeout: 60000, // active socket keepalive for 60 seconds
// NOTE freeSocketTimeout option not exist in https client
// freeSocketTimeout: 30000, // free socket keepalive for 30 seconds
}),
retryDelay: Math.floor(Math.random() * (8000 - 3000 + 1) + 3000),
logHandler: (level, data) => {},
retryCondition: (error: any): boolean => {
// Don't retry proxy connection errors - fail fast
// Check if proxy is configured and this is a connection error
if (proxyConfig) {
const errorCode = error.code || error.errno || error.syscall;
const errorMessage = error.message || String(error);
// Detect proxy connection failures - don't retry these
if (
errorCode === 'ECONNREFUSED' ||
errorCode === 'ETIMEDOUT' ||
errorCode === 'ENOTFOUND' ||
errorCode === 'ERR_BAD_RESPONSE' ||
errorMessage?.includes('ECONNREFUSED') ||
errorMessage?.includes('connect ECONNREFUSED') ||
errorMessage?.includes('ERR_BAD_RESPONSE') ||
errorMessage?.includes('Bad response') ||
errorMessage?.includes('proxy') ||
(errorCode === 'ETIMEDOUT' && proxyConfig) // Timeout with proxy likely means proxy issue
) {
// Don't retry - return false to fail immediately
// The error will be thrown and handled by error formatter
return false;
}
}
// LINK ***REMOVED***vascript/blob/72fee8ad75ba7d1d5bab8489ebbbbbbaefb1c880/src/core/stack.js#L49
if (error.response && error.response.status) {
switch (error.response.status) {
case 401:
case 429:
case 408:
case 422:
return true;
default:
return false;
}
}
return false;
},
retryDelayOptions: {
base: 1000,
customBackoff: (retryCount, error) => {
return 1;
},
},
refreshToken: () => {
return new Promise((resolve, reject) => {
const authorisationType = configStore.get('authorisationType');
if (authorisationType === 'BASIC') {
// Handle basic auth 401 here
reject('Session timed out, please login to proceed');
} else if (authorisationType === 'OAUTH') {
return authHandler
.compareOAuthExpiry(true)
.then(() => {
resolve({
authorization: `Bearer ${configStore.get('oauthAccessToken')}`,
});
})
.catch((error) => {
reject(error);
});
} else {
reject('You do not have permissions to perform this action, please login to proceed');
}
});
},
};
if (proxyConfig) {
option.proxy = proxyConfig;
}
if (config.endpoint) {
option.endpoint = config.endpoint;
}
if (typeof config.branchName === 'string') {
if (!option.headers) option.headers = {};
option.headers.branch = config.branchName;
}
if (this.analyticsInfo) {
if (!option.headers) option.headers = {};
option.headers['X-CS-CLI'] = this.analyticsInfo;
}
if (!config.management_token) {
const authorisationType = configStore.get('authorisationType');
if (authorisationType === 'BASIC') {
option.authtoken = configStore.get('authtoken');
option.authorization = '';
} else if (authorisationType === 'OAUTH') {
if (!config.skipTokenValidity) {
await authHandler.compareOAuthExpiry();
option.authorization = `Bearer ${configStore.get('oauthAccessToken')}`;
} else {
option.authtoken = '';
option.authorization = '';
}
} else {
option.authtoken = '';
option.authorization = '';
}
}
const earlyAccessHeaders = configStore.get(`earlyAccessHeaders`);
if (earlyAccessHeaders && Object.keys(earlyAccessHeaders).length > 0) {
option.early_access = Object.values(earlyAccessHeaders);
}
return client(option);
}
}
export const managementSDKInitiator = new ManagementSDKInitiator();
export default managementSDKInitiator.createAPIClient.bind(managementSDKInitiator);
export { ContentstackConfig, ContentstackClient };