xjs-common 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +216 -0
- package/dist/const/gender.d.ts +4 -0
- package/dist/const/gender.js +8 -0
- package/dist/const/http-method.d.ts +8 -0
- package/dist/const/http-method.js +12 -0
- package/dist/const/types.d.ts +12 -0
- package/dist/const/types.js +14 -0
- package/dist/func/decorator/d-type.d.ts +24 -0
- package/dist/func/decorator/d-type.js +71 -0
- package/dist/func/decorator/d-validate.d.ts +20 -0
- package/dist/func/decorator/d-validate.js +64 -0
- package/dist/func/decorator/transaction.d.ts +7 -0
- package/dist/func/decorator/transaction.js +36 -0
- package/dist/func/u-array.d.ts +40 -0
- package/dist/func/u-array.js +67 -0
- package/dist/func/u-file.d.ts +10 -0
- package/dist/func/u-file.js +69 -0
- package/dist/func/u-http.d.ts +7 -0
- package/dist/func/u-http.js +25 -0
- package/dist/func/u-obj.d.ts +18 -0
- package/dist/func/u-obj.js +44 -0
- package/dist/func/u-string.d.ts +9 -0
- package/dist/func/u-string.js +56 -0
- package/dist/func/u-type.d.ts +21 -0
- package/dist/func/u-type.js +53 -0
- package/dist/func/u.d.ts +6 -0
- package/dist/func/u.js +45 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +35 -0
- package/dist/obj/type-exp.d.ts +8 -0
- package/dist/obj/type-exp.js +23 -0
- package/dist/obj/xjs-err.d.ts +4 -0
- package/dist/obj/xjs-err.js +11 -0
- package/dist/prcs/http/http-resolver-context.d.ts +46 -0
- package/dist/prcs/http/http-resolver-context.js +287 -0
- package/dist/prcs/http/http-resolver.d.ts +38 -0
- package/dist/prcs/http/http-resolver.js +52 -0
- package/dist/prcs/http/i-http-client.d.ts +37 -0
- package/dist/prcs/http/i-http-client.js +2 -0
- package/dist/prcs/http-resolver.d.ts +53 -0
- package/dist/prcs/http-resolver.js +255 -0
- package/package.json +30 -0
@@ -0,0 +1,255 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
9
|
+
}) : (function(o, m, k, k2) {
|
10
|
+
if (k2 === undefined) k2 = k;
|
11
|
+
o[k2] = m[k];
|
12
|
+
}));
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
15
|
+
}) : function(o, v) {
|
16
|
+
o["default"] = v;
|
17
|
+
});
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
19
|
+
if (mod && mod.__esModule) return mod;
|
20
|
+
var result = {};
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
22
|
+
__setModuleDefault(result, mod);
|
23
|
+
return result;
|
24
|
+
};
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
26
|
+
exports.HttpResolver = exports.s_clientMode = void 0;
|
27
|
+
const tls = __importStar(require("tls"));
|
28
|
+
const zlib = __importStar(require("zlib"));
|
29
|
+
const url_1 = require("url");
|
30
|
+
const https_1 = require("https");
|
31
|
+
const http_1 = require("http");
|
32
|
+
const async_hooks_1 = require("async_hooks");
|
33
|
+
const xjs_err_1 = require("../obj/xjs-err");
|
34
|
+
const u_http_1 = require("../func/u-http");
|
35
|
+
const u_array_1 = require("../func/u-array");
|
36
|
+
exports.s_clientMode = {
|
37
|
+
nodejs: { id: 0, cipherOrder: null },
|
38
|
+
chrome: { id: 1, cipherOrder: [2, 0, 1] },
|
39
|
+
firefox: { id: 2, cipherOrder: [2, 1, 0] }
|
40
|
+
};
|
41
|
+
const s_cmvRange = 5;
|
42
|
+
const s_timeout = 1000 * 20;
|
43
|
+
const s_errCode = 200;
|
44
|
+
class HttpResolver {
|
45
|
+
_baseCmv;
|
46
|
+
_l;
|
47
|
+
_als = new async_hooks_1.AsyncLocalStorage();
|
48
|
+
_mode2headers = new Map([
|
49
|
+
[exports.s_clientMode.firefox, (cmv) => ({
|
50
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
51
|
+
"Accept-Encoding": "gzip, deflate, br",
|
52
|
+
"Accept-Language": "en-US,en;q=0.5",
|
53
|
+
"Sec-Fetch-Dest": "document",
|
54
|
+
"Sec-Fetch-Mode": "navigate",
|
55
|
+
"Sec-Fetch-Site": "none",
|
56
|
+
"Sec-Fetch-User": "?1",
|
57
|
+
"Upgrade-Insecure-Requests": "1",
|
58
|
+
"User-Agent": `Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:${cmv}.0) Gecko/20100101 Firefox/${cmv}.0`
|
59
|
+
})],
|
60
|
+
[exports.s_clientMode.chrome, (cmv) => {
|
61
|
+
const uad = cmv < 130
|
62
|
+
? `"Not/A)Brand";v="8", "Chromium";v="${cmv}", "Google Chrome";v="${cmv}"`
|
63
|
+
: `"Chromium";v="${cmv}", "Not:A-Brand";v="24", "Google Chrome";v="${cmv}"`;
|
64
|
+
const ch = {
|
65
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
66
|
+
"Accept-Encoding": "gzip, deflate, br, zstd",
|
67
|
+
"Accept-Language": "en-US,en;q=0.9",
|
68
|
+
"Sec-Ch-Ua": uad,
|
69
|
+
"Sec-Ch-Ua-Mobile": "?0",
|
70
|
+
"Sec-Ch-Ua-Platform": "Windows",
|
71
|
+
"Sec-Fetch-Dest": "document",
|
72
|
+
"Sec-Fetch-Mode": "navigate",
|
73
|
+
"Sec-Fetch-Site": "none",
|
74
|
+
"Sec-Fetch-User": "?1",
|
75
|
+
"Upgrade-Insecure-Requests": "1",
|
76
|
+
"User-Agent": `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${cmv}.0.0.0 Safari/537.36`
|
77
|
+
};
|
78
|
+
if (cmv >= 124)
|
79
|
+
ch["Priority"] = "u=0, i";
|
80
|
+
return ch;
|
81
|
+
}]
|
82
|
+
]);
|
83
|
+
/**
|
84
|
+
* @param _baseCmv chrome major version refered when construct a user agent, and the version will be randomized between `n` to `n-4`.
|
85
|
+
* @param _l custom logger. default is `console`.
|
86
|
+
*/
|
87
|
+
constructor(_baseCmv, _l = console) {
|
88
|
+
this._baseCmv = _baseCmv;
|
89
|
+
this._l = _l;
|
90
|
+
}
|
91
|
+
/**
|
92
|
+
* request to the url with GET.
|
93
|
+
* @param url target url.
|
94
|
+
* @param op.mode {@link s_clientMode} that is imitated. default is random between chrome or firefox.
|
95
|
+
* @param op.proxy proxy configuration.
|
96
|
+
* @param op.ignoreQuery if true, query part in the `url` is ignored.
|
97
|
+
* @param op.redirectAsNewRequest handle redirect as new request. this may be efficient when using proxy which is implemented reverse proxy.
|
98
|
+
* @returns string encoded by utf-8 as response payload.
|
99
|
+
*/
|
100
|
+
async get(url, op) {
|
101
|
+
const cmv = this.fixCmv();
|
102
|
+
const m = op?.mode ?? u_array_1.UArray.randomPick([exports.s_clientMode.chrome, exports.s_clientMode.firefox]);
|
103
|
+
const ciphers = this.createCiphers(m);
|
104
|
+
const u = new url_1.URL(url);
|
105
|
+
const proxyAgent = op?.proxy && await this.createProxyAgent(u, op.proxy);
|
106
|
+
this._l.log(`Starts to request with ${Object.keys(exports.s_clientMode).find((_, i) => i === m.id)}:${cmv}.`);
|
107
|
+
const rc = {
|
108
|
+
mode: m, cmv, ciphers,
|
109
|
+
ignoreQuery: !!op?.ignoreQuery,
|
110
|
+
redirectCount: 0, redirectAsNewRequest: !!op?.redirectAsNewRequest,
|
111
|
+
proxyAgent, proxyConfig: op?.proxy
|
112
|
+
};
|
113
|
+
return await this._als.run(rc, this.getIn, u).finally(() => proxyAgent?.destroy());
|
114
|
+
}
|
115
|
+
fixCmv() {
|
116
|
+
return this._baseCmv - Math.floor(Math.random() * s_cmvRange);
|
117
|
+
}
|
118
|
+
createProxyAgent(u, conf) {
|
119
|
+
return new Promise((resolve, reject) => {
|
120
|
+
const headers = {};
|
121
|
+
if (conf.auth)
|
122
|
+
headers['Proxy-Authorization'] = `Basic ${Buffer.from(conf.auth.name + ':' + conf.auth.pass).toString('base64')}`;
|
123
|
+
const req = (0, http_1.request)({
|
124
|
+
host: conf.server,
|
125
|
+
port: conf.port,
|
126
|
+
method: 'CONNECT',
|
127
|
+
path: `${u.hostname}:443`,
|
128
|
+
headers
|
129
|
+
}).on('connect', (res, socket) => {
|
130
|
+
if (res.statusCode === 200)
|
131
|
+
resolve(new https_1.Agent({ socket, keepAlive: true }));
|
132
|
+
else
|
133
|
+
reject(new xjs_err_1.XjsErr(s_errCode, "Could not connect to proxy."));
|
134
|
+
});
|
135
|
+
req.on('error', reject);
|
136
|
+
req.on('timeout', () => {
|
137
|
+
req.destroy();
|
138
|
+
reject(new xjs_err_1.XjsErr(s_errCode, "The http request timeout, maybe server did not respond."));
|
139
|
+
});
|
140
|
+
req.end();
|
141
|
+
});
|
142
|
+
}
|
143
|
+
getIn = async (u) => {
|
144
|
+
const params = {};
|
145
|
+
const rc = this._als.getStore();
|
146
|
+
const toRefresh = rc.redirectCount > 0 && rc.redirectAsNewRequest;
|
147
|
+
params.method = "GET";
|
148
|
+
params.protocol = u.protocol;
|
149
|
+
params.host = u.host;
|
150
|
+
params.path = (rc.ignoreQuery || !u.search) ? u.pathname : `${u.pathname}${u.search}`;
|
151
|
+
params.agent = toRefresh ? await this.createProxyAgent(u, rc.proxyConfig) : rc.proxyAgent;
|
152
|
+
if (toRefresh) {
|
153
|
+
rc.cmv = this.fixCmv();
|
154
|
+
rc.ciphers = this.createCiphers(rc.mode);
|
155
|
+
}
|
156
|
+
return await this.reqHttps(params, null);
|
157
|
+
};
|
158
|
+
reqHttps(params, postData) {
|
159
|
+
params.timeout = s_timeout;
|
160
|
+
const rc = this._als.getStore();
|
161
|
+
if (rc.mode.id > 0) {
|
162
|
+
params.ciphers = rc.ciphers;
|
163
|
+
const chHeaders = this._mode2headers.get(rc.mode)(rc.cmv);
|
164
|
+
params.headers = params.headers ? Object.assign(params.headers, chHeaders) : chHeaders;
|
165
|
+
}
|
166
|
+
if (rc.cookies && !rc.redirectAsNewRequest)
|
167
|
+
this.setCookies(params.headers);
|
168
|
+
return new Promise((resolve, reject) => {
|
169
|
+
const req = (0, https_1.request)(params, (res) => {
|
170
|
+
const sc = u_http_1.UHttp.statusCategoryOf(res.statusCode);
|
171
|
+
if (sc === 3) {
|
172
|
+
this.handleRedirect(res, params.host).then(resolve).catch(reject).finally(() => res.destroy());
|
173
|
+
return;
|
174
|
+
}
|
175
|
+
const bfs = [];
|
176
|
+
const contentEncofing = res.headers["content-encoding"]?.toLocaleLowerCase();
|
177
|
+
res.on('data', chunk => bfs.push(chunk));
|
178
|
+
res.on('end', () => {
|
179
|
+
try {
|
180
|
+
let retBuf = Buffer.concat(bfs);
|
181
|
+
if (contentEncofing == "gzip")
|
182
|
+
retBuf = zlib.gunzipSync(retBuf);
|
183
|
+
else if (contentEncofing == "br")
|
184
|
+
retBuf = zlib.brotliDecompressSync(retBuf);
|
185
|
+
const data = retBuf.toString("utf8");
|
186
|
+
if (sc !== 2) {
|
187
|
+
if (data.trim())
|
188
|
+
this.warn(data);
|
189
|
+
reject(new xjs_err_1.XjsErr(s_errCode, `Https received a error status ${res.statusCode}`));
|
190
|
+
}
|
191
|
+
else
|
192
|
+
resolve(data);
|
193
|
+
}
|
194
|
+
catch (e) {
|
195
|
+
reject(e);
|
196
|
+
}
|
197
|
+
});
|
198
|
+
});
|
199
|
+
req.on('error', reject);
|
200
|
+
req.on('timeout', () => {
|
201
|
+
req.destroy();
|
202
|
+
reject(new xjs_err_1.XjsErr(s_errCode, "The http request timeout, maybe server did not respond."));
|
203
|
+
});
|
204
|
+
if (postData)
|
205
|
+
req.write(postData);
|
206
|
+
req.end();
|
207
|
+
});
|
208
|
+
}
|
209
|
+
handleRedirect(res, host) {
|
210
|
+
const rc = this._als.getStore();
|
211
|
+
if (!res.headers.location)
|
212
|
+
throw new xjs_err_1.XjsErr(s_errCode, "Received http redirection, but no location header found.");
|
213
|
+
if (rc.redirectCount++ > 2)
|
214
|
+
throw new xjs_err_1.XjsErr(s_errCode, "Count of http redirection exceeds limit.");
|
215
|
+
if (res.headers["set-cookie"] && !rc.redirectAsNewRequest)
|
216
|
+
this.storeCookies(res.headers["set-cookie"]);
|
217
|
+
this.log(`Redirect to ${res.headers.location}. (count is ${rc.redirectCount})`);
|
218
|
+
const dest = res.headers.location.startsWith("http") ? res.headers.location : `https://${host}${res.headers.location}`;
|
219
|
+
if (rc.redirectAsNewRequest)
|
220
|
+
rc.proxyAgent?.destroy();
|
221
|
+
return this.getIn(new url_1.URL(dest));
|
222
|
+
}
|
223
|
+
createCiphers(mode) {
|
224
|
+
const defaultCiphers = tls.DEFAULT_CIPHERS.split(':');
|
225
|
+
return [
|
226
|
+
defaultCiphers[mode.cipherOrder[0]],
|
227
|
+
defaultCiphers[mode.cipherOrder[1]],
|
228
|
+
defaultCiphers[mode.cipherOrder[2]],
|
229
|
+
...u_array_1.UArray.shuffle(defaultCiphers.slice(3))
|
230
|
+
].join(':');
|
231
|
+
}
|
232
|
+
setCookies(headers) {
|
233
|
+
const cks = this._als.getStore().cookies;
|
234
|
+
headers.cookie = Object.keys(cks).map(ckk => `${ckk}=${cks[ckk]};`).join(" ");
|
235
|
+
}
|
236
|
+
storeCookies(cookies) {
|
237
|
+
this._als.getStore().cookies ??= {};
|
238
|
+
cookies.filter(c => c).flatMap(c => c.split(";"))
|
239
|
+
.map(c => {
|
240
|
+
const idx = c.indexOf("=");
|
241
|
+
return idx !== -1 && [c.substring(0, idx).toLowerCase().trim(), c.substring(idx + 1)];
|
242
|
+
})
|
243
|
+
.filter(cp => cp && cp[0] && !["secure", "path", "domain", "samesite"].includes(cp[0]))
|
244
|
+
.forEach(cp => this._als.getStore().cookies[cp[0]] = cp[1]);
|
245
|
+
this.log("Store cookies from set-cookie headers.");
|
246
|
+
this.log(JSON.stringify(this._als.getStore().cookies));
|
247
|
+
}
|
248
|
+
log(msg) {
|
249
|
+
this._l.log(`[http-resolver] ${msg}`);
|
250
|
+
}
|
251
|
+
warn(msg) {
|
252
|
+
this._l.warn(`[http-resolver] ${msg}`);
|
253
|
+
}
|
254
|
+
}
|
255
|
+
exports.HttpResolver = HttpResolver;
|
package/package.json
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
{
|
2
|
+
"name": "xjs-common",
|
3
|
+
"version": "7.0.0",
|
4
|
+
"description": "library modules for nodejs that bundled general-purpose implementations.",
|
5
|
+
"repository": {
|
6
|
+
"type": "git",
|
7
|
+
"url": "https://github.com/begyyal/xjs_commons"
|
8
|
+
},
|
9
|
+
"keywords": [
|
10
|
+
"node",
|
11
|
+
"utility"
|
12
|
+
],
|
13
|
+
"scripts": {
|
14
|
+
"build": "tsc",
|
15
|
+
"test": "node ./dist/test/test.js"
|
16
|
+
},
|
17
|
+
"author": "begyyal",
|
18
|
+
"license": "Apache-2.0",
|
19
|
+
"files": [
|
20
|
+
"dist",
|
21
|
+
"!/dist/test*"
|
22
|
+
],
|
23
|
+
"main": "./dist/index",
|
24
|
+
"types": "./dist/index.d.ts",
|
25
|
+
"devDependencies": {
|
26
|
+
"@types/node": "^20.3.1",
|
27
|
+
"ts-node": "^10.9.1",
|
28
|
+
"typescript": "^4.9.5"
|
29
|
+
}
|
30
|
+
}
|