xshell 1.1.1 → 1.1.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xshell",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -57,9 +57,9 @@
57
57
  "@svgr/webpack": "^8.1.0",
58
58
  "@types/sass-loader": "^8.0.9",
59
59
  "@types/ws": "^8.5.13",
60
- "@typescript-eslint/eslint-plugin": "^8.17.0",
61
- "@typescript-eslint/parser": "^8.17.0",
62
- "@typescript-eslint/utils": "^8.17.0",
60
+ "@typescript-eslint/eslint-plugin": "^8.18.0",
61
+ "@typescript-eslint/parser": "^8.18.0",
62
+ "@typescript-eslint/utils": "^8.18.0",
63
63
  "@xterm/addon-fit": "^0.10.0",
64
64
  "@xterm/addon-web-links": "^0.11.0",
65
65
  "@xterm/addon-webgl": "^0.18.0",
@@ -91,7 +91,7 @@
91
91
  "mime-types": "^2.1.35",
92
92
  "ora": "^8.1.1",
93
93
  "react": "^19.0.0",
94
- "react-i18next": "^15.1.3",
94
+ "react-i18next": "^15.1.4",
95
95
  "react-object-model": "^1.2.20",
96
96
  "resolve-path": "^1.4.0",
97
97
  "sass": "^1.82.0",
package/process.d.ts CHANGED
@@ -2,6 +2,7 @@ import { type ChildProcess } from 'child_process';
2
2
  import './prototype.ts';
3
3
  import type { Encoding } from './file.ts';
4
4
  import type { MyProxy } from './net.ts';
5
+ export declare const sea: boolean;
5
6
  export declare const exe_nodejs: string;
6
7
  export declare const platform: NodeJS.Platform;
7
8
  export declare const username: string;
package/process.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { spawn } from 'child_process';
2
2
  import os from 'os';
3
+ import node_sea from 'node:sea';
3
4
  import "./prototype.js";
4
5
  import { inspect, DecoderStream, filter_values, check } from "./utils.js";
6
+ export const sea = node_sea.isSea();
5
7
  export const exe_nodejs = process.execPath.fp;
6
8
  export const platform = os.platform();
7
9
  export const username = os.userInfo().username;
