@napuketto/loader 0.0.14 → 0.0.15

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.
@@ -25,6 +25,7 @@ let node_path = require("node:path");
25
25
  let node_process = require("node:process");
26
26
  node_process = __toESM(node_process, 1);
27
27
  let node_os = require("node:os");
28
+ let zod = require("zod");
28
29
  let node_readline = require("node:readline");
29
30
  //#region src/instance-lock.ts
30
31
  /**
@@ -459,20 +460,156 @@ function errorCodeOf(err) {
459
460
  }
460
461
  return "UNKNOWN";
461
462
  }
462
- //#endregion
463
- //#region src/host/ipc/ipc-types.ts
464
- /** 消息类型集合(解码校验用)。 */
465
- const IPC_MESSAGE_TYPES = /* @__PURE__ */ new Set([
466
- "status",
467
- "login",
468
- "qr",
469
- "event",
470
- "result",
471
- "log",
472
- "ping",
473
- "pong",
474
- "action",
475
- "control"
463
+ /** 引导阶段(status 消息)。 */
464
+ const IpcBootPhaseSchema = zod.z.enum([
465
+ "booting",
466
+ "dlopening",
467
+ "logging",
468
+ "sessioning",
469
+ "ready",
470
+ "failed"
471
+ ]);
472
+ /** 结构化日志级别(pino level)。 */
473
+ const IpcLogLevelSchema = zod.z.enum([
474
+ "trace",
475
+ "debug",
476
+ "info",
477
+ "warn",
478
+ "error",
479
+ "fatal"
480
+ ]);
481
+ /** 登录状态(与 kernel LoginState 结构等价)。 */
482
+ const LoginStateSchema = zod.z.enum([
483
+ "idle",
484
+ "waiting_scan",
485
+ "scanned",
486
+ "logged_in",
487
+ "failed"
488
+ ]);
489
+ /** selfInfo(与 kernel SelfInfo 结构等价)。 */
490
+ const SelfInfoSchema = zod.z.object({
491
+ uin: zod.z.string(),
492
+ uid: zod.z.string(),
493
+ nick: zod.z.string()
494
+ });
495
+ /** status 消息 payload。 */
496
+ const IpcStatusPayloadSchema = zod.z.object({
497
+ phase: IpcBootPhaseSchema,
498
+ message: zod.z.string().optional(),
499
+ error: zod.z.object({
500
+ code: zod.z.string(),
501
+ message: zod.z.string()
502
+ }).optional()
503
+ });
504
+ /** login 消息 payload。 */
505
+ const IpcLoginPayloadSchema = zod.z.object({
506
+ state: LoginStateSchema,
507
+ selfInfo: SelfInfoSchema.optional(),
508
+ /** 失败原因(state=failed 时;如「登录超时,请刷新页面重试」)。 */
509
+ message: zod.z.string().optional()
510
+ });
511
+ /** event 消息 payload(kernel 事件通道形状,翻译层按 service/name 断言具体类型)。 */
512
+ const IpcEventPayloadSchema = zod.z.object({
513
+ service: zod.z.string(),
514
+ name: zod.z.string(),
515
+ args: zod.z.array(zod.z.unknown())
516
+ });
517
+ /** log 消息 payload。 */
518
+ const IpcLogPayloadSchema = zod.z.object({
519
+ level: IpcLogLevelSchema,
520
+ message: zod.z.string()
521
+ });
522
+ /** 动作响应 payload(result 消息)。 */
523
+ const IpcResultPayloadSchema = zod.z.union([zod.z.object({
524
+ ok: zod.z.literal(true),
525
+ value: zod.z.unknown().optional()
526
+ }), zod.z.object({
527
+ ok: zod.z.literal(false),
528
+ error: zod.z.object({
529
+ code: zod.z.string(),
530
+ message: zod.z.string()
531
+ })
532
+ })]);
533
+ /** 动作请求 payload(action 消息)。 */
534
+ const IpcActionPayloadSchema = zod.z.object({
535
+ action: zod.z.string(),
536
+ params: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
537
+ });
538
+ /** 控制指令 payload(control 消息,父→子)。 */
539
+ const IpcControlPayloadSchema = zod.z.union([
540
+ zod.z.object({ command: zod.z.literal("stop") }),
541
+ zod.z.object({ command: zod.z.literal("restart") }),
542
+ zod.z.object({
543
+ command: zod.z.literal("login"),
544
+ uin: zod.z.string().optional(),
545
+ qr: zod.z.boolean().optional()
546
+ })
547
+ ]);
548
+ const IpcStatusMessageSchema = zod.z.object({
549
+ v: zod.z.literal(1),
550
+ type: zod.z.literal("status"),
551
+ payload: IpcStatusPayloadSchema
552
+ });
553
+ const IpcLoginMessageSchema = zod.z.object({
554
+ v: zod.z.literal(1),
555
+ type: zod.z.literal("login"),
556
+ payload: IpcLoginPayloadSchema
557
+ });
558
+ const IpcQrMessageSchema = zod.z.object({
559
+ v: zod.z.literal(1),
560
+ type: zod.z.literal("qr"),
561
+ payload: zod.z.object({
562
+ pngBase64: zod.z.string(),
563
+ qrcodeUrl: zod.z.string()
564
+ })
565
+ });
566
+ const IpcEventMessageSchema = zod.z.object({
567
+ v: zod.z.literal(1),
568
+ type: zod.z.literal("event"),
569
+ payload: IpcEventPayloadSchema
570
+ });
571
+ const IpcResultMessageSchema = zod.z.object({
572
+ v: zod.z.literal(1),
573
+ type: zod.z.literal("result"),
574
+ id: zod.z.number(),
575
+ payload: IpcResultPayloadSchema
576
+ });
577
+ const IpcLogMessageSchema = zod.z.object({
578
+ v: zod.z.literal(1),
579
+ type: zod.z.literal("log"),
580
+ payload: IpcLogPayloadSchema
581
+ });
582
+ const IpcPingMessageSchema = zod.z.object({
583
+ v: zod.z.literal(1),
584
+ type: zod.z.literal("ping")
585
+ });
586
+ const IpcPongMessageSchema = zod.z.object({
587
+ v: zod.z.literal(1),
588
+ type: zod.z.literal("pong")
589
+ });
590
+ const IpcActionMessageSchema = zod.z.object({
591
+ v: zod.z.literal(1),
592
+ type: zod.z.literal("action"),
593
+ id: zod.z.number(),
594
+ payload: IpcActionPayloadSchema
595
+ });
596
+ const IpcControlMessageSchema = zod.z.object({
597
+ v: zod.z.literal(1),
598
+ type: zod.z.literal("control"),
599
+ payload: IpcControlPayloadSchema
600
+ });
601
+ /** IPC 消息判别联合(协议边界,运行时经 safeParse 校验)。 */
602
+ const IpcMessageSchema = zod.z.discriminatedUnion("type", [
603
+ IpcStatusMessageSchema,
604
+ IpcLoginMessageSchema,
605
+ IpcQrMessageSchema,
606
+ IpcEventMessageSchema,
607
+ IpcResultMessageSchema,
608
+ IpcLogMessageSchema,
609
+ IpcPingMessageSchema,
610
+ IpcPongMessageSchema,
611
+ IpcActionMessageSchema,
612
+ IpcControlMessageSchema
476
613
  ]);
477
614
  //#endregion
478
615
  //#region src/host/ipc/ipc-sender.ts
@@ -615,22 +752,18 @@ function attachIpcServices(actions, services) {
615
752
  /**
616
753
  * ipc-codec.ts:IPC 消息编解码(JSON 行协议,与 koishi 插件侧 src/ipc/codec.ts 对齐)。
617
754
  *
618
- * 每行一条 JSON 消息,`\n` 结尾。解码宽松:非法行 / 空行 / v 不匹配 /
619
- * 未知 type → null,调用方忽略,不崩通道。
755
+ * 每行一条 JSON 消息,`\n` 结尾。解码用 IpcMessageSchema.safeParse 校验:
756
+ * 非法行 / 空行 / v 不匹配 / type 未知 / payload 形状非法 → null,调用方忽略,
757
+ * 不崩通道。
620
758
  */
621
- /** 类型守卫:v 匹配 + type 合法。 */
622
- function isIpcMessage(value) {
623
- if (typeof value !== "object" || value === null) return false;
624
- const record = value;
625
- return record["v"] === 1 && typeof record["type"] === "string" && IPC_MESSAGE_TYPES.has(record["type"]);
626
- }
627
- /** 解码一行 JSON;非法行/空行返回 null。 */
759
+ /** 解码一行 JSON;非法行/空行/形状不合法返回 null。 */
628
760
  function decodeIpcMessage(line) {
629
761
  const trimmed = line.trim();
630
762
  if (trimmed === "") return null;
631
763
  try {
632
764
  const parsed = JSON.parse(trimmed);
633
- return isIpcMessage(parsed) ? parsed : null;
765
+ const result = IpcMessageSchema.safeParse(parsed);
766
+ return result.success ? result.data : null;
634
767
  } catch {
635
768
  return null;
636
769
  }
package/dist/index.cjs CHANGED
@@ -21,6 +21,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  enumerable: true
22
22
  }) : target, mod));
