mc-tunnel 0.2.1 → 0.2.3

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/README.md CHANGED
@@ -9,6 +9,7 @@
9
9
  > (详见 [合规说明](#合规说明))。
10
10
  >
11
11
  > 📖 完整使用文档(全部参数、事件、错误码、Electron 集成、排错表)见 [docs/USAGE.md](docs/USAGE.md)。
12
+ > 🔧 口令模式与中文房间码的源码级实现说明(位布局、算法、行号索引)见 [docs/PASSPHRASE-ZH-CODE.md](docs/PASSPHRASE-ZH-CODE.md)。
12
13
 
13
14
  ---
14
15
 
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.MC_WATCH_INTERVAL_MS = exports.HEARTBEAT_INTERVAL_MS = exports.DEFAULT_MOTD = exports.DEFAULT_COORD_PORT = exports.HOST_VIRTUAL_IP = exports.VERSION = void 0;
5
5
  /** 与 package.json 的 version 保持一致(发布前请同步)。 */
6
- exports.VERSION = '0.2.1';
6
+ exports.VERSION = '0.2.3';
7
7
  /**
8
8
  * 房主在虚拟网络中的固定地址。
9
9
  * 10.144.144.0/24 是 EasyTier 的默认虚拟网段,固定 .1 可以让客人无需任何发现协议
@@ -27,15 +27,17 @@ const PASSPHRASE_LABEL = 'mc-tunnel/v1/passphrase';
27
27
  const SEED_MASK = (1n << 64n) - 1n;
28
28
  /**
29
29
  * 口令归一化,让「同一个口令的不同写法」落到同一个种子:
30
- * - NFKC:全角字符折成半角(`ABC` → `ABC`,`123` → `123`)
31
- * - 首尾去空白、中间连续空白折成一个空格
30
+ * - NFKC:全角字符折成半角(`ABC` → `ABC`,`123` → `123`,全角空格 → 半角空格)
31
+ * - **所有空白一律删除**:中文短语里的空格、换行、制表符只是排版噪音,
32
+ * 「为理云鬓 为簪银钩」与「为理云鬓为簪银钩」视为同一口令;
33
+ * 英文里 `hello world` 与 `helloworld` 同理等价
32
34
  * - 统一小写(英文大小写不敏感,对中文无影响)
33
35
  */
34
36
  function normalizePassphrase(input) {
35
37
  if (typeof input !== 'string') {
36
38
  return '';
37
39
  }
38
- return input.normalize('NFKC').trim().replace(/\s+/g, ' ').toLowerCase();
40
+ return input.normalize('NFKC').replace(/\s+/g, '').toLowerCase();
39
41
  }
40
42
  /**
41
43
  * 口令 → 64 bit 种子。
@@ -1 +1 @@
1
- {"version":3,"file":"passphrase.js","sourceRoot":"","sources":["../../src/passphrase.ts"],"names":[],"mappings":";;AA8BA,kDAMC;AAMD,gDAUC;AAQD,wDAEC;AAQD,gDAiCC;AAvGD;;;;;;;;;;;;;GAaG;AACH,6CAAyC;AACzC,2CAA0C;AAC1C,iDAAkD;AAElD,mCAAmC;AACnC,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AAEnD,mBAAmB;AACnB,MAAM,SAAS,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC;AAEnC;;;;;GAKG;AACH,SAAgB,mBAAmB,CAAC,KAAa;IAC/C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;AAC3E,CAAC;AAED;;;GAGG;AACH,SAAgB,kBAAkB,CAAC,UAAkB;IACnD,MAAM,IAAI,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAE7C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,uBAAW,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,MAAM,GAAG,IAAA,wBAAU,EAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;IAEpF,OAAO,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,SAAgB,sBAAsB,CAAC,UAAkB;IACvD,OAAO,IAAA,+BAAgB,EAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;GAKG;AACH,SAAgB,kBAAkB,CAAC,UAAkB;IACnD,6CAA6C;IAC7C,MAAM,IAAI,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAChE,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAE5B,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;QACjB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IACxE,0CAA0C;IAC1C,MAAM,SAAS,GAAG,GAAG,GAAG,MAAM,IAAI,GAAG,CAAC;IAEtC,IAAI,SAAS,EAAE,CAAC;QACd,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YACf,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,OAAO,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;IACvC,CAAC;IAED,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QACf,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAExF,IAAI,MAAM,IAAI,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE,IAAI,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC;QACnD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"passphrase.js","sourceRoot":"","sources":["../../src/passphrase.ts"],"names":[],"mappings":";;AAgCA,kDAMC;AAMD,gDAUC;AAQD,wDAEC;AAQD,gDAiCC;AAzGD;;;;;;;;;;;;;GAaG;AACH,6CAAyC;AACzC,2CAA0C;AAC1C,iDAAkD;AAElD,mCAAmC;AACnC,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AAEnD,mBAAmB;AACnB,MAAM,SAAS,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC;AAEnC;;;;;;;GAOG;AACH,SAAgB,mBAAmB,CAAC,KAAa;IAC/C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AACnE,CAAC;AAED;;;GAGG;AACH,SAAgB,kBAAkB,CAAC,UAAkB;IACnD,MAAM,IAAI,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAE7C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,uBAAW,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,MAAM,GAAG,IAAA,wBAAU,EAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;IAEpF,OAAO,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,SAAgB,sBAAsB,CAAC,UAAkB;IACvD,OAAO,IAAA,+BAAgB,EAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;GAKG;AACH,SAAgB,kBAAkB,CAAC,UAAkB;IACnD,6CAA6C;IAC7C,MAAM,IAAI,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAChE,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAE5B,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;QACjB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IACxE,0CAA0C;IAC1C,MAAM,SAAS,GAAG,GAAG,GAAG,MAAM,IAAI,GAAG,CAAC;IAEtC,IAAI,SAAS,EAAE,CAAC;QACd,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YACf,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,OAAO,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;IACvC,CAAC;IAED,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QACf,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAExF,IAAI,MAAM,IAAI,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE,IAAI,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC;QACnD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  /** 全局常量。 */
2
2
  /** 与 package.json 的 version 保持一致(发布前请同步)。 */
3
- export const VERSION = '0.2.1';
3
+ export const VERSION = '0.2.3';
4
4
  /**
5
5
  * 房主在虚拟网络中的固定地址。
6
6
  * 10.144.144.0/24 是 EasyTier 的默认虚拟网段,固定 .1 可以让客人无需任何发现协议
@@ -21,15 +21,17 @@ const PASSPHRASE_LABEL = 'mc-tunnel/v1/passphrase';
21
21
  const SEED_MASK = (1n << 64n) - 1n;
22
22
  /**
23
23
  * 口令归一化,让「同一个口令的不同写法」落到同一个种子:
24
- * - NFKC:全角字符折成半角(`ABC` → `ABC`,`123` → `123`)
25
- * - 首尾去空白、中间连续空白折成一个空格
24
+ * - NFKC:全角字符折成半角(`ABC` → `ABC`,`123` → `123`,全角空格 → 半角空格)
25
+ * - **所有空白一律删除**:中文短语里的空格、换行、制表符只是排版噪音,
26
+ * 「为理云鬓 为簪银钩」与「为理云鬓为簪银钩」视为同一口令;
27
+ * 英文里 `hello world` 与 `helloworld` 同理等价
26
28
  * - 统一小写(英文大小写不敏感,对中文无影响)
27
29
  */
28
30
  export function normalizePassphrase(input) {
29
31
  if (typeof input !== 'string') {
30
32
  return '';
31
33
  }
32
- return input.normalize('NFKC').trim().replace(/\s+/g, ' ').toLowerCase();
34
+ return input.normalize('NFKC').replace(/\s+/g, '').toLowerCase();
33
35
  }
34
36
  /**
35
37
  * 口令 → 64 bit 种子。
@@ -1 +1 @@
1
- {"version":3,"file":"passphrase.js","sourceRoot":"","sources":["../../src/passphrase.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"passphrase.js","sourceRoot":"","sources":["../../src/passphrase.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,mCAAmC;AACnC,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AAEnD,mBAAmB;AACnB,MAAM,SAAS,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC;AAEnC;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AACnE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAAkB;IACnD,MAAM,IAAI,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAE7C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,WAAW,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;IAEpF,OAAO,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,UAAkB;IACvD,OAAO,gBAAgB,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAAkB;IACnD,6CAA6C;IAC7C,MAAM,IAAI,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAChE,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAE5B,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;QACjB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IACxE,0CAA0C;IAC1C,MAAM,SAAS,GAAG,GAAG,GAAG,MAAM,IAAI,GAAG,CAAC;IAEtC,IAAI,SAAS,EAAE,CAAC;QACd,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YACf,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,OAAO,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;IACvC,CAAC;IAED,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QACf,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAExF,IAAI,MAAM,IAAI,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE,IAAI,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC;QACnD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  /** 全局常量。 */
2
2
  /** 与 package.json 的 version 保持一致(发布前请同步)。 */
3
- export declare const VERSION = "0.2.1";
3
+ export declare const VERSION = "0.2.3";
4
4
  /**
5
5
  * 房主在虚拟网络中的固定地址。
6
6
  * 10.144.144.0/24 是 EasyTier 的默认虚拟网段,固定 .1 可以让客人无需任何发现协议
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * 口令归一化,让「同一个口令的不同写法」落到同一个种子:
3
- * - NFKC:全角字符折成半角(`ABC` → `ABC`,`123` → `123`)
4
- * - 首尾去空白、中间连续空白折成一个空格
3
+ * - NFKC:全角字符折成半角(`ABC` → `ABC`,`123` → `123`,全角空格 → 半角空格)
4
+ * - **所有空白一律删除**:中文短语里的空格、换行、制表符只是排版噪音,
5
+ * 「为理云鬓 为簪银钩」与「为理云鬓为簪银钩」视为同一口令;
6
+ * 英文里 `hello world` 与 `helloworld` 同理等价
5
7
  * - 统一小写(英文大小写不敏感,对中文无影响)
6
8
  */
7
9
  export declare function normalizePassphrase(input: string): string;
@@ -0,0 +1,337 @@
1
+ # 口令模式 与 中文房间码 —— 实现说明
2
+
3
+ > 面向**维护者**的源码级说明:这两个功能怎么实现、位是怎么排的、改哪里会炸。
4
+ > 使用层面的教程看 [`USAGE.md`](./USAGE.md) 的 §9.5 / §9.6;本文只讲「为什么这样写 + 代码在哪」。
5
+ >
6
+ > 对应版本:`mc-tunnel@0.2.2`
7
+
8
+ ---
9
+
10
+ ## 0. 一张图看懂四个概念
11
+
12
+ ```
13
+ ┌──────────────┐
14
+ 口令(用户输入)─┤ HMAC-SHA256 ├──► 64 bit 种子 ──┐
15
+ │ 域分离标签 │ │
16
+ └──────────────┘ │
17
+ ▼
18
+ ┌──────────────────────────┐
19
+ 随机生成 ─────────────────────────► │ 80 bit 房间码数值 │
20
+ │ version(4) | seed(64) | │
21
+ │ crc(12) │
22
+ └───────┬──────────┬───────┘
23
+ │ │
24
+ 16 × 5 bit │ │ 7 × 11 bit
25
+ ▼ ▼
26
+ 2B31-KGB6-... 截 耗 湿 香 苯 见 烧
27
+ (Base32 标准码) (中文码,彩蛋)
28
+ ```
29
+
30
+ **核心思想**:房间码不是「字符串」,而是 **80 bit 整数**。口令、标准码、中文码只是同一个整数的三种「表面表示」,彼此可无损互转。网络名 / 网络密钥 / 握手探针 / 访问令牌全部由这个整数里的 64 bit 种子派生,所以三种入口天然等价。
31
+
32
+ ---
33
+
34
+ ## 1. 80 bit 的精确位布局
35
+
36
+ 定义在 [`src/room-code.ts`](../src/room-code.ts) 第 30–41 行:
37
+
38
+ | 常量 | 值 | 含义 |
39
+ |---|---|---|
40
+ | `VERSION` | `1n` | 编码版本(当前只有 1) |
41
+ | `VERSION_BITS` | `4n` | 版本号占 4 bit |
42
+ | `SEED_BITS` | `64n` | 种子占 64 bit |
43
+ | `CRC_BITS` | `12n` | 校验占 12 bit |
44
+ | `CRC_POLY` | `0x80f` | CRC-12 多项式 |
45
+ | `CHAR_COUNT` | `16` | Base32 字符数 |
46
+ | `CODE_LENGTH` | `19` | 展示长度(16 + 3 个连字符) |
47
+
48
+ 位排布(bit 0 为最低位):
49
+
50
+ ```
51
+ bit 79 ───────────── 76 75 ─────────────────────────────── 12 11 ────── 0
52
+ ┌──────────────┬───────────────────────────────────────┬────────────┐
53
+ │ version(4) │ seed(64) │ crc(12) │
54
+ └──────────────┴───────────────────────────────────────┴────────────┘
55
+ version = 1 → 0001,所以 bit 79..77 恒为 0
56
+ ```
57
+
58
+ **关键推论**:版本号为 1 时,整个 80 bit 数值实际 < 2⁷⁷。这 3 个恒零位是中文码能压到 7 个字的前提(见 §3)。
59
+
60
+ 编码流程(`buildValue` → `encodeChars` → `group`,同文件):
61
+
62
+ ```
63
+ body = (1 << 76) | (seed << 12)
64
+ crc = crc12(body >> 12, 76) // 对「版本+种子」这 76 bit 算 CRC-12
65
+ value = body | crc
66
+ code = 每 5 bit 取一格,映射到 ALPHABET,再按 4 字符分组,用 '-' 连接
67
+ ```
68
+
69
+ ---
70
+
71
+ ## 2. 口令模式
72
+
73
+ ### 2.1 算法
74
+
75
+ 源码:[`src/passphrase.ts`](../src/passphrase.ts)
76
+
77
+ ```
78
+ 归一化(NFKC → 删除所有空白 → 小写)
79
+ ↓
80
+ HMAC-SHA256(key = 'mc-tunnel/v1/passphrase', msg = 归一化后的文本)
81
+ ↓ 取前 8 字节
82
+ 64 bit 种子 → roomCodeFromSeed(seed) → 标准房间码
83
+ → deriveRoomSecrets() → probe / token(与随机码完全一致)
84
+ ```
85
+
86
+ | 符号 | 位置 | 说明 |
87
+ |---|---|---|
88
+ | `PASSPHRASE_LABEL` | `passphrase.ts:20` | 域分离标签 `'mc-tunnel/v1/passphrase'` |
89
+ | `normalizePassphrase` | `passphrase.ts:33` | NFKC → `replace(/\s+/g, '')` → `toLowerCase()` |
90
+ | `seedFromPassphrase` | `passphrase.ts:45` | 空口令抛 `INVALID_PASSPHRASE`,否则 HMAC 取前 8 字节 |
91
+ | `roomCodeFromPassphrase` | `passphrase.ts:63` | `roomCodeFromSeed(seedFromPassphrase(x))` |
92
+ | `passphraseStrength` | `passphrase.ts:73` | 展示用分档,不参与任何派生 |
93
+
94
+ ### 2.2 为什么空白是「删除」而不是「折叠」
95
+
96
+ 0.2.1 之前实现是 `trim().replace(/\s+/, ' ')`,结果:
97
+
98
+ ```
99
+ 为理云鬓为簪银钩明月可鉴情深亦寿 → 2B31-KGB6-KDKC-E8Q7
100
+ 为理云鬓 为簪银钩 明月可鉴 情深亦寿 → 2W81-12J6-N325-N75J ← 不同房间!
101
+ ```
102
+
103
+ 用户从聊天记录复制、手机粘贴带换行、输入法打全角空格,都会落到不同房间;而且**表现极具误导性**:传输层能连上(peer 列表里有对方),但虚拟 IP 对不上,报 `PEER_NOT_FOUND: 已发现 2 个节点,但房主(10.144.144.1)尚未出现`。
104
+
105
+ 0.2.2 起改为**删除所有空白**,`NFKC` 已先把全角空格(U+3000)折成半角空格,正则 `\s` 再统一清掉。代价是「`hello world` 与 `helloworld` 等价」,对接入口令来说是纯收益。
106
+
107
+ ### 2.3 强度评估(仅 UI 提示)
108
+
109
+ `passphraseStrength` 不计空白;汉字占比 ≥ 60% 时用更短阈值:
110
+
111
+ | 分支 | weak | fair | good |
112
+ |---|---|---|---|
113
+ | 以汉字为主 | < 4 字 | 4–5 字 | ≥ 6 字 |
114
+ | 其它 | < 8 字符 | 8–15 字符 / 类别 < 2 | ≥ 16 字符,或 ≥ 10 字符且类别 ≥ 2 |
115
+
116
+ > 提醒:这里只是「看起来够不够长」,与真实熵无关。口令模式的真实强度 = 口令的猜测难度。
117
+
118
+ ### 2.4 会话层集成
119
+
120
+ | 位置 | 代码 |
121
+ |---|---|
122
+ | `src/types.ts:40-48` | `HostOptions.passphrase?: string`(与 `roomCode` 二选一) |
123
+ | `src/types.ts:75-78` | `GuestOptions.passphrase?: string`(`roomCode` 改为可选) |
124
+ | `src/host.ts:55-61` | 两者同给 → `INVALID_OPTIONS`;有口令 → `parseRoomCode(roomCodeFromPassphrase(passphrase))` |
125
+ | `src/guest.ts:64-74` | 同给 → `INVALID_OPTIONS`;都不给 → `INVALID_OPTIONS`;其余同上 |
126
+ | `src/index.ts:27-32` | 四个口令函数从主入口导出 |
127
+ | `src/errors.ts:7-8` | 新增 `INVALID_PASSPHRASE` / `INVALID_OPTIONS` |
128
+
129
+ 会话对象本身不加字段:口令模式的房主 `host.roomCode` 就是派生出来的标准码,客人反之亦然——**上层代码无需感知当前用的是哪种入口**。
130
+
131
+ ---
132
+
133
+ ## 3. 中文房间码
134
+
135
+ ### 3.1 为什么正好是 7 个字
136
+
137
+ 源码:[`src/chinese-code.ts`](../src/chinese-code.ts)
138
+
139
+ ```
140
+ 2048 = 2^11 → 每个汉字携带 11 bit
141
+ 7 个字 = 2^77 → 恰好覆盖「版本号为 1」时的有效位宽
142
+ ```
143
+
144
+ 位映射(第 k 个字,1-based,从左到右):
145
+
146
+ | 字序 | 覆盖 bit | 说明 |
147
+ |---|---|---|
148
+ | 第 1 字 | 66–76 | 含版本位(bit 76),所以它只可能是字表后半段 |
149
+ | 第 2 字 | 55–65 | |
150
+ | 第 3 字 | 44–54 | |
151
+ | 第 4 字 | 33–43 | |
152
+ | 第 5 字 | 22–32 | |
153
+ | 第 6 字 | 11–21 | **跨 CRC/payload 边界(bit 11)**,见 §5.2 |
154
+ | 第 7 字 | 0–10 | 全在 CRC 区内,抄错必被发现 |
155
+
156
+ **自查位映射**(5 行,可随时验证实现与本文一致):
157
+
158
+ ```js
159
+ const chars = Array.from(toChineseRoomCode(code));
160
+ const value = roomValueFromCode(code);
161
+ chars.every((c, i) =>
162
+ BigInt(ZH_ALPHABET.indexOf(c)) === ((value >> BigInt(11 * (6 - i))) & 2047n)); // => true
163
+ ```
164
+
165
+ 编码:
166
+
167
+ ```ts
168
+ // toChineseRoomCode,chinese-code.ts:163
169
+ value = roomValueFromCode(code) // 校验版本 + CRC,非法直接抛
170
+ if (value >= 2^77) throw INVALID_ROOM_CODE // 版本 ≥ 2 装不下(chinese-code.ts:166)
171
+ for i in 6..0: 第 (7-i) 个字 = CHARS[(value >> 11i) & 2047]
172
+ ```
173
+
174
+ 解码:
175
+
176
+ ```ts
177
+ // parseChineseRoomCode,chinese-code.ts:117
178
+ value = Σ (index(第 k 个字) << 11*(7-k))
179
+ isValidRoomValue(value) 否则抛 INVALID_ROOM_CODE // 复用 room-code.ts:253
180
+ return roomCodeFromValue(value) // 回到标准码
181
+ ```
182
+
183
+ ### 3.2 字表与繁简映射怎么来的(可复现)
184
+
185
+ | 文件 | 内容 | 生成方式 |
186
+ |---|---|---|
187
+ | [`src/data/bip39-zh.ts`](../src/data/bip39-zh.ts) | `BIP39_ZH`:2048 个去重单字(一行长字符串) | 从 npm 包 `@scure/bip39` 的 `wordlists/simplified-chinese.js` 提取模板字符串 |
188
+ | [`src/data/zh-variants.ts`](../src/data/zh-variants.ts) | `ZH_VARIANT_PAIRS`:773 条 繁→简,两字一组 | 逐位比较 `chinese_simplified` 与 `chinese_traditional` 两张表得出(两表**顺序完全一致**,下标相同即同一字的不同字形) |
189
+
190
+ 生成脚本是一次性的(未入库),核心逻辑:
191
+
192
+ ```js
193
+ // 1) 取简体表
194
+ const words = extract('simplified-chinese.js'); // 2048 条,每条 1 个字
195
+ // 2) 取繁体表,同下标比较,取出字形差异
196
+ const pairs = simplified.map((s, i) => traditional[i] === s ? null : traditional[i] + s).filter(Boolean);
197
+ // 3) 写成 TS 常量(显式 : string,避免 TS 把 2048 字内联成字面量类型)
198
+ ```
199
+
200
+ > 为什么要 `: string` 注解:不加注解时 TypeScript 会为这个常量生成**字面量类型**,`dist/types/data/bip39-zh.d.ts` 会膨胀到 12.9 kB;加注解后是 585 B。
201
+
202
+ **顺序即契约**:词表顺序不能改,改一个字 → 所有已发出的中文码指向别的房间。
203
+
204
+ ### 3.3 归一化与报错
205
+
206
+ `stripSeparators`(`chinese-code.ts:50`):`NFKC` 后删除所有 `\p{P}`(标点)、`\p{S}`(符号)、`\s`(空白)。
207
+ `mapChar`(`chinese-code.ts:55`):先查 2048 字表,再查繁简映射,都失败返回 `undefined`。
208
+
209
+ | 输入 | 结果 |
210
+ |---|---|
211
+ | `'朱 上 岸 雪 存 违 户'` | ✅ 空格被忽略 |
212
+ | `'朱上岸雪存违户。'` | ✅ 中文句号被忽略 |
213
+ | `' 朱上岸雪存违户,'` | ✅ 全角空格/标点被忽略 |
214
+ | `'這'`(繁体) | ✅ 折叠成 `这` 后命中 |
215
+ | `'的一是'` | ❌ `中文房间码应为 7 个字,当前是 3 个字` |
216
+ | `'…违龘'` | ❌ `第 7 个字「龘」不在房间码字符集中` |
217
+ | `'…违這'`(`这` 不在当前字表时) | ❌ `第 7 个字「這」不在字符集中,是不是想输「这」?` |
218
+ | 字表内但 CRC/版本不合法 | ❌ `中文房间码校验失败,可能抄错了某个字` |
219
+
220
+ ### 3.4 会话层用法
221
+
222
+ 中文码没有塞进 `createGuest` 的入参(那会让主入口被迫加载 12 KB 字表),而是走**子入口**:
223
+
224
+ ```jsonc
225
+ // package.json → exports
226
+ "./zh": {
227
+ "types": "./dist/types/chinese-code.d.ts",
228
+ "import": "./dist/esm/chinese-code.js",
229
+ "require": "./dist/cjs/chinese-code.js"
230
+ }
231
+ ```
232
+
233
+ 调用方自己转换:
234
+
235
+ ```ts
236
+ const guest = await createGuest({ roomCode: parseChineseRoomCode('朱上岸雪存违户') });
237
+ ```
238
+
239
+ ---
240
+
241
+ ## 4. 对外 API 一览
242
+
243
+ | 入口 | 符号 | 行号 |
244
+ |---|---|---|
245
+ | `mc-tunnel` | `normalizePassphrase` | `passphrase.ts:33` |
246
+ | | `seedFromPassphrase` | `passphrase.ts:45` |
247
+ | | `roomCodeFromPassphrase` | `passphrase.ts:63` |
248
+ | | `passphraseStrength` | `passphrase.ts:73` |
249
+ | `mc-tunnel/zh` | `ZH_ALPHABET` / `ZH_CODE_LENGTH` | `chinese-code.ts:44,47` |
250
+ | | `normalizeChineseRoomCode` | `chinese-code.ts:75` |
251
+ | | `isChineseRoomCode` | `chinese-code.ts:102` |
252
+ | | `parseChineseRoomCode` | `chinese-code.ts:117` |
253
+ | | `toChineseRoomCode` | `chinese-code.ts:163` |
254
+ | | `formatChineseRoomCode` | `chinese-code.ts:185` |
255
+ | 包内(未从入口导出) | `roomValueFromSeed` / `roomSeedFromValue` / `roomCodeFromValue` / `roomCodeFromSeed` / `roomValueFromCode` / `isValidRoomValue` | `room-code.ts:222-266` |
256
+
257
+ > 那 6 个「包内」函数是为了让口令与中文码**复用同一份**版本/CRC 实现。不导出是刻意的:它们是「80 bit 数值层」的裸操作,暴露出去容易让人绕过校验。
258
+
259
+ ---
260
+
261
+ ## 5. 已知风险与设计取舍
262
+
263
+ ### 5.1 口令的强度不等于种子强度
264
+
265
+ 种子永远是 64 bit,但口令是**人选的**。`1234`、`芝麻开门` 这类口令等于公开房间;攻击者还可以离线爆破口令 → 房间码。设计上接受这一点,因为口令模式定位是「少记一串随机码」的便利选项,安全场景请用随机房间码。
266
+
267
+ ### 5.2 CRC-12 没有想象中强(实测)
268
+
269
+ 多项式 `0x80F` 含 `(x+1)` 因子,对进位链式错误存在系统性漏检。实测(本仓库 `test/room-code.test.mjs`、`test/chinese-code.test.mjs`):
270
+
271
+ | 错误类型 | 拒绝率 |
272
+ |---|---|
273
+ | 标准码随机换 1 个字符(3000 样本) | 99.73% |
274
+ | 中文码随机换 1 个字(5000 样本) | 100% |
275
+ | 标准码固定 `+1` 偏移(各位置穷举) | 100% |
276
+ | 中文码固定 `+1` 偏移 @ 第 6 字 | **仅 75.5%**(漏检 24.5%) |
277
+ | 中文码固定 `+1` 偏移 @ 其它 6 个字 | 100% |
278
+
279
+ 第 6 个字跨在 CRC 与 payload 的边界(bit 11)上,`+1` 会同时改动 CRC 最高位并进位到 payload,恰好落进多项式的倍式集合。**这是既有格式的固有属性**:改 CRC 会让所有已发布房间码失效,因此只在文档层面如实标注,不改实现。
280
+
281
+ ### 5.3 中文码只覆盖 version ≤ 1
282
+
283
+ `2^77` 装得下版本 0/1。若将来把 `VERSION` 升到 ≥ 2(第 78/79 位被使用),7 个字装不下——`toChineseRoomCode` 会明确抛错(`chinese-code.ts:166`),而不是悄悄截断。届时的升级路径:改成 8 个字(`2048^8 = 2^88`),并同时保留旧 7 字解码分支。
284
+
285
+ ### 5.4 中文码是「单字」不是「词」
286
+
287
+ BIP39 表是 2048 个单字,摊在约 400 个拼音音节上,同音字极多。适合复制/扫码/点选,**不适合电话口述**。若要口述友好,需要自建双字词表(`cfbao/chinese-diceware` 的 8192 词、CC BY 4.0,约 108 KB),那是另一套编码方案。
288
+
289
+ ---
290
+
291
+ ## 6. 测试映射
292
+
293
+ | 测试文件 | 用例 | 覆盖点 |
294
+ |---|---|---|
295
+ | `test/passphrase.test.mjs` | 口令派生的房间码合法且稳定 | 同口令同码、`verifyRoomCode` 通过 |
296
+ | | 口令归一化:大小写、空白、全角 | 空格/换行/全角空格/大小写等价,中文短语等价 |
297
+ | | 不同口令派生不同房间码 | 6 个不同口令 → 6 个不同码 |
298
+ | | 口令码与标准码等价 | 派生出的 `networkName` / `networkSecret` 一致 |
299
+ | | 空口令抛 INVALID_PASSPHRASE | `''` / 空格 / 全角空格 |
300
+ | | 口令强度分档 | 含中文口令不被误判为 weak |
301
+ | | 一万次不同口令 | 无重复、全部合法 |
302
+ | `test/chinese-code.test.mjs` | 标准码 ⇄ 中文码 双向无损(一万次) | 每字都在字表内、往返还原 |
303
+ | | 口令码同样可以转成中文码 | 与口令模式串联 |
304
+ | | 展示格式按字分隔 | `formatChineseRoomCode` |
305
+ | | 单字篡改几乎全部被 CRC 拒绝 | 5000 样本随机替换 |
306
+ | | 繁体/全角/空格/标点容错 | 773 条繁简映射抽样命中 |
307
+ | | 表外字报错并指出位置 | 错误文案含「第 N 个字」 |
308
+ | | 字数不符 / 非法 CRC | 明确报错 |
309
+ | `test/e2e.test.mjs` | 端到端:口令模式双方同一口令直连 | 真起 2 个 `easytier-core`,隧道内 ping 通假 MC 服务端 |
310
+
311
+ 运行:`npm run build && npm test`(端到端用例缺 EasyTier 二进制时自动跳过)。
312
+
313
+ ---
314
+
315
+ ## 7. 改动指南(改哪里会炸什么)
316
+
317
+ | 想做的事 | 要动的地方 | 影响 |
318
+ |---|---|---|
319
+ | 换中文码字表 | `src/data/bip39-zh.ts` + 重新生成 `zh-variants.ts` | **所有已发出的中文码失效**(标准码/口令不受影响);语义版本应升 minor |
320
+ | 调整口令归一化规则 | `src/passphrase.ts:33` | 所有口令房间码改变;须同步 `test/passphrase.test.mjs` 与 `USAGE.md` §9.5 |
321
+ | 改域分离标签 | `src/passphrase.ts:20` | 同上(标签换了等于换算法) |
322
+ | 升级编码 version | `src/room-code.ts:30` | 所有房间码格式变化;中文码需扩到 8 字;必须保留旧版本解码分支 |
323
+ | 想加新的「表面表示」(如 emoji 码) | 新增模块 + `package.json` `exports` 子入口,复用 §1 的 80 bit 数值层 | 不影响既有格式,注意别从主入口引入大体积数据 |
324
+ | 提升校验强度 | 需新增 version,改用更强 CRC(如 CRC-16)或多位校验 | 破坏兼容,务必老码可读 |
325
+
326
+ ---
327
+
328
+ ## 8. 与其他文档的关系
329
+
330
+ | 文件 | 定位 |
331
+ |---|---|
332
+ | `README.md` | 首页卖点 + 最短示例(口令 / 中文码各一段) |
333
+ | `docs/USAGE.md` §9.5 §9.6 | 面向使用者的参数、示例、容错范围、限制 |
334
+ | **本文** | 面向维护者的算法、位布局、源码行号、改动风险 |
335
+ | `THIRD_PARTY_NOTICES.md` | EasyTier 与 Minecraft 的合规声明 |
336
+
337
+ > 若后续修改了 `src/passphrase.ts` / `src/chinese-code.ts` 的行号范围,请同步更新本文 §2.1 / §3 / §4 的表格——本文是按行号索引的,行号漂移会让人找不到代码。
package/docs/USAGE.md CHANGED
@@ -602,11 +602,14 @@ const guest = await createGuest({ passphrase: ' 彩蛋口令 演示 ' });
602
602
  原理:
603
603
 
604
604
  ```
605
- 口令 → NFKC 归一化 → 小写 / 折叠空白
605
+ 口令 → NFKC 归一化 → 删除所有空白 → 小写
606
606
  → HMAC-SHA256('mc-tunnel/v1/passphrase', 口令) 前 8 字节 = 64 bit 种子
607
607
  → 与随机房间码走**完全相同**的派生链(网络名 / 网络密钥 / probe / token)
608
608
  ```
609
609
 
610
+ 空白是**完全忽略**的(不是折叠成一个空格):`为理云鬓 为簪银钩` 与 `为理云鬓为簪银钩`
611
+ 等价,`hello world` 与 `helloworld` 也等价——这样从聊天记录、换行、全角空格里复制粘贴都不会出错。
612
+
610
613
  因此「口令」和「房间码」是同一个种子的两种表达方式,两者可互换:
611
614
 
612
615
  ```ts
@@ -621,6 +624,7 @@ roomCodeFromPassphrase('彩蛋口令 演示'); // => '2GJY-DYSK-8825-DZJY' 这
621
624
  | `passphraseStrength(text)` | `'weak' \| 'fair' \| 'good'`,纯中文按更短阈值评估(<4 弱、≥6 好) |
622
625
 
623
626
  > ⚠️ 安全边界:口令的强度**完全由口令本身决定**。`1234`、`芝麻开门` 这类弱口令等于把房间公开——64 bit 种子并不等于 64 bit 安全性。需要严格安全时请用随机房间码(默认行为)。
627
+ > 🔧 算法与源码位置(HMAC 标签、归一化细节、位布局)见 [PASSPHRASE-ZH-CODE.md](./PASSPHRASE-ZH-CODE.md)。
624
628
  > 另外,口令一旦泄露,攻击者不仅能进房间,还能反推出同一个房间码;反之从房间码也推不出口令(HMAC 单向)。
625
629
 
626
630
  ### 9.6 中文房间码(彩蛋,需显式引入)
@@ -646,6 +650,7 @@ const guest = await createGuest({ roomCode: parseChineseRoomCode('朱 上 岸
646
650
  ```
647
651
 
648
652
  字符集是 **BIP39 简体中文词表**的 2048 个去重单字(标准、MIT、永不改动),因此不需要自己维护词库。
653
+ 🔧 位映射表、繁简映射的生成方式、各位置的 CRC 敏感度见 [PASSPHRASE-ZH-CODE.md](./PASSPHRASE-ZH-CODE.md)。
649
654
 
650
655
  | 工具 | 说明 |
651
656
  |---|---|
package/package.json CHANGED
@@ -1,66 +1,66 @@
1
- {
2
- "name": "mc-tunnel",
3
- "version": "0.2.1",
4
- "description": "面向 Electron / Node.js 的 Minecraft 局域网联机模块:房间码一键组网,基于 EasyTier 的 P2P 隧道(Minecraft LAN tunnel)",
5
- "license": "MIT",
6
- "type": "module",
7
- "main": "./dist/cjs/index.js",
8
- "module": "./dist/esm/index.js",
9
- "types": "./dist/types/index.d.ts",
10
- "exports": {
11
- ".": {
12
- "types": "./dist/types/index.d.ts",
13
- "import": "./dist/esm/index.js",
14
- "require": "./dist/cjs/index.js"
15
- },
16
- "./electron": {
17
- "types": "./dist/types/electron.d.ts",
18
- "import": "./dist/esm/electron.js",
19
- "require": "./dist/cjs/electron.js"
20
- },
21
- "./zh": {
22
- "types": "./dist/types/chinese-code.d.ts",
23
- "import": "./dist/esm/chinese-code.js",
24
- "require": "./dist/cjs/chinese-code.js"
25
- },
26
- "./package.json": "./package.json"
27
- },
28
- "files": [
29
- "dist",
30
- "docs",
31
- "README.md",
32
- "LICENSE",
33
- "THIRD_PARTY_NOTICES.md"
34
- ],
35
- "engines": {
36
- "node": ">=18.17"
37
- },
38
- "publishConfig": {
39
- "registry": "https://registry.npmjs.org/",
40
- "access": "public"
41
- },
42
- "keywords": [
43
- "minecraft",
44
- "lan",
45
- "tunnel",
46
- "p2p",
47
- "easytier",
48
- "electron",
49
- "我的世界",
50
- "联机",
51
- "局域网"
52
- ],
53
- "scripts": {
54
- "clean": "node scripts/clean.mjs",
55
- "build": "npm run clean && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && tsc -p tsconfig.types.json && node scripts/postbuild.mjs",
56
- "typecheck": "tsc -p tsconfig.esm.json --noEmit",
57
- "test": "node --test \"test/**/*.test.mjs\"",
58
- "prepack": "npm run build",
59
- "release": "npm publish",
60
- "release:mirror": "node scripts/sync-mirror.mjs"
61
- },
62
- "devDependencies": {
63
- "@types/node": "^22.10.0",
64
- "typescript": "^5.7.0"
65
- }
66
- }
1
+ {
2
+ "name": "mc-tunnel",
3
+ "version": "0.2.3",
4
+ "description": "面向 Electron / Node.js 的 Minecraft 局域网联机模块:房间码一键组网,基于 EasyTier 的 P2P 隧道(Minecraft LAN tunnel)",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/cjs/index.js",
8
+ "module": "./dist/esm/index.js",
9
+ "types": "./dist/types/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/types/index.d.ts",
13
+ "import": "./dist/esm/index.js",
14
+ "require": "./dist/cjs/index.js"
15
+ },
16
+ "./electron": {
17
+ "types": "./dist/types/electron.d.ts",
18
+ "import": "./dist/esm/electron.js",
19
+ "require": "./dist/cjs/electron.js"
20
+ },
21
+ "./zh": {
22
+ "types": "./dist/types/chinese-code.d.ts",
23
+ "import": "./dist/esm/chinese-code.js",
24
+ "require": "./dist/cjs/chinese-code.js"
25
+ },
26
+ "./package.json": "./package.json"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "docs",
31
+ "README.md",
32
+ "LICENSE",
33
+ "THIRD_PARTY_NOTICES.md"
34
+ ],
35
+ "engines": {
36
+ "node": ">=18.17"
37
+ },
38
+ "publishConfig": {
39
+ "registry": "https://registry.npmjs.org/",
40
+ "access": "public"
41
+ },
42
+ "keywords": [
43
+ "minecraft",
44
+ "lan",
45
+ "tunnel",
46
+ "p2p",
47
+ "easytier",
48
+ "electron",
49
+ "我的世界",
50
+ "联机",
51
+ "局域网"
52
+ ],
53
+ "scripts": {
54
+ "clean": "node scripts/clean.mjs",
55
+ "build": "npm run clean && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && tsc -p tsconfig.types.json && node scripts/postbuild.mjs",
56
+ "typecheck": "tsc -p tsconfig.esm.json --noEmit",
57
+ "test": "node --test \"test/**/*.test.mjs\"",
58
+ "prepack": "npm run build",
59
+ "release": "npm publish",
60
+ "release:mirror": "node scripts/sync-mirror.mjs"
61
+ },
62
+ "devDependencies": {
63
+ "@types/node": "^22.10.0",
64
+ "typescript": "^5.7.0"
65
+ }
66
+ }