xshell 1.1.25 → 1.2.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/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 { inspect, concat, assert, genid, delay, Lock, encode, decode, pipe_with_error, map_values, unique, timeout, check, colored } from "./utils.js";
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
- if (!message.id)
672
- message.id = genid();
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 = Remote.parse(data);
642
+ message = parse(data);
690
643
  }
691
644
  catch (error) {
692
645
  this.on_error(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xshell",
3
- "version": "1.1.25",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "bin": {
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 = [];
@@ -77,10 +77,17 @@ declare global {
77
77
  indent: number;
78
78
  text: string;
79
79
  };
80
- /** 将 string 根据首个找到的 splitter 拆分 string 为两个部分(必须包含 splitter,否则抛出错误)
80
+ /** 将 string 根据首个找到的 splitter 拆分 string 为两个部分
81
81
  - spitter: 不会包含在结果中
82
- - last?: `false` 为 true 时从后往前找 splitter */
83
- split2(this: string, splitter: string, last?: boolean): [string, string];
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: keyof TObj) => (obj: TObj) => TObj[keyof TObj];
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;
@@ -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
- throw new Error(`字符串: ${this} 必须包含 splitter: ${splitter}`);
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
- throw new Error(`slice_from 在字符串 ${this} 中找不到 search: ${search}`);
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
- throw new Error(`slice_to 在字符串 ${this} 中找不到 search: ${search}`);
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 为两个部分(必须包含 splitter,否则抛出错误)
97
+ /** 将 string 根据首个找到的 splitter 拆分 string 为两个部分
98
98
  - spitter: 不会包含在结果中
99
- - last?: `false` 为 true 时从后往前找 splitter */
100
- split2(this: string, splitter: string, last?: boolean): [string, string];
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: keyof TObj) => (obj: TObj) => TObj[keyof TObj];
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
- throw new Error(`字符串: ${this} 必须包含 splitter: ${splitter}`);
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
- throw new Error(`slice_from 在字符串 ${this} 中找不到 search: ${search}`);
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
- throw new Error(`slice_to 在字符串 ${this} 中找不到 search: ${search}`);
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
- closer: (event: any) => void;
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 closer = subscriber.closer = () => {
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', closer, { once: true });
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.closer);
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(this.url_width);
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
- let range = headers.range;
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(this.url_width);
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 && Object.keys(body).length)
434
- s += '\n' + inspect(body).replace('[Object: null prototype] ', '');
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
- const { fext } = fp;
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) {
@@ -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