23
23
  //#endregion
24
+ let zod = require("zod");
24
25
  let node_fs = require("node:fs");
25
26
  let node_path = require("node:path");
26
27
  let node_process = require("node:process");
@@ -30,6 +31,171 @@ let node_url = require("node:url");
30
31
  let node_fs_promises = require("node:fs/promises");
31
32
  let node_crypto = require("node:crypto");
32
33
  let node_https = require("node:https");
34
+ //#region src/host/ipc/ipc-types.ts
35
+ /**
36
+ * ipc-types.ts:IPC 协议消息(koishi 插件 ↔ self-host 子进程,单一来源)。
37
+ *
38
+ * 协议契约(koishi 插件 design.md §5.6):`{ v, type, id?, payload }` JSON 行。
39
+ * 用 zod v4 定义单一 schema,两侧(loader host + koishi 插件)import 同一份:
40
+ * 类型经 z.infer 派生、运行时经 safeParse 校验,消除「两份手写 types.ts」漂移。
41
+ *
42
+ * 领域字段(LoginState / SelfInfo / QrCodeData)与 kernel 同名类型结构等价,
43
+ * 在协议边界内联为 zod schema(kernel 不依赖 zod,故不在 kernel 定义)。
44
+ */
45
+ /** IPC 协议版本(解码校验)。 */
46
+ const IPC_VERSION = 1;
47
+ /** 引导阶段(status 消息)。 */
48
+ const IpcBootPhaseSchema = zod.z.enum([
49
+ "booting",
50
+ "dlopening",
51
+ "logging",
52
+ "sessioning",
53
+ "ready",
54
+ "failed"
55
+ ]);
56
+ /** 结构化日志级别(pino level)。 */
57
+ const IpcLogLevelSchema = zod.z.enum([
58
+ "trace",
59
+ "debug",
60
+ "info",
61
+ "warn",
62
+ "error",
63
+ "fatal"
64
+ ]);
65
+ /** 登录状态(与 kernel LoginState 结构等价)。 */
66
+ const LoginStateSchema = zod.z.enum([
67
+ "idle",
68
+ "waiting_scan",
69
+ "scanned",
70
+ "logged_in",
71
+ "failed"
72
+ ]);
73
+ /** selfInfo(与 kernel SelfInfo 结构等价)。 */
74
+ const SelfInfoSchema = zod.z.object({
75
+ uin: zod.z.string(),
76
+ uid: zod.z.string(),
77
+ nick: zod.z.string()
78
+ });
79
+ /** status 消息 payload。 */
80
+ const IpcStatusPayloadSchema = zod.z.object({
81
+ phase: IpcBootPhaseSchema,
82
+ message: zod.z.string().optional(),
83
+ error: zod.z.object({
84
+ code: zod.z.string(),
85
+ message: zod.z.string()
86
+ }).optional()
87
+ });
88
+ /** login 消息 payload。 */
89
+ const IpcLoginPayloadSchema = zod.z.object({
90
+ state: LoginStateSchema,
91
+ selfInfo: SelfInfoSchema.optional(),
92
+ /** 失败原因(state=failed 时;如「登录超时,请刷新页面重试」)。 */
93
+ message: zod.z.string().optional()
94
+ });
95
+ /** event 消息 payload(kernel 事件通道形状,翻译层按 service/name 断言具体类型)。 */
96
+ const IpcEventPayloadSchema = zod.z.object({
97
+ service: zod.z.string(),
98
+ name: zod.z.string(),
99
+ args: zod.z.array(zod.z.unknown())
100
+ });
101
+ /** log 消息 payload。 */
102
+ const IpcLogPayloadSchema = zod.z.object({
103
+ level: IpcLogLevelSchema,
104
+ message: zod.z.string()
105
+ });
106
+ /** 动作响应 payload(result 消息)。 */
107
+ const IpcResultPayloadSchema = zod.z.union([zod.z.object({
108
+ ok: zod.z.literal(true),
109
+ value: zod.z.unknown().optional()
110
+ }), zod.z.object({
111
+ ok: zod.z.literal(false),
112
+ error: zod.z.object({
113
+ code: zod.z.string(),
114
+ message: zod.z.string()
115
+ })
116
+ })]);
117
+ /** 动作请求 payload(action 消息)。 */
118
+ const IpcActionPayloadSchema = zod.z.object({
119
+ action: zod.z.string(),
120
+ params: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
121
+ });
122
+ /** 控制指令 payload(control 消息,父→子)。 */
123
+ const IpcControlPayloadSchema = zod.z.union([
124
+ zod.z.object({ command: zod.z.literal("stop") }),
125
+ zod.z.object({ command: zod.z.literal("restart") }),
126
+ zod.z.object({
127
+ command: zod.z.literal("login"),
128
+ uin: zod.z.string().optional(),
129
+ qr: zod.z.boolean().optional()
130
+ })
131
+ ]);
132
+ const IpcStatusMessageSchema = zod.z.object({
133
+ v: zod.z.literal(1),
134
+ type: zod.z.literal("status"),
135
+ payload: IpcStatusPayloadSchema
136
+ });
137
+ const IpcLoginMessageSchema = zod.z.object({
138
+ v: zod.z.literal(1),
139
+ type: zod.z.literal("login"),
140
+ payload: IpcLoginPayloadSchema
141
+ });
142
+ const IpcQrMessageSchema = zod.z.object({
143
+ v: zod.z.literal(1),
144
+ type: zod.z.literal("qr"),
145
+ payload: zod.z.object({
146
+ pngBase64: zod.z.string(),
147
+ qrcodeUrl: zod.z.string()
148
+ })
149
+ });
150
+ const IpcEventMessageSchema = zod.z.object({
151
+ v: zod.z.literal(1),
152
+ type: zod.z.literal("event"),
153
+ payload: IpcEventPayloadSchema
154
+ });
155
+ const IpcResultMessageSchema = zod.z.object({
156
+ v: zod.z.literal(1),
157
+ type: zod.z.literal("result"),
158
+ id: zod.z.number(),
159
+ payload: IpcResultPayloadSchema
160
+ });
161
+ const IpcLogMessageSchema = zod.z.object({
162
+ v: zod.z.literal(1),
163
+ type: zod.z.literal("log"),
164
+ payload: IpcLogPayloadSchema
165
+ });
166
+ const IpcPingMessageSchema = zod.z.object({
167
+ v: zod.z.literal(1),
168
+ type: zod.z.literal("ping")
169
+ });
170
+ const IpcPongMessageSchema = zod.z.object({
171
+ v: zod.z.literal(1),
172
+ type: zod.z.literal("pong")
173
+ });
174
+ const IpcActionMessageSchema = zod.z.object({
175
+ v: zod.z.literal(1),
176
+ type: zod.z.literal("action"),
177
+ id: zod.z.number(),
178
+ payload: IpcActionPayloadSchema
179
+ });
180
+ const IpcControlMessageSchema = zod.z.object({
181
+ v: zod.z.literal(1),
182
+ type: zod.z.literal("control"),
183
+ payload: IpcControlPayloadSchema
184
+ });
185
+ /** IPC 消息判别联合(协议边界,运行时经 safeParse 校验)。 */
186
+ const IpcMessageSchema = zod.z.discriminatedUnion("type", [
187
+ IpcStatusMessageSchema,
188
+ IpcLoginMessageSchema,
189
+ IpcQrMessageSchema,
190
+ IpcEventMessageSchema,
191
+ IpcResultMessageSchema,
192
+ IpcLogMessageSchema,
193
+ IpcPingMessageSchema,
194
+ IpcPongMessageSchema,
195
+ IpcActionMessageSchema,
196
+ IpcControlMessageSchema
197
+ ]);
198
+ //#endregion
33
199
  //#region src/instance-lock.ts