package/server.d.ts CHANGED
@@ -27,6 +27,8 @@ export declare class Server {
27
27
  /** proxy 时需要丢弃的 resposne headers */
28
28
  static drop_response_headers: Set<string>;
29
29
  name: string;
30
+ /** sea 下最后修改时间,用于 http 资源缓存 */
31
+ last_modified_str?: string;
30
32
  UAParser: typeof UAParser;
31
33
  js_exts: Set<string>;
32
34
  empty_body_statuses: Set<number>;
@@ -115,27 +117,32 @@ export declare class Server {
115
117
  if (path.startsWith(prefix_docs)) {
116
118
  await this.try_send(
117
119
  ctx,
118
- `T:/t/docs${prefix_docs}`,
119
120
  (path.endsWith('/') ? `${path}index.html` : path).strip_start(prefix_docs),
120
- true
121
+ { fpd_root: `T:/t/docs${prefix_docs}` },
121
122
  )
122
123
  return
123
124
  } */
124
- try_send(ctx: Context, fpd_root: string, fp: string, log_404: boolean, assets_root?: string): Promise<boolean>;
125
+ try_send(ctx: Context, fp: string, { fpd_root, assets_root, sea: _sea, log_404, }?: {
126
+ fpd_root?: string;
127
+ assets_root?: string;
128
+ sea?: boolean;
129
+ log_404?: boolean;
130
+ }): Promise<boolean>;
125
131
  /** 将 body 设置为 fp 所指文件的 ReadStream
126
132
  检查 fp 是否合法,设置对应的 content-type,支持缓存,支持分块下载
127
133
  - ctx: Koa.Context
128
- - fp: 文件路径,有 root 时为相对路径或以 root 开头的绝对路径,无 root 时必须是绝对路径
134
+ - fp: 文件路径,有 fpd_root 时为相对路径或以 fpd_root 开头的绝对路径,无 fpd_root 时必须是绝对路径
129
135
  - options?:
130
- - root?: 只能访问 root 下面的文件
136
+ - fpd_root?: 只能访问 fpd_root 下面的文件
131
137
  - absolute?: `false` 使用绝对路径指定发送的文件
132
138
  - download?: `undefined` 在 response.headers 中加上 content-disposition: attachment 指示浏览器下载文件
133
139
  - assets_root?: 替换模板 xxx.template.html 中 {root} 占位符 */
134
- fsend(ctx: Context, fp: string, { root, absolute, download, assets_root, }?: {
135
- root?: string;
140
+ fsend(ctx: Context, fp: string, { fpd_root, absolute, download, assets_root, sea: _sea }?: {
141
+ fpd_root?: string;
136
142
  absolute?: boolean;
137
143
  download?: boolean;
138
144
  assets_root?: string;
145
+ sea?: boolean;
139
146
  }): Promise<string>;
140
147
  /** - range: 取值为逗号分割的多个可用端口或端口区间 (不能含有空格,包含区间右值),比如:`8321,8322,8300-8310,11000-11999
141
148
  - reverse?: `false` 在 range 内从后往前尝试 */
package/server.js CHANGED
@@ -5,11 +5,13 @@ import zlib from 'zlib';
5
5
  import fs from 'fs';
6
6
  import { buffer as stream_to_buffer } from 'stream/consumers';
7
7
  import util from 'util';
8
+ import node_sea from 'node:sea';
8
9
  // --- my libs
9
10
  import { t } from "./i18n/instance.js";
10
11
  import { request as _request, Remote } from "./net.js";
11
- import { inspect, output_width, check, range_to_numbers, encode, filter_keys, filter_values, consume_stream, assert } from "./utils.js";
12
+ import { inspect, output_width, check, range_to_numbers, encode, filter_keys, filter_values, consume_stream, decode } from "./utils.js";
12
13
  import { flist, fread, fstat } from "./file.js";
14
+ import { exe_nodejs, sea } from "./process.js";
13
15
  // ------------ my server
14
16
  export class Server {
15
17
  /** proxy 时需要丢弃的 resposne headers */
@@ -22,6 +24,8 @@ export class Server {
22
24
  'x-powered-by'
23
25
  ]);
24
26
  name;
27
+ /** sea 下最后修改时间,用于 http 资源缓存 */
28
+ last_modified_str;
25
29
  UAParser;
26
30
  js_exts = new Set(['.js', '.mjs', '.cjs']);
27
31
  empty_body_statuses = new Set([304, 204, 205]);
@@ -82,6 +86,9 @@ export class Server {
82
86
  const { default: Koa } = await import('koa');
83
87
  const { default: KoaCors } = await import('@koa/cors');
84
88
  const { default: KoaCompress } = await import('koa-compress');
89
+ if (sea)
90
+ this.last_modified_str = (await fstat(exe_nodejs))
91
+ .mtime.toUTCString();
85
92
  const { UAParser } = await import('ua-parser-js');
86
93
  this.UAParser = UAParser;
87
94
  // --- init koa app
@@ -534,20 +541,19 @@ export class Server {
534
541
  if (path.startsWith(prefix_docs)) {
535
542
  await this.try_send(
536
543
  ctx,
537
- `T:/t/docs${prefix_docs}`,
538
544
  (path.endsWith('/') ? `${path}index.html` : path).strip_start(prefix_docs),
539
- true
545
+ { fpd_root: `T:/t/docs${prefix_docs}` },
540
546
  )
541
547
  return
542
548
  } */
543
- async try_send(ctx, fpd_root, fp, log_404, assets_root) {
549
+ async try_send(ctx, fp, { fpd_root, assets_root, sea: _sea = sea, log_404 = true, } = {}) {
544
550
  const { request: { _path, path, method }, response, } = ctx;
545
551
  if (response.body !== undefined || response.status !== 404)
546
552
  return true;
547
553
  if (method !== 'HEAD' && method !== 'GET')
548
554
  return false;
549
555
  try {
550
- await this.fsend(ctx, fp, { root: fpd_root, assets_root });
556
+ await this.fsend(ctx, fp, { fpd_root, assets_root, sea: _sea });
551
557
  return true;
552
558
  }
553
559
  catch (error) {
@@ -565,34 +571,38 @@ export class Server {
565
571
  /** 将 body 设置为 fp 所指文件的 ReadStream
566
572
  检查 fp 是否合法,设置对应的 content-type,支持缓存,支持分块下载
567
573
  - ctx: Koa.Context
568
- - fp: 文件路径,有 root 时为相对路径或以 root 开头的绝对路径,无 root 时必须是绝对路径
574
+ - fp: 文件路径,有 fpd_root 时为相对路径或以 fpd_root 开头的绝对路径,无 fpd_root 时必须是绝对路径
569
575
  - options?:
570
- - root?: 只能访问 root 下面的文件
576
+ - fpd_root?: 只能访问 fpd_root 下面的文件
571
577
  - absolute?: `false` 使用绝对路径指定发送的文件
572
578
  - download?: `undefined` 在 response.headers 中加上 content-disposition: attachment 指示浏览器下载文件
573
579
  - assets_root?: 替换模板 xxx.template.html 中 {root} 占位符 */
574
- async fsend(ctx, fp, { root, absolute, download, assets_root, } = {}) {
575
- check(absolute || root?.isdir, t('fsend 必须传 absolute 选项或 root 文件夹'));
580
+ async fsend(ctx, fp, { fpd_root, absolute, download, assets_root, sea: _sea = sea } = {}) {
581
+ check(absolute || _sea || fpd_root?.isdir, t('fsend 必须传 absolute 选项, sea 选项, 或 fpd_root 文件夹'));
576
582
  const { request, request: { method } } = ctx;
577
583
  let { response } = ctx;
578
- if (!absolute) {
579
- fp = fp.strip_if_start(root).strip_if_start('/');
584
+ if (!absolute && !_sea) {
585
+ fp = fp.strip_if_start(fpd_root).strip_if_start('/');
580
586
  const { default: resolve_safely } = await import('resolve-path');
581
587
  try {
582
- fp = resolve_safely(root, fp).fp;
588
+ fp = resolve_safely(fpd_root, fp).fp;
583
589
  }
584
590
  catch (error) {
585
591
  error.message += `, fp: ${fp}`;
586
592
  throw error;
587
593
  }
588
594
  }
589
- // stat
595
+ // stats, asset
590
596
  let stats;
597
+ let asset;
591
598
  try {
592
- stats = await fstat(fp);
599
+ if (_sea)
600
+ asset = new Uint8Array(node_sea.getRawAsset(fp));
601
+ else
602
+ stats = await fstat(fp);
593
603
  }
594
604
  catch (error) {
595
- if (['ENOENT', 'ENAMETOOLONG', 'ENOTDIR'].includes(error.code)) {
605
+ if (_sea || ['ENOENT', 'ENAMETOOLONG', 'ENOTDIR'].includes(error.code)) {
596
606
  error.status = 404;
597
607
  throw error;
598
608
  }
@@ -600,6 +610,7 @@ export class Server {
600
610
  error.message = `fs.stat 出错: ${error.message}`;
601
611
  throw error;
602
612
  }
613
+ const size = _sea ? asset.length : Number(stats.size);
603
614
  // 上面的 fstat 异步操作之后有可能 socket 已经关闭,那么这里什么都不做,直接结束吧
604
615
  // 如果不这么做,后续 createReadStream 打开的流会赋给 response.body,但是 response.res 不再触发 finish, close 事件,
605
616
  // 打开的流永远不会关闭,最终导致资源泄露,文件句柄一直打开
@@ -613,7 +624,9 @@ export class Server {
613
624
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#up-to-date_contents_always
614
625
  if (!response.get('cache-control'))
615
626
  response.set('cache-control', 'no-cache');
616
- const last_modified_str = stats.mtime.toUTCString();
627
+ const last_modified_str = _sea
628
+ ? this.last_modified_str
629
+ : stats.mtime.toUTCString();
617
630
  if (!response.get('last-modified'))
618
631
  response.set('last-modified', last_modified_str);
619
632
  if (download)
@@ -632,8 +645,9 @@ export class Server {
632
645
  }
633
646
  if (assets_root) {
634
647
  check(fp.endsWith('.html'), '传入 assets_root 参数时 fp 应该为 .html 文件');
635
- response.body = (await fread(fp, { print: false }))
636
- .replaceAll('{root}', assets_root);
648
+ response.body = (_sea
649
+ ? decode(asset)
650
+ : (await fread(fp, { print: false }))).replaceAll('{root}', assets_root);
637
651
  return fp;
638
652
  }
639
653
  const range_header = request.headers.range;
@@ -641,28 +655,29 @@ export class Server {
641
655
  try {
642
656
  const range_value = /=(.*)$/.exec(range_header)[1];
643
657
  const range = /^[\w]*?(\d*)-(\d*)$/.exec(range_value);
644
- assert(stats.size <= Number.MAX_SAFE_INTEGER);
645
658
  let start = range[1] ? Number(range[1]) : undefined;
646
- let end = range[2] ? Number(range[2]) : Number(stats.size) - 1;
659
+ let end = range[2] ? Number(range[2]) : size - 1;
647
660
  if (start === undefined) {
648
- start = Number(stats.size) - end;
649
- end = Number(stats.size) - 1;
661
+ start = size - end;
662
+ end = size - 1;
650
663
  }
651
664
  const chunksize = (end - start + 1);
652
665
  response.status = 206;
653
666
  response.set('content-length', String(chunksize));
654
- response.set('content-range', `bytes ${start}-${end}/${stats.size}`);
655
- response.body = fs.createReadStream(fp, { start, end });
667
+ response.set('content-range', `bytes ${start}-${end}/${size}`);
668
+ response.body = _sea
669
+ ? asset.subarray(start, end)
670
+ : fs.createReadStream(fp, { start, end });
656
671
  }
657
672
  catch {
658
673
  response.status = 416;
659
- response.set('content-length', String(stats.size));
660
- response.set('content-range', `bytes */${stats.size}`);
674
+ response.set('content-length', String(size));
675
+ response.set('content-range', `bytes */${size}`);
661
676
  response.body = fs.createReadStream(fp);
662
677
  }
663
678
  else {
664
- response.set('content-length', String(stats.size));
665
- response.body = fs.createReadStream(fp);
679
+ response.set('content-length', String(size));
680
+ response.body = _sea ? asset : fs.createReadStream(fp);
666
681
  }
667
682
  return fp;
668
683
  }