xshell 1.1.25 → 1.1.26
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/builder.d.ts +2 -2
- package/builder.js +1 -1
- package/file.d.ts +1 -2
- package/file.js +15 -16
- package/io.browser.d.ts +22 -0
- package/io.browser.js +537 -0
- package/io.d.ts +22 -0
- package/io.js +537 -0
- package/net.browser.d.ts +1 -30
- package/net.browser.js +6 -53
- package/net.d.ts +1 -30
- package/net.js +6 -53
- package/package.json +1 -1
- package/process.js +1 -0
- package/prototype.browser.d.ts +19 -6
- package/prototype.browser.js +20 -6
- package/prototype.d.ts +19 -6
- package/prototype.js +20 -6
- package/server.d.ts +2 -2
- package/server.js +17 -17
- package/utils.browser.d.ts +7 -1
- package/utils.browser.js +19 -2
- package/utils.d.ts +10 -4
- package/utils.js +23 -5
package/net.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { FormData } from 'undici';
|
|
|
3
3
|
import type { WebSocket, CloseEvent, ErrorEvent } from 'ws';
|
|
4
4
|
import type { Cookie, CookieJar, MemoryCookieStore } from 'tough-cookie';
|
|
5
5
|
import './prototype.ts';
|
|
6
|
+
import { type Message } from './io.ts';
|
|
6
7
|
import type { Encoding } from './file.ts';
|
|
7
8
|
import { inspect, Lock } from './utils.ts';
|
|
8
9
|
export type { WebSocket };
|
|
@@ -167,33 +168,6 @@ on_message, on_error, on_close, proxy, print, }: {
|
|
|
167
168
|
- void: 什么都不做
|
|
168
169
|
- 以上的 promise */
|
|
169
170
|
export type MessageHandler<TData extends any[] = any[]> = (message: Message<TData>, websocket?: WebSocket) => void | any[] | Promise<void | any[]>;
|
|
170
|
-
/** 二进制消息格式
|
|
171
|
-
- json.length (小端序): 4 字节
|
|
172
|
-
- json 数据
|
|
173
|
-
- binary 数据 */
|
|
174
|
-
export interface Message<TData extends any[] = any[]> {
|
|
175
|
-
/** rpc id: 在 rpc 系统中认为是唯一的。用来在单个 websocket 连接上复用多个 rpc 请求。多个相同 id 的 message 组成一个请求流 */
|
|
176
|
-
id?: number;
|
|
177
|
-
/** 只在 rpc 发起时指定被调用的 function name,发起时 rpc 时必传 */
|
|
178
|
-
func?: string;
|
|
179
|
-
/** 通过这个 flag 主动表明这是发往对方的最后一个 message, 对方可以销毁 handler 了
|
|
180
|
-
并非强制,可以不说明,由双方的函数自己约定
|
|
181
|
-
*/
|
|
182
|
-
done?: boolean;
|
|
183
|
-
/** 通知对方这里产生的错误,本质上类似 data 也是一种数据,并不代表 rpc 的结束,后续可能继续有 rpc message 交换 */
|
|
184
|
-
error?: Error;
|
|
185
|
-
/** data 是一个数组, 作为:
|
|
186
|
-
- rpc 发起方调用 func 的参数,或者请求流 message 携带的数据
|
|
187
|
-
- 结果或者响应流的数据,传给请求发起方
|
|
188
|
-
|
|
189
|
-
里面是可序列化为 json 的 js 变量,或者是 Uint8Array
|
|
190
|
-
Uint8Array 的参数处理后被替换为 Uint8Array.byteLength, 并将下标记录在 bins 中
|
|
191
|
-
|
|
192
|
-
注意: 数组中如果有 undefined 值,传输到对面会变成 null 值 */
|
|
193
|
-
data?: TData;
|
|
194
|
-
/** bins: data 中哪些下标对应的原始值是 Uint8Array 类型的,如: [0, 3] */
|
|
195
|
-
bins?: number[];
|
|
196
|
-
}
|
|
197
171
|
/** 自动保持 remote 连接,只对 initiator 且传入 url 有效,
|
|
198
172
|
- 在未连接,或 websocket on_error 检测到连接断开后以 reconnect_interval 间隔尝试 remote.call(func) 重连
|
|
199
173
|
- 在连接状态下以 heartbeat_interval 间隔发送心跳包确保连接状态
|
|
@@ -257,9 +231,6 @@ export declare class Remote {
|
|
|
257
231
|
keeping: boolean;
|
|
258
232
|
reconnecting: boolean;
|
|
259
233
|
disconnected: boolean;
|
|
260
|
-
/** 使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
|
|
261
|
-
static parse<TData extends any[] = any[]>(buffer: Uint8Array): Message<TData>;
|
|
262
|
-
static pack({ id, func, data, done, error }: Message): Uint8Array<ArrayBuffer>;
|
|
263
234
|
/** 作为 websocket 连接发起方,传入 url 或 websocket,定义远程 Remote
|
|
264
235
|
作为 websocket 连接接收方,不传 url 和 websocket,定义本地 Remote */
|
|
265
236
|
constructor({ url, funcs, print, verbose, websocket, keeper, on_error, }?: {
|
package/net.js
CHANGED
|
@@ -3,7 +3,8 @@ import { buffer as stream_to_buffer, text as stream_to_text } from 'stream/consu
|
|
|
3
3
|
import { isReadable } from 'stream';
|
|
4
4
|
import { t } from "./i18n/instance.js";
|
|
5
5
|
import "./prototype.js";
|
|
6
|
-
import {
|
|
6
|
+
import { message_symbol, pack, parse } from "./io.js";
|
|
7
|
+
import { inspect, assert, genid, delay, Lock, pipe_with_error, map_values, unique, timeout, check, colored } from "./utils.js";
|
|
7
8
|
export const WebSocketConnecting = 0;
|
|
8
9
|
export const WebSocketOpen = 1;
|
|
9
10
|
export const WebSocketClosing = 2;
|
|
@@ -474,54 +475,6 @@ export class Remote {
|
|
|
474
475
|
keeping = false;
|
|
475
476
|
reconnecting = false;
|
|
476
477
|
disconnected = false;
|
|
477
|
-
/** 使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
|
|
478
|
-
static parse(buffer) {
|
|
479
|
-
const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
480
|
-
const len_json = dv.getUint32(0, true);
|
|
481
|
-
let offset = 4 + len_json;
|
|
482
|
-
let message = JSON.parse(decode(buffer.subarray(4, offset)));
|
|
483
|
-
message.data ??= [];
|
|
484
|
-
if (message.bins) {
|
|
485
|
-
const { bins } = message;
|
|
486
|
-
let { data } = message;
|
|
487
|
-
for (const ibin of bins) {
|
|
488
|
-
const len_buf = data[ibin];
|
|
489
|
-
data[ibin] = buffer.subarray(offset, offset + len_buf);
|
|
490
|
-
offset += len_buf;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
if (message.error)
|
|
494
|
-
message.error = Object.assign(new Error(), message.error);
|
|
495
|
-
return message;
|
|
496
|
-
}
|
|
497
|
-
static pack({ id, func, data = [], done, error }) {
|
|
498
|
-
let data_ = new Array(data.length);
|
|
499
|
-
let bins = [];
|
|
500
|
-
let bufs = [];
|
|
501
|
-
for (let i = 0; i < data.length; i++) {
|
|
502
|
-
const item = data[i];
|
|
503
|
-
if (item instanceof Uint8Array) {
|
|
504
|
-
bins.push(i);
|
|
505
|
-
bufs.push(item);
|
|
506
|
-
data_[i] = item.length;
|
|
507
|
-
}
|
|
508
|
-
else
|
|
509
|
-
data_[i] = item;
|
|
510
|
-
}
|
|
511
|
-
const data_json = {
|
|
512
|
-
id,
|
|
513
|
-
...func ? { func } : {},
|
|
514
|
-
...done ? { done } : {},
|
|
515
|
-
...error ? { error } : {},
|
|
516
|
-
...data_.length ? { data: data_ } : {},
|
|
517
|
-
...bins.length ? { bins } : {},
|
|
518
|
-
};
|
|
519
|
-
// 有可能 data_json 含有循环引用导致 JSON.stringify 报错
|
|
520
|
-
const str_json = encode(JSON.stringify(data_json));
|
|
521
|
-
let dv = new DataView(new ArrayBuffer(4));
|
|
522
|
-
dv.setUint32(0, str_json.length, true);
|
|
523
|
-
return concat([dv, str_json, ...bufs]);
|
|
524
|
-
}
|
|
525
478
|
/** 作为 websocket 连接发起方,传入 url 或 websocket,定义远程 Remote
|
|
526
479
|
作为 websocket 连接接收方,不传 url 和 websocket,定义本地 Remote */
|
|
527
480
|
constructor({ url, funcs, print, verbose, websocket, keeper, on_error, } = {}) {
|
|
@@ -668,9 +621,8 @@ export class Remote {
|
|
|
668
621
|
console.log('remote.send:', message);
|
|
669
622
|
try {
|
|
670
623
|
await this.connect(websocket);
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
(websocket || this.lwebsocket.resource).send(Remote.pack(message));
|
|
624
|
+
message[message_symbol] = true;
|
|
625
|
+
(websocket || this.lwebsocket.resource).send(pack(message));
|
|
674
626
|
}
|
|
675
627
|
catch (error) {
|
|
676
628
|
if (message.id)
|
|
@@ -684,9 +636,10 @@ export class Remote {
|
|
|
684
636
|
使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214
|
|
685
637
|
这个方法一般不会抛出错误,也不需要 await,一般在 websocket on_message 时使用 */
|
|
686
638
|
async handle(data, websocket) {
|
|
639
|
+
check(data[0] === 0xcc, 'message 格式错误');
|
|
687
640
|
let message;
|
|
688
641
|
try {
|
|
689
|
-
message =
|
|
642
|
+
message = parse(data);
|
|
690
643
|
}
|
|
691
644
|
catch (error) {
|
|
692
645
|
this.on_error(error);
|
package/package.json
CHANGED
package/process.js
CHANGED
|
@@ -36,6 +36,7 @@ input, stdin = Boolean(input), stdout = !detached, stderr = stdout, print = true
|
|
|
36
36
|
...envs
|
|
37
37
|
});
|
|
38
38
|
// --- 处理 stdio: 将 stdio 中的 true, false, string 转换为对应的 node.js stdio 值
|
|
39
|
+
check(stdout !== 'ignore' || stderr !== 'ignore');
|
|
39
40
|
if (detached)
|
|
40
41
|
check([stdin, stdout, stderr].every(io => io !== true), '调用 start 启动 detached 进程时 stdio 不能为 true (pipe)');
|
|
41
42
|
let opened_handles = [];
|
package/prototype.browser.d.ts
CHANGED
|
@@ -77,10 +77,17 @@ declare global {
|
|
|
77
77
|
indent: number;
|
|
78
78
|
text: string;
|
|
79
79
|
};
|
|
80
|
-
/** 将 string 根据首个找到的 splitter 拆分 string
|
|
80
|
+
/** 将 string 根据首个找到的 splitter 拆分 string 为两个部分
|
|
81
81
|
- spitter: 不会包含在结果中
|
|
82
|
-
-
|
|
83
|
-
|
|
82
|
+
- options?:
|
|
83
|
+
- last?: `false` 为 true 时从后往前找 splitter
|
|
84
|
+
- optional?: `false`
|
|
85
|
+
- false: 必须包含 splitter,否则抛出错误
|
|
86
|
+
- true: 允许不存在 splitter, 返回的数组为 [this] */
|
|
87
|
+
split2(this: string, splitter: string, options?: {
|
|
88
|
+
last?: boolean;
|
|
89
|
+
optional?: boolean;
|
|
90
|
+
}): [string, string?];
|
|
84
91
|
space(this: string): string;
|
|
85
92
|
/** 返回去掉 prefix 开头的字符串
|
|
86
93
|
- validate?: `false` 传 true 时确保字符串以 prefix 开头(失败时抛出错误) */
|
|
@@ -99,12 +106,14 @@ declare global {
|
|
|
99
106
|
/** 从 search 字符串出现的位置开始截取到最后
|
|
100
107
|
- options?:
|
|
101
108
|
- include?: `false` 传 true 时包括 search 部分
|
|
102
|
-
- last?: `false` true 时从最后一次出现的地方开始截取,否则默认从第一次出现的地方开始截取
|
|
109
|
+
- last?: `false` true 时从最后一次出现的地方开始截取,否则默认从第一次出现的地方开始截取
|
|
110
|
+
- optional?: `false` 传 true 时允许不存在 search,此时返回完整的 this */
|
|
103
111
|
slice_from(this: string, search: string, options?: SliceOptions): string;
|
|
104
112
|
/** 从 search 字符串出现的位置开始截断,去掉尾部
|
|
105
113
|
- options?:
|
|
106
114
|
- include?: `false` 传 true 时包括 search 部分
|
|
107
|
-
- last?: `false` true 时截取到最后一次出现的地方,否则默认截取到第一次出现的地方
|
|
115
|
+
- last?: `false` 传 true 时截取到最后一次出现的地方,否则默认截取到第一次出现的地方
|
|
116
|
+
- optional?: `false` 传 true 时允许不存在 search,此时返回完整的 this */
|
|
108
117
|
slice_to(this: string, search: string, options?: SliceOptions): string;
|
|
109
118
|
/** 等价于 .endsWith('/') */
|
|
110
119
|
isdir: boolean;
|
|
@@ -192,15 +201,19 @@ declare global {
|
|
|
192
201
|
interface Set<T> {
|
|
193
202
|
map<TResult>(mapper: (value: T, index: number) => TResult): TResult[];
|
|
194
203
|
}
|
|
204
|
+
interface Uint8Array<TArrayBuffer extends ArrayBufferLike> {
|
|
205
|
+
dataview: DataView<TArrayBuffer>;
|
|
206
|
+
}
|
|
195
207
|
}
|
|
196
208
|
interface SliceOptions {
|
|
197
209
|
include?: boolean;
|
|
198
210
|
last?: boolean;
|
|
211
|
+
optional?: boolean;
|
|
199
212
|
}
|
|
200
213
|
export declare const emoji_regex: RegExp;
|
|
201
214
|
export declare const noop: () => void;
|
|
202
215
|
export declare const ident: <T>(x: T) => T;
|
|
203
|
-
export declare const build_mapper: <TObj>(key:
|
|
216
|
+
export declare const build_mapper: <TObj, TKey extends keyof TObj>(key: TKey) => (obj: TObj) => TObj[TKey];
|
|
204
217
|
export type Mapper<TObj = any, TKey extends keyof TObj = keyof TObj> = (obj: TObj) => TObj[TKey];
|
|
205
218
|
/** value 不为 null 或 undefined */
|
|
206
219
|
export declare const not_empty: (value: any) => boolean;
|
package/prototype.browser.js
CHANGED
|
@@ -265,10 +265,13 @@ Object.defineProperties(String.prototype, {
|
|
|
265
265
|
text: this.slice(i)
|
|
266
266
|
};
|
|
267
267
|
},
|
|
268
|
-
split2(splitter, last = false) {
|
|
268
|
+
split2(splitter, { last = false, optional = false } = {}) {
|
|
269
269
|
const isplitter = last ? this.lastIndexOf(splitter) : this.indexOf(splitter);
|
|
270
270
|
if (isplitter === -1)
|
|
271
|
-
|
|
271
|
+
if (optional)
|
|
272
|
+
return [this];
|
|
273
|
+
else
|
|
274
|
+
throw new Error(`字符串: ${this} 必须包含 splitter: ${splitter}`);
|
|
272
275
|
return [this.slice(0, isplitter), this.slice(isplitter + splitter.length)];
|
|
273
276
|
},
|
|
274
277
|
strip_start(prefix, validate) {
|
|
@@ -293,17 +296,23 @@ Object.defineProperties(String.prototype, {
|
|
|
293
296
|
ensure_end(suffix = '\n') {
|
|
294
297
|
return this.endsWith(suffix) ? this : this + suffix;
|
|
295
298
|
},
|
|
296
|
-
slice_from(search, { include = false, last = false } = {}) {
|
|
299
|
+
slice_from(search, { include = false, last = false, optional = false } = {}) {
|
|
297
300
|
const i = last ? this.lastIndexOf(search) : this.indexOf(search);
|
|
298
301
|
if (i === -1)
|
|
299
|
-
|
|
302
|
+
if (optional)
|
|
303
|
+
return this;
|
|
304
|
+
else
|
|
305
|
+
throw new Error(`slice_from 在字符串 ${this} 中找不到 search: ${search}`);
|
|
300
306
|
else
|
|
301
307
|
return this.slice(include ? i : i + search.length);
|
|
302
308
|
},
|
|
303
|
-
slice_to(search, { include = false, last = false } = {}) {
|
|
309
|
+
slice_to(search, { include = false, last = false, optional = false } = {}) {
|
|
304
310
|
const i = last ? this.lastIndexOf(search) : this.indexOf(search);
|
|
305
311
|
if (i === -1)
|
|
306
|
-
|
|
312
|
+
if (optional)
|
|
313
|
+
return this;
|
|
314
|
+
else
|
|
315
|
+
throw new Error(`slice_to 在字符串 ${this} 中找不到 search: ${search}`);
|
|
307
316
|
else
|
|
308
317
|
return this.slice(0, include ? i + search.length : i);
|
|
309
318
|
},
|
|
@@ -561,6 +570,11 @@ Object.defineProperties(Set.prototype, to_method_property_descriptors({
|
|
|
561
570
|
return Array.from(this, mapfn);
|
|
562
571
|
}
|
|
563
572
|
}));
|
|
573
|
+
Object.defineProperties(Uint8Array.prototype, to_getter_property_descriptors({
|
|
574
|
+
dataview() {
|
|
575
|
+
return new DataView(this.buffer, this.byteOffset, this.byteLength);
|
|
576
|
+
}
|
|
577
|
+
}));
|
|
564
578
|
export function to_json(obj, replacer) {
|
|
565
579
|
return JSON.stringify(obj, replacer, 4) + '\n';
|
|
566
580
|
}
|
package/prototype.d.ts
CHANGED
|
@@ -94,10 +94,17 @@ declare global {
|
|
|
94
94
|
indent: number;
|
|
95
95
|
text: string;
|
|
96
96
|
};
|
|
97
|
-
/** 将 string 根据首个找到的 splitter 拆分 string
|
|
97
|
+
/** 将 string 根据首个找到的 splitter 拆分 string 为两个部分
|
|
98
98
|
- spitter: 不会包含在结果中
|
|
99
|
-
-
|
|
100
|
-
|
|
99
|
+
- options?:
|
|
100
|
+
- last?: `false` 为 true 时从后往前找 splitter
|
|
101
|
+
- optional?: `false`
|
|
102
|
+
- false: 必须包含 splitter,否则抛出错误
|
|
103
|
+
- true: 允许不存在 splitter, 返回的数组为 [this] */
|
|
104
|
+
split2(this: string, splitter: string, options?: {
|
|
105
|
+
last?: boolean;
|
|
106
|
+
optional?: boolean;
|
|
107
|
+
}): [string, string?];
|
|
101
108
|
to_base64(this: string): string;
|
|
102
109
|
/** - buffer: `false` 直接返回 Buffer */
|
|
103
110
|
decode_base64(this: string): string;
|
|
@@ -121,12 +128,14 @@ declare global {
|
|
|
121
128
|
/** 从 search 字符串出现的位置开始截取到最后
|
|
122
129
|
- options?:
|
|
123
130
|
- include?: `false` 传 true 时包括 search 部分
|
|
124
|
-
- last?: `false` true 时从最后一次出现的地方开始截取,否则默认从第一次出现的地方开始截取
|
|
131
|
+
- last?: `false` true 时从最后一次出现的地方开始截取,否则默认从第一次出现的地方开始截取
|
|
132
|
+
- optional?: `false` 传 true 时允许不存在 search,此时返回完整的 this */
|
|
125
133
|
slice_from(this: string, search: string, options?: SliceOptions): string;
|
|
126
134
|
/** 从 search 字符串出现的位置开始截断,去掉尾部
|
|
127
135
|
- options?:
|
|
128
136
|
- include?: `false` 传 true 时包括 search 部分
|
|
129
|
-
- last?: `false` true 时截取到最后一次出现的地方,否则默认截取到第一次出现的地方
|
|
137
|
+
- last?: `false` 传 true 时截取到最后一次出现的地方,否则默认截取到第一次出现的地方
|
|
138
|
+
- optional?: `false` 传 true 时允许不存在 search,此时返回完整的 this */
|
|
130
139
|
slice_to(this: string, search: string, options?: SliceOptions): string;
|
|
131
140
|
/** 等价于 .endsWith('/') */
|
|
132
141
|
isdir: boolean;
|
|
@@ -222,15 +231,19 @@ declare global {
|
|
|
222
231
|
interface Set<T> {
|
|
223
232
|
map<TResult>(mapper: (value: T, index: number) => TResult): TResult[];
|
|
224
233
|
}
|
|
234
|
+
interface Uint8Array<TArrayBuffer extends ArrayBufferLike> {
|
|
235
|
+
dataview: DataView<TArrayBuffer>;
|
|
236
|
+
}
|
|
225
237
|
}
|
|
226
238
|
interface SliceOptions {
|
|
227
239
|
include?: boolean;
|
|
228
240
|
last?: boolean;
|
|
241
|
+
optional?: boolean;
|
|
229
242
|
}
|
|
230
243
|
export declare const emoji_regex: RegExp;
|
|
231
244
|
export declare const noop: () => void;
|
|
232
245
|
export declare const ident: <T>(x: T) => T;
|
|
233
|
-
export declare const build_mapper: <TObj>(key:
|
|
246
|
+
export declare const build_mapper: <TObj, TKey extends keyof TObj>(key: TKey) => (obj: TObj) => TObj[TKey];
|
|
234
247
|
export type Mapper<TObj = any, TKey extends keyof TObj = keyof TObj> = (obj: TObj) => TObj[TKey];
|
|
235
248
|
/** value 不为 null 或 undefined */
|
|
236
249
|
export declare const not_empty: (value: any) => boolean;
|
package/prototype.js
CHANGED
|
@@ -272,10 +272,13 @@ if (!globalThis.my_prototype_defined) {
|
|
|
272
272
|
text: this.slice(i)
|
|
273
273
|
};
|
|
274
274
|
},
|
|
275
|
-
split2(splitter, last = false) {
|
|
275
|
+
split2(splitter, { last = false, optional = false } = {}) {
|
|
276
276
|
const isplitter = last ? this.lastIndexOf(splitter) : this.indexOf(splitter);
|
|
277
277
|
if (isplitter === -1)
|
|
278
|
-
|
|
278
|
+
if (optional)
|
|
279
|
+
return [this];
|
|
280
|
+
else
|
|
281
|
+
throw new Error(`字符串: ${this} 必须包含 splitter: ${splitter}`);
|
|
279
282
|
return [this.slice(0, isplitter), this.slice(isplitter + splitter.length)];
|
|
280
283
|
},
|
|
281
284
|
trim_doc_comment() {
|
|
@@ -315,17 +318,23 @@ if (!globalThis.my_prototype_defined) {
|
|
|
315
318
|
ensure_end(suffix = '\n') {
|
|
316
319
|
return this.endsWith(suffix) ? this : this + suffix;
|
|
317
320
|
},
|
|
318
|
-
slice_from(search, { include = false, last = false } = {}) {
|
|
321
|
+
slice_from(search, { include = false, last = false, optional = false } = {}) {
|
|
319
322
|
const i = last ? this.lastIndexOf(search) : this.indexOf(search);
|
|
320
323
|
if (i === -1)
|
|
321
|
-
|
|
324
|
+
if (optional)
|
|
325
|
+
return this;
|
|
326
|
+
else
|
|
327
|
+
throw new Error(`slice_from 在字符串 ${this} 中找不到 search: ${search}`);
|
|
322
328
|
else
|
|
323
329
|
return this.slice(include ? i : i + search.length);
|
|
324
330
|
},
|
|
325
|
-
slice_to(search, { include = false, last = false } = {}) {
|
|
331
|
+
slice_to(search, { include = false, last = false, optional = false } = {}) {
|
|
326
332
|
const i = last ? this.lastIndexOf(search) : this.indexOf(search);
|
|
327
333
|
if (i === -1)
|
|
328
|
-
|
|
334
|
+
if (optional)
|
|
335
|
+
return this;
|
|
336
|
+
else
|
|
337
|
+
throw new Error(`slice_to 在字符串 ${this} 中找不到 search: ${search}`);
|
|
329
338
|
else
|
|
330
339
|
return this.slice(0, include ? i + search.length : i);
|
|
331
340
|
},
|
|
@@ -625,6 +634,11 @@ if (!globalThis.my_prototype_defined) {
|
|
|
625
634
|
return Array.from(this, mapfn);
|
|
626
635
|
}
|
|
627
636
|
}));
|
|
637
|
+
Object.defineProperties(Uint8Array.prototype, to_getter_property_descriptors({
|
|
638
|
+
dataview() {
|
|
639
|
+
return new DataView(this.buffer, this.byteOffset, this.byteLength);
|
|
640
|
+
}
|
|
641
|
+
}));
|
|
628
642
|
}
|
|
629
643
|
export function to_json(obj, replacer) {
|
|
630
644
|
return JSON.stringify(obj, replacer, 4) + '\n';
|
package/server.d.ts
CHANGED
|
@@ -59,12 +59,11 @@ export declare class Server {
|
|
|
59
59
|
stdio_subscribable?: boolean;
|
|
60
60
|
stdio_subscribers: (((chunk: Uint8Array | string) => Promise<void>) & {
|
|
61
61
|
id: number;
|
|
62
|
-
|
|
62
|
+
close: () => void;
|
|
63
63
|
})[];
|
|
64
64
|
/** 原始 process.stdout.write 函数 bind 后的备份 */
|
|
65
65
|
stdout_write: Function;
|
|
66
66
|
stderr_write: Function;
|
|
67
|
-
url_width: 52;
|
|
68
67
|
constructor({ name, print, http, http2, http_port, http2_port, fpd_certs, default_hostnames, remote, funcs, stdio_subscribable, log_date }: {
|
|
69
68
|
name: string;
|
|
70
69
|
print?: boolean | {
|
|
@@ -153,6 +152,7 @@ export declare class Server {
|
|
|
153
152
|
assets_root?: string;
|
|
154
153
|
sea?: boolean;
|
|
155
154
|
}): Promise<string>;
|
|
155
|
+
set_content_type(response: Koa.Response, fext: string): void;
|
|
156
156
|
/** - range: 取值为逗号分割的多个可用端口或端口区间 (不能含有空格,包含区间右值),比如:`8321,8322,8300-8310,11000-11999
|
|
157
157
|
- reverse?: `false` 在 range 内从后往前尝试 */
|
|
158
158
|
static get_available_port(range: string, reverse?: boolean): Promise<number>;
|
package/server.js
CHANGED
|
@@ -14,7 +14,7 @@ import { contentType as get_content_type } from 'mime-types';
|
|
|
14
14
|
// --- my libs
|
|
15
15
|
import { t } from "./i18n/instance.js";
|
|
16
16
|
import { request as _request, Remote } from "./net.js";
|
|
17
|
-
import { inspect, check, range_to_numbers, encode, filter_keys, filter_values, consume_stream, colored } from "./utils.js";
|
|
17
|
+
import { inspect, check, range_to_numbers, encode, filter_keys, filter_values, consume_stream, colored, url_width } from "./utils.js";
|
|
18
18
|
import { flist, fread, fstat } from "./file.js";
|
|
19
19
|
import { exe_nodejs, sea } from "./process.js";
|
|
20
20
|
// ------------ my server
|
|
@@ -64,7 +64,6 @@ export class Server {
|
|
|
64
64
|
/** 原始 process.stdout.write 函数 bind 后的备份 */
|
|
65
65
|
stdout_write;
|
|
66
66
|
stderr_write;
|
|
67
|
-
url_width = 52;
|
|
68
67
|
constructor({ name, print, http, http2, http_port, http2_port, fpd_certs, default_hostnames, remote, funcs, stdio_subscribable, log_date }) {
|
|
69
68
|
this.name = name;
|
|
70
69
|
if (print !== undefined) {
|
|
@@ -187,20 +186,20 @@ export class Server {
|
|
|
187
186
|
};
|
|
188
187
|
// 让后续可以通过 unsubscribe_stdio 取消订阅
|
|
189
188
|
subscriber.id = id;
|
|
190
|
-
const
|
|
189
|
+
const close = subscriber.close = () => {
|
|
191
190
|
const length = this.stdio_subscribers.length;
|
|
192
191
|
const stdio_subscribers_ = this.stdio_subscribers.filter(s => s !== subscriber);
|
|
193
192
|
if (stdio_subscribers_.length !== length)
|
|
194
193
|
this.stdio_subscribers = stdio_subscribers_;
|
|
195
194
|
};
|
|
196
195
|
this.stdio_subscribers.push(subscriber);
|
|
197
|
-
websocket.addEventListener('close',
|
|
196
|
+
websocket.addEventListener('close', close, { once: true });
|
|
198
197
|
},
|
|
199
198
|
/** 主动取消订阅,需要清理 stdio listener, websocket close lisener */
|
|
200
199
|
unsubscribe_stdio: ({ data: [id] }, websocket) => {
|
|
201
200
|
this.stdio_subscribers = this.stdio_subscribers.filter(s => {
|
|
202
201
|
if (s.id === id) {
|
|
203
|
-
websocket.removeEventListener('close', s.
|
|
202
|
+
websocket.removeEventListener('close', s.close);
|
|
204
203
|
return false;
|
|
205
204
|
}
|
|
206
205
|
else
|
|
@@ -286,7 +285,7 @@ export class Server {
|
|
|
286
285
|
host +
|
|
287
286
|
// path
|
|
288
287
|
url;
|
|
289
|
-
s = s.pad(
|
|
288
|
+
s = s.pad(url_width);
|
|
290
289
|
// ip
|
|
291
290
|
s += ` <- ${request.socket.remoteAddress.strip_if_start('::ffff:')}`;
|
|
292
291
|
// ua
|
|
@@ -405,14 +404,14 @@ export class Server {
|
|
|
405
404
|
? path
|
|
406
405
|
: path.yellow;
|
|
407
406
|
// range
|
|
408
|
-
|
|
407
|
+
const range = headers.range;
|
|
409
408
|
if (headers.range) {
|
|
410
409
|
let [, start, end] = /(\d*)-(\d*)/.exec(range) || [];
|
|
411
410
|
if (start)
|
|
412
411
|
start = Number(start).to_fsize_str();
|
|
413
412
|
if (end)
|
|
414
413
|
end = Number(end).to_fsize_str();
|
|
415
|
-
s += ` (${start} - ${end || ''})
|
|
414
|
+
s += ` (${start} - ${end || ''})`.limit(18);
|
|
416
415
|
}
|
|
417
416
|
s += ' ';
|
|
418
417
|
// queries
|
|
@@ -422,7 +421,7 @@ export class Server {
|
|
|
422
421
|
.strip_if_end('\n');
|
|
423
422
|
s += ' ';
|
|
424
423
|
}
|
|
425
|
-
s = s.pad(
|
|
424
|
+
s = s.pad(url_width);
|
|
426
425
|
// ip
|
|
427
426
|
s += ` <- ${ip.strip_if_start('::ffff:')}`;
|
|
428
427
|
// ua
|
|
@@ -430,8 +429,8 @@ export class Server {
|
|
|
430
429
|
if (ua)
|
|
431
430
|
s += `/${ua}`;
|
|
432
431
|
// body
|
|
433
|
-
if (body
|
|
434
|
-
s += '\n' + inspect(body)
|
|
432
|
+
if (body)
|
|
433
|
+
s += '\n' + inspect(body);
|
|
435
434
|
// 打印日志
|
|
436
435
|
console.log(s);
|
|
437
436
|
}
|
|
@@ -653,12 +652,8 @@ export class Server {
|
|
|
653
652
|
response.set('last-modified', last_modified_str);
|
|
654
653
|
if (download)
|
|
655
654
|
response.set('content-disposition', `attachment; filename="${encodeURIComponent(fp.fname)}"`);
|
|
656
|
-
if (!response.get('content-type'))
|
|
657
|
-
|
|
658
|
-
response.set('content-type', (Server.js_exts.has(fext)
|
|
659
|
-
? 'text/javascript; chatset=utf-8'
|
|
660
|
-
: get_content_type(`file.${fext}`) || 'application/octet-stream'));
|
|
661
|
-
}
|
|
655
|
+
if (!response.get('content-type'))
|
|
656
|
+
this.set_content_type(response, fp.fext);
|
|
662
657
|
const modified_since_str = request.get('if-modified-since');
|
|
663
658
|
if ((method === 'GET' || method === 'HEAD') && modified_since_str === last_modified_str) {
|
|
664
659
|
response.status = 304;
|
|
@@ -703,6 +698,11 @@ export class Server {
|
|
|
703
698
|
}
|
|
704
699
|
return fp;
|
|
705
700
|
}
|
|
701
|
+
set_content_type(response, fext) {
|
|
702
|
+
response.set('content-type', (Server.js_exts.has(fext)
|
|
703
|
+
? 'text/javascript; chatset=utf-8'
|
|
704
|
+
: get_content_type(`file.${fext}`) || 'application/octet-stream'));
|
|
705
|
+
}
|
|
706
706
|
/** - range: 取值为逗号分割的多个可用端口或端口区间 (不能含有空格,包含区间右值),比如:`8321,8322,8300-8310,11000-11999
|
|
707
707
|
- reverse?: `false` 在 range 内从后往前尝试 */
|
|
708
708
|
static async get_available_port(range, reverse = false) {
|
package/utils.browser.d.ts
CHANGED
|
@@ -16,7 +16,9 @@ export declare function unique<TObj>(iterable: TObj[] | Iterable<TObj>, mapper?:
|
|
|
16
16
|
export declare function seq<T = number>(n: number, generator?: (index: number) => T): T[];
|
|
17
17
|
/** 将 keys, values 数组按对应的顺序组合成一个对象 */
|
|
18
18
|
export declare function zip_object<TValue>(keys: (string | number)[], values: TValue[]): Record<string, TValue>;
|
|
19
|
-
/** 过滤对象中的 values, 返回新对象
|
|
19
|
+
/** 过滤对象中的 values, 返回新对象
|
|
20
|
+
- obj
|
|
21
|
+
- filter?: `not_empty` */
|
|
20
22
|
export declare function filter_values<TObj extends Record<string, any>>(obj: TObj, filter?: (value: TObj[string]) => any): TObj;
|
|
21
23
|
export declare function delay(milliseconds: number, { signal }?: {
|
|
22
24
|
signal?: AbortSignal;
|
|
@@ -78,6 +80,7 @@ export declare class Lock<TResource = void> {
|
|
|
78
80
|
export declare function pause(milliseconds?: number): Promise<void>;
|
|
79
81
|
/** 将字符串简单的编码为 utf-8 的 buffer (Uint8Array)。高频使用或者在流式处理时,考虑使用 TextEncoder 的 encodeInto 方法 */
|
|
80
82
|
export declare function encode(str: string): Uint8Array<ArrayBufferLike>;
|
|
83
|
+
export declare function encode_into(str: string, buf: Uint8Array): TextEncoderEncodeIntoResult;
|
|
81
84
|
/** 将 utf-8 buffer (Uint8Array) 简单的解码为 string。
|
|
82
85
|
在流式处理 (buffer 可能不完整) 时,应使用独立的 TextDecoder 实例调用 decode(buffer, { stream: true }) */
|
|
83
86
|
export declare function decode(buffer: Uint8Array): string;
|
|
@@ -90,6 +93,7 @@ export declare function vercmp(l: string, r: string, loose?: boolean): number;
|
|
|
90
93
|
/** 过滤符合 pattern 的行 */
|
|
91
94
|
export declare function grep(str: string, pattern: string | RegExp): string;
|
|
92
95
|
/** 模糊过滤字符串列表或对象列表,常用于根据用户输入补全或搜索过滤
|
|
96
|
+
如果有完全匹配的,只返回那一项的数组
|
|
93
97
|
- query: 查询字符串,要求为全小写
|
|
94
98
|
- list: 要过滤的列表
|
|
95
99
|
- list_lower?: 要过滤的列表对应的全小写字符串列表形式,传入时可复用缓存,加快搜索速度 */
|
|
@@ -127,3 +131,5 @@ export declare class Timer {
|
|
|
127
131
|
getstr_and_reset(parenthesis?: boolean): string;
|
|
128
132
|
}
|
|
129
133
|
export declare function lowercase_first_letter(str: string): string;
|
|
134
|
+
/** 大于 n 的最小的 2 的幂次 */
|
|
135
|
+
export declare function ceil2(n: number): number;
|
package/utils.browser.js
CHANGED
|
@@ -54,7 +54,9 @@ export function zip_object(keys, values) {
|
|
|
54
54
|
return obj;
|
|
55
55
|
}, {});
|
|
56
56
|
}
|
|
57
|
-
/** 过滤对象中的 values, 返回新对象
|
|
57
|
+
/** 过滤对象中的 values, 返回新对象
|
|
58
|
+
- obj
|
|
59
|
+
- filter?: `not_empty` */
|
|
58
60
|
export function filter_values(obj, filter = not_empty) {
|
|
59
61
|
return Object.fromEntries(Object.entries(obj)
|
|
60
62
|
.filter(([, value]) => filter(value)));
|
|
@@ -229,6 +231,10 @@ let encoder = new TextEncoder();
|
|
|
229
231
|
export function encode(str) {
|
|
230
232
|
return encoder.encode(str);
|
|
231
233
|
}
|
|
234
|
+
export function encode_into(str, buf) {
|
|
235
|
+
// 这个是直接用 v8 String::WriteUtf8 最高效的方法了
|
|
236
|
+
return encoder.encodeInto(str, buf);
|
|
237
|
+
}
|
|
232
238
|
let decoder = new TextDecoder();
|
|
233
239
|
/** 将 utf-8 buffer (Uint8Array) 简单的解码为 string。
|
|
234
240
|
在流式处理 (buffer 可能不完整) 时,应使用独立的 TextDecoder 实例调用 decode(buffer, { stream: true }) */
|
|
@@ -271,6 +277,7 @@ export function grep(str, pattern) {
|
|
|
271
277
|
.join_lines();
|
|
272
278
|
}
|
|
273
279
|
/** 模糊过滤字符串列表或对象列表,常用于根据用户输入补全或搜索过滤
|
|
280
|
+
如果有完全匹配的,只返回那一项的数组
|
|
274
281
|
- query: 查询字符串,要求为全小写
|
|
275
282
|
- list: 要过滤的列表
|
|
276
283
|
- list_lower?: 要过滤的列表对应的全小写字符串列表形式,传入时可复用缓存,加快搜索速度 */
|
|
@@ -281,11 +288,14 @@ export function fuzzyfilter(query, list, mapper = ident, list_lower, single_char
|
|
|
281
288
|
mapper = build_mapper(mapper);
|
|
282
289
|
mapper ??= ident;
|
|
283
290
|
list_lower ??= list.map(item => mapper(item).toLowerCase());
|
|
291
|
+
const query_lower = query.toLowerCase();
|
|
292
|
+
let ifullmatch;
|
|
293
|
+
if ((ifullmatch = list_lower.indexOf(query_lower)) !== -1)
|
|
294
|
+
return [list[ifullmatch]];
|
|
284
295
|
if (single_char_startswith && query.length === 1) {
|
|
285
296
|
const c = query[0];
|
|
286
297
|
return list.filter((_, i) => list_lower[i].startsWith(c));
|
|
287
298
|
}
|
|
288
|
-
const query_lower = query.toLowerCase();
|
|
289
299
|
return list.filter((_, i) => {
|
|
290
300
|
const str_lower = list_lower[i];
|
|
291
301
|
let j = 0;
|
|
@@ -401,4 +411,11 @@ export class Timer {
|
|
|
401
411
|
export function lowercase_first_letter(str) {
|
|
402
412
|
return str[0].toLowerCase() + str.slice(1);
|
|
403
413
|
}
|
|
414
|
+
/** 大于 n 的最小的 2 的幂次 */
|
|
415
|
+
export function ceil2(n) {
|
|
416
|
+
let power = 1;
|
|
417
|
+
for (; power <= n; power <<= 1)
|
|
418
|
+
;
|
|
419
|
+
return power;
|
|
420
|
+
}
|
|
404
421
|
//# sourceMappingURL=utils.browser.js.map
|