34
200
  /**
35
201
  * instance-lock.ts:单实例锁(2026-08-07 根治「多实例抢数据目录锁挂起」)
@@ -909,6 +1075,17 @@ async function ensureQqFiles(options = {}) {
909
1075
  exports.DownloadError = DownloadError;
910
1076
  exports.ENV = ENV;
911
1077
  exports.INSTANCE_LOCK_FILE = INSTANCE_LOCK_FILE;
1078
+ exports.IPC_VERSION = IPC_VERSION;
1079
+ exports.IpcActionPayloadSchema = IpcActionPayloadSchema;
1080
+ exports.IpcBootPhaseSchema = IpcBootPhaseSchema;
1081
+ exports.IpcControlPayloadSchema = IpcControlPayloadSchema;
1082
+ exports.IpcEventPayloadSchema = IpcEventPayloadSchema;
1083
+ exports.IpcLogLevelSchema = IpcLogLevelSchema;
1084
+ exports.IpcLogPayloadSchema = IpcLogPayloadSchema;
1085
+ exports.IpcLoginPayloadSchema = IpcLoginPayloadSchema;
1086
+ exports.IpcMessageSchema = IpcMessageSchema;
1087
+ exports.IpcResultPayloadSchema = IpcResultPayloadSchema;
1088
+ exports.IpcStatusPayloadSchema = IpcStatusPayloadSchema;
912
1089
  exports.QQ_FILES_DIR_NAME = QQ_FILES_DIR_NAME;
913
1090
  exports.QqReleasesError = QqReleasesError;
914
1091
  exports.WIN_NODE_DIR_NAME = WIN_NODE_DIR_NAME;