xshell 1.3.3 → 1.3.4
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/file.d.ts +1 -1
- package/git.js +1 -1
- package/net.d.ts +3 -3
- package/net.js +29 -31
- package/package.json +8 -8
- package/server.js +42 -32
- package/utils.common.d.ts +7 -13
- package/utils.common.js +98 -92
- package/utils.d.ts +0 -1
- package/utils.js +0 -5
package/file.d.ts
CHANGED
package/git.js
CHANGED
package/net.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type net from 'node:net';
|
|
1
2
|
import { type Readable } from 'node:stream';
|
|
2
3
|
import type { Cookie, CookieJar, MemoryCookieStore } from 'tough-cookie';
|
|
3
4
|
import type { Encoding } from './file.ts';
|
|
@@ -48,7 +49,6 @@ export interface RequestOptions {
|
|
|
48
49
|
retry: boolean;
|
|
49
50
|
timeout: boolean;
|
|
50
51
|
};
|
|
51
|
-
long_connection?: true;
|
|
52
52
|
}
|
|
53
53
|
export interface RequestFullOptions extends RequestOptions {
|
|
54
54
|
full: true;
|
|
@@ -97,8 +97,7 @@ export declare class StatusCodeError extends Error {
|
|
|
97
97
|
- decode?: `true` 根据 content-encoding: br 等解码压缩后的 response.body
|
|
98
98
|
- print?:
|
|
99
99
|
- retry: `true` 是否打印 "等待 1 秒后重试 request" 提示信息
|
|
100
|
-
- timeout: `true` 是否打印最后一次超时重试失败时的错误
|
|
101
|
-
- long_connection?: 用于保持长 tcp 连接,保留 nat 端口映射记录 */
|
|
100
|
+
- timeout: `true` 是否打印最后一次超时重试失败时的错误 */
|
|
102
101
|
export declare function request(url: string | URL): Promise<string>;
|
|
103
102
|
export declare function request(url: string | URL, options: RequestRawOptions): Promise<RawResponse>;
|
|
104
103
|
export declare function request(url: string | URL, options: RequestFullOptions & {
|
|
@@ -111,3 +110,4 @@ export declare function request(url: string | URL, options: RequestOptions & {
|
|
|
111
110
|
export declare function request(url: string | URL, options: RequestOptions): Promise<string>;
|
|
112
111
|
/** 发起 http 请求并将响应体作为 json 解析 */
|
|
113
112
|
export declare function request_json<T = any>(url: string | URL, options?: RequestOptions): Promise<T>;
|
|
113
|
+
export declare function get_socket_ip(socket: net.Socket): string;
|
package/net.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import zlib from 'node:zlib';
|
|
2
2
|
import { buffer as stream_to_buffer, text as stream_to_text } from 'node:stream/consumers';
|
|
3
|
-
import { isReadable } from 'node:stream';
|
|
4
|
-
import {
|
|
3
|
+
import { pipeline, isReadable } from 'node:stream';
|
|
4
|
+
import { noop } from "./prototype.js";
|
|
5
|
+
import { inspect, assert, delay, map_values, unique, timeout, check, colored, encode, TimeoutError } from "./utils.js";
|
|
5
6
|
import { drop_request_headers } from "./net.common.js";
|
|
6
7
|
export * from "./net.common.js";
|
|
7
8
|
export var MyProxy;
|
|
@@ -32,27 +33,25 @@ async function request_retry(url, options, _timeout, retries = 0, count = 0, pri
|
|
|
32
33
|
// 设置给 undici 设置 timeout, signal 不一定管用,还是得自己兜底
|
|
33
34
|
options.signal = AbortSignal.timeout(_timeout);
|
|
34
35
|
return await timeout(_timeout + 300, // 为 undici 兜底
|
|
35
|
-
undici.request(url, options), undefined, print.timeout && count >= retries // 只打印最后一次超时的错误,避免太多冗余输出
|
|
36
|
-
);
|
|
36
|
+
undici.request(url, options), undefined, print.timeout && count >= retries); // 只打印最后一次超时的错误,避免太多冗余输出
|
|
37
37
|
}
|
|
38
38
|
else
|
|
39
39
|
return await undici.request(url, options);
|
|
40
40
|
}
|
|
41
41
|
catch (error) {
|
|
42
|
-
if (error.name
|
|
43
|
-
if (count < retries) {
|
|
44
|
-
const duration = 2 ** count;
|
|
45
|
-
if (print.retry)
|
|
46
|
-
console.log(`${`等待 ${duration} 秒后重试请求 (已尝试 ${count + 1} 次) ··`.yellow} ${url.toString().blue.underline}`);
|
|
47
|
-
await delay(1000 * duration);
|
|
48
|
-
return request_retry(url, options, _timeout, retries, count + 1, print);
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
const seconds = _timeout / 1000;
|
|
52
|
-
throw Object.assign(new Error(`请求超过 ${seconds.toFixed(seconds < 1 ? 1 : 0)} 秒等待时间: ${url.toString()}`), { name: 'TimeoutError' });
|
|
53
|
-
}
|
|
54
|
-
else
|
|
42
|
+
if (error.name !== 'TimeoutError')
|
|
55
43
|
throw error;
|
|
44
|
+
// 重试次数内
|
|
45
|
+
if (count < retries) {
|
|
46
|
+
const duration = 2 ** count;
|
|
47
|
+
if (print.retry)
|
|
48
|
+
console.log(`等待 ${duration} 秒后重试请求 (已尝试 ${count + 1} 次) ··`.yellow + ' ' +
|
|
49
|
+
url.toString().blue.underline);
|
|
50
|
+
await delay(1000 * duration);
|
|
51
|
+
return request_retry(url, options, _timeout, retries, count + 1, print);
|
|
52
|
+
}
|
|
53
|
+
const seconds = _timeout / 1000;
|
|
54
|
+
throw new TimeoutError(`请求超过 ${seconds.toFixed(seconds < 1 ? 1 : 0)} 秒等待时间: ${url.toString()}`);
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
export class StatusCodeError extends Error {
|
|
@@ -71,7 +70,7 @@ export async function request(url, options = {}) {
|
|
|
71
70
|
const { queries, headers: _headers, body, type = 'application/json', timeout = 5 * 1000, auth, cookies: _cookies, raw = false, full = false, redirect = 'follow', decode = true, print = {
|
|
72
71
|
timeout: true,
|
|
73
72
|
retry: true
|
|
74
|
-
}
|
|
73
|
+
} } = options;
|
|
75
74
|
let { method, retries, encoding, proxy, } = options;
|
|
76
75
|
url = new URL(url);
|
|
77
76
|
if (queries)
|
|
@@ -128,7 +127,7 @@ export async function request(url, options = {}) {
|
|
|
128
127
|
proxy = MyProxy.socks5;
|
|
129
128
|
let undici_options = {
|
|
130
129
|
...method ? { method } : {},
|
|
131
|
-
dispatcher: get_dispatcher(proxy, redirect
|
|
130
|
+
dispatcher: get_dispatcher(proxy, redirect),
|
|
132
131
|
// 下面这些 timeout 都不是总的时间
|
|
133
132
|
headersTimeout: timeout,
|
|
134
133
|
// 从收完 headers 开始算
|
|
@@ -155,30 +154,26 @@ export async function request(url, options = {}) {
|
|
|
155
154
|
switch (content_encoding) {
|
|
156
155
|
case 'gzip':
|
|
157
156
|
case 'x-gzip':
|
|
158
|
-
body =
|
|
157
|
+
body = pipeline(_body, zlib.createGunzip({
|
|
159
158
|
// Be less strict when decoding compressed responses, since sometimes
|
|
160
159
|
// servers send slightly invalid responses that are still accepted
|
|
161
160
|
// by common browsers.
|
|
162
161
|
// Always using Z_SYNC_FLUSH is what cURL does.
|
|
163
162
|
flush: zlib.constants.Z_SYNC_FLUSH,
|
|
164
163
|
finishFlush: zlib.constants.Z_SYNC_FLUSH
|
|
165
|
-
}));
|
|
164
|
+
}), noop);
|
|
166
165
|
break;
|
|
167
166
|
case 'deflate':
|
|
168
|
-
body =
|
|
167
|
+
body = pipeline(_body, zlib.createInflate(), noop);
|
|
169
168
|
break;
|
|
170
169
|
case 'br':
|
|
171
|
-
body =
|
|
170
|
+
body = pipeline(_body, zlib.createBrotliDecompress(), noop);
|
|
172
171
|
break;
|
|
173
172
|
default:
|
|
174
173
|
throw new Error(`不支持 content-encoding: ${content_encoding.quote()} 的 http 请求`);
|
|
175
174
|
}
|
|
176
175
|
}
|
|
177
|
-
response = {
|
|
178
|
-
status,
|
|
179
|
-
headers,
|
|
180
|
-
body
|
|
181
|
-
};
|
|
176
|
+
response = { status, headers, body };
|
|
182
177
|
if (!((200 <= status && status <= 299) || status === 304 || (redirect === 'manual' && 300 <= status && status < 400)))
|
|
183
178
|
throw new StatusCodeError(status, url.toString());
|
|
184
179
|
}
|
|
@@ -234,8 +229,8 @@ export async function request(url, options = {}) {
|
|
|
234
229
|
:
|
|
235
230
|
body_;
|
|
236
231
|
}
|
|
237
|
-
function get_dispatcher(proxy, redirect
|
|
238
|
-
const key = `${proxy ||
|
|
232
|
+
function get_dispatcher(proxy, redirect) {
|
|
233
|
+
const key = `${proxy || 'direct'}.${redirect}`;
|
|
239
234
|
let dispatcher = dispatchers[key];
|
|
240
235
|
if (dispatcher)
|
|
241
236
|
return dispatcher;
|
|
@@ -243,7 +238,7 @@ function get_dispatcher(proxy, redirect, long_connection) {
|
|
|
243
238
|
// @ts-ignore
|
|
244
239
|
new undici.ProxyAgent({ uri: proxy })
|
|
245
240
|
:
|
|
246
|
-
new undici.Agent({
|
|
241
|
+
new undici.Agent({ allowH2: true });
|
|
247
242
|
if (redirect === 'follow')
|
|
248
243
|
dispatcher = dispatcher.compose(
|
|
249
244
|
// todo: 强制手动处理重定向,来正确处理 cookie ?
|
|
@@ -297,4 +292,7 @@ export async function request_json(url, options) {
|
|
|
297
292
|
throw error;
|
|
298
293
|
}
|
|
299
294
|
}
|
|
295
|
+
export function get_socket_ip(socket) {
|
|
296
|
+
return socket.remoteAddress.strip_if_start('::ffff:');
|
|
297
|
+
}
|
|
300
298
|
//# sourceMappingURL=net.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xshell",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"bin": {
|
|
@@ -56,19 +56,19 @@
|
|
|
56
56
|
"@stylistic/eslint-plugin": "^5.5.0",
|
|
57
57
|
"@svgr/webpack": "^8.1.0",
|
|
58
58
|
"@types/sass-loader": "^8.0.10",
|
|
59
|
-
"@typescript-eslint/eslint-plugin": "^8.46.
|
|
60
|
-
"@typescript-eslint/parser": "^8.46.
|
|
61
|
-
"@typescript-eslint/utils": "^8.46.
|
|
59
|
+
"@typescript-eslint/eslint-plugin": "^8.46.3",
|
|
60
|
+
"@typescript-eslint/parser": "^8.46.3",
|
|
61
|
+
"@typescript-eslint/utils": "^8.46.3",
|
|
62
62
|
"archiver": "^7.0.1",
|
|
63
63
|
"chalk": "^5.6.2",
|
|
64
64
|
"commander": "^14.0.2",
|
|
65
65
|
"css-loader": "^7.1.2",
|
|
66
66
|
"emoji-regex": "^10.6.0",
|
|
67
|
-
"eslint": "^9.39.
|
|
67
|
+
"eslint": "^9.39.1",
|
|
68
68
|
"eslint-plugin-import": "^2.32.0",
|
|
69
69
|
"eslint-plugin-react": "^7.37.5",
|
|
70
70
|
"https-proxy-agent": "^7.0.6",
|
|
71
|
-
"i18next": "^25.6.
|
|
71
|
+
"i18next": "^25.6.1",
|
|
72
72
|
"i18next-scanner": "^4.6.0",
|
|
73
73
|
"koa": "^3.1.1",
|
|
74
74
|
"koa-compress": "^5.1.1",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"mime-types": "^3.0.1",
|
|
77
77
|
"p-map": "^7.0.3",
|
|
78
78
|
"react": "^19.2.0",
|
|
79
|
-
"react-i18next": "^16.2.
|
|
79
|
+
"react-i18next": "^16.2.4",
|
|
80
80
|
"resolve-path": "^1.4.0",
|
|
81
81
|
"sass": "^1.93.3",
|
|
82
82
|
"sass-loader": "^16.0.6",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
"@types/koa": "^3.0.1",
|
|
102
102
|
"@types/koa-compress": "^4.0.7",
|
|
103
103
|
"@types/mime-types": "^3.0.1",
|
|
104
|
-
"@types/node": "^24.
|
|
104
|
+
"@types/node": "^24.10.0",
|
|
105
105
|
"@types/react": "^19.2.2",
|
|
106
106
|
"@types/tough-cookie": "^4.0.5",
|
|
107
107
|
"@types/vscode": "^1.105.0",
|
package/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { default as http, } from 'node:http';
|
|
2
|
+
import { default as http2 } from 'node:http2';
|
|
3
3
|
import { createSecureContext } from 'node:tls';
|
|
4
4
|
import zlib from 'node:zlib';
|
|
5
5
|
import fs from 'node:fs';
|
|
@@ -12,8 +12,8 @@ import resolve_safely from 'resolve-path';
|
|
|
12
12
|
import { contentType as get_content_type } from 'mime-types';
|
|
13
13
|
// --- my libs
|
|
14
14
|
import { t } from "./i18n/instance.js";
|
|
15
|
-
import { request as _request, Remote } from "./net.js";
|
|
16
|
-
import { inspect, check, range_to_numbers, encode, filter_keys, filter_values, consume_stream, colored, url_width } from "./utils.js";
|
|
15
|
+
import { request as _request, Remote, get_socket_ip } from "./net.js";
|
|
16
|
+
import { inspect, check, range_to_numbers, encode, filter_keys, filter_values, consume_stream, colored, url_width, defer2 } from "./utils.js";
|
|
17
17
|
import { flist, fread, fstat } from "./file.js";
|
|
18
18
|
import { exe_nodejs, sea } from "./process.js";
|
|
19
19
|
// ------------ my server
|
|
@@ -138,9 +138,8 @@ export class Server {
|
|
|
138
138
|
app.use(this._router.bind(this));
|
|
139
139
|
this.app = app;
|
|
140
140
|
this.handler = this.app.callback();
|
|
141
|
-
this.http_server =
|
|
142
|
-
|
|
143
|
-
if (http2) {
|
|
141
|
+
this.http_server = http.createServer({ optimizeEmptyRequests: true, noDelay: true }, this.handler);
|
|
142
|
+
if (this.http2) {
|
|
144
143
|
const { fpd_certs } = this;
|
|
145
144
|
let lazy_secure_ctxs = Object.fromEntries(await Promise.all(
|
|
146
145
|
// fpd_certs 文件夹下面的每个 .key 对应一个证书及域名
|
|
@@ -157,7 +156,7 @@ export class Server {
|
|
|
157
156
|
})));
|
|
158
157
|
const default_ctx = lazy_secure_ctxs[this.default_hostnames.find(hostname => hostname in lazy_secure_ctxs)];
|
|
159
158
|
check(default_ctx);
|
|
160
|
-
this.http2_server =
|
|
159
|
+
this.http2_server = http2.createSecureServer({
|
|
161
160
|
SNICallback(servername, callback) {
|
|
162
161
|
let lazy_ctx = lazy_secure_ctxs[servername] ||
|
|
163
162
|
lazy_secure_ctxs[servername.replace(/^.*?\./, '*.')] ||
|
|
@@ -166,6 +165,7 @@ export class Server {
|
|
|
166
165
|
callback(null, lazy_ctx.ctx ??= createSecureContext(lazy_ctx));
|
|
167
166
|
},
|
|
168
167
|
allowHTTP1: true,
|
|
168
|
+
noDelay: true
|
|
169
169
|
}, this.handler);
|
|
170
170
|
}
|
|
171
171
|
// websocket rpc
|
|
@@ -229,20 +229,16 @@ export class Server {
|
|
|
229
229
|
}
|
|
230
230
|
await this.init_server();
|
|
231
231
|
await Promise.all([
|
|
232
|
-
http &&
|
|
233
|
-
|
|
234
|
-
}),
|
|
235
|
-
http2 && new Promise(resolve => {
|
|
236
|
-
this.http2_server.listen(this.http2_port, resolve);
|
|
237
|
-
}),
|
|
232
|
+
this.http && listen_http_server(this.http_server, this.http_port),
|
|
233
|
+
this.http2 && listen_http_server(this.http2_server, this.http2_port)
|
|
238
234
|
]);
|
|
239
235
|
if (this.print.info)
|
|
240
236
|
console.log(t('{{name}} 启动成功,正在监听 {{ports}} 端口', {
|
|
241
237
|
name: this.name,
|
|
242
238
|
ports: [
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
].join(', ')
|
|
239
|
+
this.http && this.http_port,
|
|
240
|
+
this.http2 && this.http2_port
|
|
241
|
+
].filter(Boolean).join(', ')
|
|
246
242
|
}));
|
|
247
243
|
}
|
|
248
244
|
try_write_stdio_subscribers(chunk) {
|
|
@@ -258,25 +254,25 @@ export class Server {
|
|
|
258
254
|
}
|
|
259
255
|
stop() {
|
|
260
256
|
this.http_server.close();
|
|
261
|
-
|
|
262
|
-
this.http2_server.close();
|
|
257
|
+
this.http2_server?.close();
|
|
263
258
|
}
|
|
264
259
|
/** 可被子类重写定义错误处理逻辑 */
|
|
265
260
|
on_error(error, ctx) {
|
|
266
|
-
if (this.print.errors)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
261
|
+
if (!this.print.errors)
|
|
262
|
+
return;
|
|
263
|
+
const code = error?.code;
|
|
264
|
+
if (code === 'EPIPE' || code === 'ECONNRESET')
|
|
265
|
+
console.log(`${error.code}:`, ctx?.request?.url);
|
|
266
|
+
else {
|
|
267
|
+
console.error(error);
|
|
268
|
+
if (ctx)
|
|
269
|
+
console.log('ctx:', ctx);
|
|
275
270
|
}
|
|
276
271
|
}
|
|
277
272
|
on_upgrade(request, socket, head) {
|
|
278
273
|
// url 只有路径部分
|
|
279
274
|
const { url, headers, headers: { host = '' }, } = request;
|
|
275
|
+
const ip = get_socket_ip(request.socket);
|
|
280
276
|
if (this.print.logs) {
|
|
281
277
|
let s =
|
|
282
278
|
// 时间
|
|
@@ -289,7 +285,7 @@ export class Server {
|
|
|
289
285
|
url;
|
|
290
286
|
s = s.pad(url_width);
|
|
291
287
|
// ip
|
|
292
|
-
s += ` <- ${
|
|
288
|
+
s += ` <- ${ip}`;
|
|
293
289
|
// 客户端信息
|
|
294
290
|
const client_info = this.get_client_info(headers);
|
|
295
291
|
if (client_info)
|
|
@@ -327,7 +323,7 @@ export class Server {
|
|
|
327
323
|
console.error(error);
|
|
328
324
|
response.status = error.status || 500;
|
|
329
325
|
response.set('content-type', text_plain);
|
|
330
|
-
response.body =
|
|
326
|
+
response.body = inspect_error(error);
|
|
331
327
|
}
|
|
332
328
|
}
|
|
333
329
|
/** 解析 req.body to request.body
|
|
@@ -638,7 +634,8 @@ export class Server {
|
|
|
638
634
|
if (this.print.errors)
|
|
639
635
|
console.log(error);
|
|
640
636
|
response.status = 500;
|
|
641
|
-
response.
|
|
637
|
+
response.set('content-type', text_plain);
|
|
638
|
+
response.body = inspect_error(error);
|
|
642
639
|
}
|
|
643
640
|
}
|
|
644
641
|
return true;
|
|
@@ -800,7 +797,7 @@ export class Server {
|
|
|
800
797
|
- reverse?: `false` 在 range 内从后往前尝试 */
|
|
801
798
|
static async get_available_port(range, reverse = false) {
|
|
802
799
|
for (const port of range_to_numbers(range, reverse)) {
|
|
803
|
-
let server =
|
|
800
|
+
let server = http.createServer();
|
|
804
801
|
try {
|
|
805
802
|
await new Promise((resolve, reject) => {
|
|
806
803
|
server.once('error', reject);
|
|
@@ -825,4 +822,17 @@ export class Server {
|
|
|
825
822
|
}
|
|
826
823
|
export const text_plain = 'text/plain; charset=utf-8';
|
|
827
824
|
const devtools_trash = '/.well-known/appspecific/com.chrome.devtools.json';
|
|
825
|
+
function listen_http_server(server, port) {
|
|
826
|
+
let plisten = defer2();
|
|
827
|
+
server.once('error', plisten.reject);
|
|
828
|
+
server.listen(port, () => {
|
|
829
|
+
server.off('error', plisten.reject);
|
|
830
|
+
plisten.resolve();
|
|
831
|
+
});
|
|
832
|
+
return plisten;
|
|
833
|
+
}
|
|
834
|
+
const no_color = { colors: false };
|
|
835
|
+
function inspect_error(error) {
|
|
836
|
+
return inspect(error, no_color);
|
|
837
|
+
}
|
|
828
838
|
//# sourceMappingURL=server.js.map
|
package/utils.common.d.ts
CHANGED
|
@@ -50,7 +50,7 @@ export declare function filter_values<TObj extends Record<string, any>>(obj: TOb
|
|
|
50
50
|
/** 简单选择对象中的部分 keys, 返回新对象 */
|
|
51
51
|
export declare function pick<TObject = any>(obj: TObject, keys: (keyof TObject)[]): Partial<TObject>;
|
|
52
52
|
/** 忽略对象中的 keys, 返回新对象 */
|
|
53
|
-
export declare function omit<TObj>(obj: TObj, omit_keys: string[]): TObj;
|
|
53
|
+
export declare function omit<TObj>(obj: TObj, omit_keys: string[] | Set<string>): TObj;
|
|
54
54
|
/** 拼接 TypedArrays 生成一个完整的 Uint8Array */
|
|
55
55
|
export declare function concat(arrays: ArrayBufferView[]): Uint8Array<ArrayBufferLike>;
|
|
56
56
|
export declare let encoder: TextEncoder;
|
|
@@ -67,8 +67,9 @@ export declare function buffer_equals(left: Uint8Array, right: Uint8Array | stri
|
|
|
67
67
|
- milliseconds: 限时毫秒数
|
|
68
68
|
- action?: 要等待运行的任务, async function 或 promise
|
|
69
69
|
- on_timeout?: 超时后调用的函数
|
|
70
|
-
- 若传:
|
|
71
|
-
|
|
70
|
+
- 若传: 超时时调用会调用 on_timeout,参数为 TimeoutError,然后等待 on_timeout 执行完成
|
|
71
|
+
最后 timeout 函数正常返回 null,如果 on_timeout 报错,同样会 reject
|
|
72
|
+
- 若不传: 直接抛出 TimeoutError
|
|
72
73
|
- print?: `true` 打印已超时任务的错误 */
|
|
73
74
|
export declare function timeout<TReturn>(milliseconds: number, action: Promise<TReturn> | (() => Promise<TReturn>), on_timeout?: (error: TimeoutError) => void | Promise<void>, print?: boolean): Promise<TReturn>;
|
|
74
75
|
/** 轮询尝试 action 共 times 次,每次间隔 duration
|
|
@@ -118,7 +119,7 @@ export type StrictArrayBuffer = ArrayBuffer & {
|
|
|
118
119
|
};
|
|
119
120
|
export interface Deferred<TValue> extends Promise<TValue> {
|
|
120
121
|
resolve(value: TValue | PromiseLike<TValue>): void;
|
|
121
|
-
reject(
|
|
122
|
+
reject(error?: Error): void;
|
|
122
123
|
}
|
|
123
124
|
/** 创建一个 promise,后续可调用 promise.resolve, promise.reject 方法设置其状态和值
|
|
124
125
|
- initial?: `undefined` 传入非 undefined 值(包括 null)时直接设置为 resolved 状态
|
|
@@ -126,7 +127,7 @@ export interface Deferred<TValue> extends Promise<TValue> {
|
|
|
126
127
|
export declare function defer<TValue>(initial?: TValue): Deferred<TValue>;
|
|
127
128
|
export interface Deferred2<TValue> extends Promise<TValue> {
|
|
128
129
|
resolve(value: TValue | PromiseLike<TValue>): void;
|
|
129
|
-
reject(
|
|
130
|
+
reject(error?: Error): void;
|
|
130
131
|
get settled(): boolean;
|
|
131
132
|
}
|
|
132
133
|
/** 有 settled 状态的 defer */
|
|
@@ -167,7 +168,7 @@ export declare function lowercase_first_letter(str: string): string;
|
|
|
167
168
|
export declare function ceil2(n: number): number;
|
|
168
169
|
/** 节流,最多只在时间间隔末尾调用一次,可以设置首次调用是否延后 */
|
|
169
170
|
export declare function throttle(duration: number, func: Function, delay_first?: boolean): (this: any, ...args: any[]) => void;
|
|
170
|
-
/**
|
|
171
|
+
/** 防抖,间隔一段时间不再触发时调用,同时大幅优化减少 setTimeout, clearTimeout 的调用次数 */
|
|
171
172
|
export declare function debounce(duration: number, func: Function): (this: any, ...args: any[]) => void;
|
|
172
173
|
export declare function tomorrow(date?: Date | string | number): Date;
|
|
173
174
|
export declare function to_csv_field(str: string): string;
|
|
@@ -176,10 +177,3 @@ export declare function set_error_message(error: Error, message: string): Error;
|
|
|
176
177
|
/** 比较两个数组中的元素完全相同,数组元素用引用比较 */
|
|
177
178
|
export declare function array_equals(a: any[], b: any[]): boolean;
|
|
178
179
|
export declare function nowstr(with_date?: boolean): string;
|
|
179
|
-
interface MyTimeout {
|
|
180
|
-
clear(): void;
|
|
181
|
-
ref: NodeJS.Timeout | number;
|
|
182
|
-
}
|
|
183
|
-
/** 面向对象的 timeout, callback 在后的 timeout */
|
|
184
|
-
export declare function set_timeout(delay: number, callback: VoidFunction): MyTimeout;
|
|
185
|
-
export {};
|
package/utils.common.js
CHANGED
|
@@ -136,8 +136,11 @@ export function pick(obj, keys) {
|
|
|
136
136
|
}
|
|
137
137
|
/** 忽略对象中的 keys, 返回新对象 */
|
|
138
138
|
export function omit(obj, omit_keys) {
|
|
139
|
-
const
|
|
140
|
-
return filter_keys(obj,
|
|
139
|
+
const set = omit_keys instanceof Set;
|
|
140
|
+
return filter_keys(obj, set ?
|
|
141
|
+
key => !(omit_keys.has(key))
|
|
142
|
+
:
|
|
143
|
+
key => !(omit_keys.includes(key)));
|
|
141
144
|
}
|
|
142
145
|
/** 拼接 TypedArrays 生成一个完整的 Uint8Array */
|
|
143
146
|
export function concat(arrays) {
|
|
@@ -184,54 +187,49 @@ export function buffer_equals(left, right) {
|
|
|
184
187
|
- milliseconds: 限时毫秒数
|
|
185
188
|
- action?: 要等待运行的任务, async function 或 promise
|
|
186
189
|
- on_timeout?: 超时后调用的函数
|
|
187
|
-
- 若传:
|
|
188
|
-
|
|
190
|
+
- 若传: 超时时调用会调用 on_timeout,参数为 TimeoutError,然后等待 on_timeout 执行完成
|
|
191
|
+
最后 timeout 函数正常返回 null,如果 on_timeout 报错,同样会 reject
|
|
192
|
+
- 若不传: 直接抛出 TimeoutError
|
|
189
193
|
- print?: `true` 打印已超时任务的错误 */
|
|
190
194
|
export async function timeout(milliseconds, action, on_timeout, print = true) {
|
|
191
195
|
const error = new TimeoutError();
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
await delay(milliseconds);
|
|
197
|
-
if (!done)
|
|
198
|
-
if (on_timeout)
|
|
199
|
-
try {
|
|
200
|
-
await on_timeout(error);
|
|
201
|
-
resolve(null);
|
|
202
|
-
}
|
|
203
|
-
catch (error) {
|
|
204
|
-
if (rejected)
|
|
205
|
-
throw error; // 会成为 unhandled rejection
|
|
206
|
-
else {
|
|
207
|
-
rejected = true;
|
|
208
|
-
reject(error);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
rejected = true;
|
|
213
|
-
reject(error);
|
|
214
|
-
}
|
|
215
|
-
})();
|
|
216
|
-
(async () => {
|
|
196
|
+
let presult = defer2();
|
|
197
|
+
let waiting_on_timeout = false;
|
|
198
|
+
let timeout = setTimeout(async () => {
|
|
199
|
+
if (on_timeout) {
|
|
217
200
|
try {
|
|
218
|
-
|
|
201
|
+
waiting_on_timeout = true;
|
|
202
|
+
await on_timeout(error);
|
|
203
|
+
presult.resolve(null);
|
|
219
204
|
}
|
|
220
205
|
catch (error) {
|
|
221
|
-
|
|
222
|
-
if (print)
|
|
223
|
-
console.log(`已超时任务的错误: ${error.message}`);
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
rejected = true;
|
|
227
|
-
reject(error);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
finally {
|
|
231
|
-
done = true;
|
|
206
|
+
presult.reject(error);
|
|
232
207
|
}
|
|
233
|
-
}
|
|
208
|
+
}
|
|
209
|
+
else
|
|
210
|
+
presult.reject(error);
|
|
211
|
+
}, milliseconds);
|
|
212
|
+
const paction = typeof action === 'function' ? action() : action;
|
|
213
|
+
paction.then(result => {
|
|
214
|
+
if (presult.settled) {
|
|
215
|
+
if (print)
|
|
216
|
+
console.log('已超时任务最终完成了');
|
|
217
|
+
}
|
|
218
|
+
else if (!waiting_on_timeout) {
|
|
219
|
+
presult.resolve(result);
|
|
220
|
+
clearTimeout(timeout);
|
|
221
|
+
}
|
|
222
|
+
}, error => {
|
|
223
|
+
if (presult.settled) {
|
|
224
|
+
if (print)
|
|
225
|
+
console.error(`已超时任务的错误: ${error.message}`);
|
|
226
|
+
}
|
|
227
|
+
else if (!waiting_on_timeout) {
|
|
228
|
+
presult.reject(error);
|
|
229
|
+
clearTimeout(timeout);
|
|
230
|
+
}
|
|
234
231
|
});
|
|
232
|
+
return presult;
|
|
235
233
|
}
|
|
236
234
|
/** 轮询尝试 action 共 times 次,每次间隔 duration
|
|
237
235
|
action 返回 trusy 值时认为成功,返回 action 的结果
|
|
@@ -382,19 +380,18 @@ export class TimeoutError extends Error {
|
|
|
382
380
|
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
383
381
|
export function defer(initial) {
|
|
384
382
|
if (initial === undefined) {
|
|
385
|
-
let resolve;
|
|
386
|
-
let
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
383
|
+
let { promise, resolve, reject } = Promise.withResolvers();
|
|
384
|
+
let p = promise;
|
|
385
|
+
p.resolve = resolve;
|
|
386
|
+
p.reject = reject;
|
|
387
|
+
return p;
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
let p = Promise.resolve(initial);
|
|
391
|
+
p.resolve = noop;
|
|
392
|
+
p.reject = noop;
|
|
393
|
+
return p;
|
|
392
394
|
}
|
|
393
|
-
else
|
|
394
|
-
return Object.assign(Promise.resolve(initial), {
|
|
395
|
-
resolve: noop,
|
|
396
|
-
reject: noop
|
|
397
|
-
});
|
|
398
395
|
}
|
|
399
396
|
/** 有 settled 状态的 defer */
|
|
400
397
|
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
@@ -402,26 +399,29 @@ export function defer2(initial) {
|
|
|
402
399
|
if (initial === undefined) {
|
|
403
400
|
let settled = false;
|
|
404
401
|
let { promise, resolve, reject } = Promise.withResolvers();
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
402
|
+
let p = promise;
|
|
403
|
+
p.resolve = (value) => {
|
|
404
|
+
settled = true;
|
|
405
|
+
resolve(value);
|
|
406
|
+
};
|
|
407
|
+
p.reject = (error) => {
|
|
408
|
+
settled = true;
|
|
409
|
+
reject(error);
|
|
410
|
+
};
|
|
411
|
+
Object.defineProperty(p, 'settled', {
|
|
412
|
+
get() { return settled; },
|
|
413
|
+
enumerable: true,
|
|
414
|
+
configurable: true
|
|
417
415
|
});
|
|
416
|
+
return p;
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
let p = Promise.resolve(initial);
|
|
420
|
+
p.resolve = noop;
|
|
421
|
+
p.reject = noop;
|
|
422
|
+
p.settled = true;
|
|
423
|
+
return p;
|
|
418
424
|
}
|
|
419
|
-
else
|
|
420
|
-
return Object.assign(Promise.resolve(initial), {
|
|
421
|
-
resolve: noop,
|
|
422
|
-
reject: noop,
|
|
423
|
-
settled: true
|
|
424
|
-
});
|
|
425
425
|
}
|
|
426
426
|
/** @example
|
|
427
427
|
let lock = new Lock(redis)
|
|
@@ -508,8 +508,8 @@ export function ceil2(n) {
|
|
|
508
508
|
export function throttle(duration, func, delay_first = false) {
|
|
509
509
|
let timeout = 0;
|
|
510
510
|
let last = 0;
|
|
511
|
-
let saved_this
|
|
512
|
-
let saved_args
|
|
511
|
+
let saved_this;
|
|
512
|
+
let saved_args;
|
|
513
513
|
return function throttled(...args) {
|
|
514
514
|
// 当前时间间隔已预定执行,本次调用仅更新调用参数
|
|
515
515
|
if (timeout) {
|
|
@@ -539,14 +539,33 @@ export function throttle(duration, func, delay_first = false) {
|
|
|
539
539
|
}
|
|
540
540
|
};
|
|
541
541
|
}
|
|
542
|
-
/**
|
|
542
|
+
/** 防抖,间隔一段时间不再触发时调用,同时大幅优化减少 setTimeout, clearTimeout 的调用次数 */
|
|
543
543
|
export function debounce(duration, func) {
|
|
544
|
-
|
|
544
|
+
const half = Math.floor(duration / 2);
|
|
545
|
+
let timeout;
|
|
546
|
+
let last = 0;
|
|
547
|
+
let saved_args;
|
|
548
|
+
let saved_this;
|
|
549
|
+
function debounce_callback() {
|
|
550
|
+
timeout = null;
|
|
551
|
+
func.apply(saved_this, saved_args);
|
|
552
|
+
saved_this = null;
|
|
553
|
+
saved_args = null;
|
|
554
|
+
}
|
|
545
555
|
return function debounced(...args) {
|
|
556
|
+
if (!timeout) {
|
|
557
|
+
timeout = setTimeout(debounce_callback, duration);
|
|
558
|
+
last = Date.now();
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
const now = Date.now();
|
|
562
|
+
saved_args = args;
|
|
563
|
+
saved_this = this;
|
|
564
|
+
if (now - last < half)
|
|
565
|
+
return;
|
|
546
566
|
clearTimeout(timeout);
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
}, duration);
|
|
567
|
+
setTimeout(debounce_callback, duration);
|
|
568
|
+
last = now;
|
|
550
569
|
};
|
|
551
570
|
}
|
|
552
571
|
export function tomorrow(date) {
|
|
@@ -576,17 +595,4 @@ export function nowstr(with_date = false) {
|
|
|
576
595
|
const date = new Date();
|
|
577
596
|
return with_date ? date.to_str() : date.to_time_str();
|
|
578
597
|
}
|
|
579
|
-
/** 面向对象的 timeout, callback 在后的 timeout */
|
|
580
|
-
export function set_timeout(delay, callback) {
|
|
581
|
-
let ret = {
|
|
582
|
-
ref: setTimeout(callback, delay),
|
|
583
|
-
clear() {
|
|
584
|
-
if (!ret.ref)
|
|
585
|
-
return;
|
|
586
|
-
clearTimeout(ret.ref);
|
|
587
|
-
ret.ref = null;
|
|
588
|
-
}
|
|
589
|
-
};
|
|
590
|
-
return ret;
|
|
591
|
-
}
|
|
592
598
|
//# sourceMappingURL=utils.common.js.map
|
package/utils.d.ts
CHANGED
|
@@ -45,7 +45,6 @@ export declare function map_stream<Out, In = any>(mapper: (obj: In, cb: Function
|
|
|
45
45
|
failures?: boolean;
|
|
46
46
|
}): Duplex;
|
|
47
47
|
export declare function stream_to_lines(stream: Readable): AsyncGenerator<string, void, unknown>;
|
|
48
|
-
export declare function pipe_with_error<TWritable extends Writable>(readable: Readable, writable: TWritable): TWritable;
|
|
49
48
|
export declare class WritableMemoryStream extends Writable {
|
|
50
49
|
chunks: Uint8Array[];
|
|
51
50
|
pbuffer: import("./utils.common.ts").Deferred<Buffer<ArrayBufferLike>>;
|
package/utils.js
CHANGED
|
@@ -225,11 +225,6 @@ export async function* stream_to_lines(stream) {
|
|
|
225
225
|
buf = chunk.slice(j);
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
|
-
export function pipe_with_error(readable, writable) {
|
|
229
|
-
// 不知道 transform 作为 AsyncIterable 使用时, emit error 是否会在 for await (...) 循环中触发错误
|
|
230
|
-
readable.once('error', error => { writable.emit('error', error); });
|
|
231
|
-
return readable.pipe(writable);
|
|
232
|
-
}
|
|
233
228
|
export class WritableMemoryStream extends Writable {
|
|
234
229
|
chunks = [];
|
|
235
230
|
pbuffer = defer();
|