@napuketto/kernel 0.0.8 → 0.0.10

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/dist/index.mjs CHANGED
@@ -2590,6 +2590,8 @@ function startupSessionStart(ctx) {
2590
2590
  /** 二维码过期错误码(errType=1 errCode=3,实测)。 */
2591
2591
  const QR_EXPIRED_ERR_TYPE = 1;
2592
2592
  const QR_EXPIRED_ERR_CODE = 3;
2593
+ /** 登录超时提示(参考项目文案,前端 failed 态展示)。 */
2594
+ const QR_LOGIN_TIMEOUT_MESSAGE = "登录超时,请刷新页面重试";
2593
2595
  /** 二维码过期判定。 */
2594
2596
  function isQrCodeExpired(errType, errCode) {
2595
2597
  return errType === QR_EXPIRED_ERR_TYPE && errCode === QR_EXPIRED_ERR_CODE;
@@ -2604,10 +2606,17 @@ var QrLoginSession = class {
2604
2606
  self = null;
2605
2607
  qrCodeHandlers = /* @__PURE__ */ new Set();
2606
2608
  stateHandlers = /* @__PURE__ */ new Set();
2607
- constructor(loginService) {
2609
+ /** 超时定时器(refresh 后启动,登录成功/失败/stop 清理)。 */
2610
+ timeoutTimer = null;
2611
+ /** 失败原因(超时等,failed 态透出给上层提示文案)。 */
2612
+ failureReasonValue = null;
2613
+ /** 超时上限(毫秒,默认 QR_LOGIN_TIMEOUT_MS)。 */
2614
+ timeoutMs;
2615
+ constructor(loginService, options = {}) {
2608
2616
  const svc = loginService;
2609
2617
  if (svc === null || typeof svc.addKernelLoginListener !== "function") throw kernelError("loginService 无效(缺 addKernelLoginListener)", "INVALID_STATE");
2610
2618
  this.loginService = svc;
2619
+ this.timeoutMs = options.timeoutMs ?? 12e4;
2611
2620
  }
2612
2621
  /** 当前状态。 */
2613
2622
  get currentState() {
@@ -2617,6 +2626,10 @@ var QrLoginSession = class {
2617
2626
  get selfInfo() {
2618
2627
  return this.self;
2619
2628
  }
2629
+ /** 失败原因(超时等;failed 态透出给上层提示文案)。 */
2630
+ get failureReason() {
2631
+ return this.failureReasonValue;
2632
+ }
2620
2633
  /** 订阅二维码(png base64 + url)。返回退订函数。 */
2621
2634
  onQrCode(cb) {
2622
2635
  this.qrCodeHandlers.add(cb);
@@ -2632,15 +2645,19 @@ var QrLoginSession = class {
2632
2645
  if (this.state !== "idle") return;
2633
2646
  this.listenerId = this.loginService.addKernelLoginListener(this.buildListener());
2634
2647
  this.loginService.connect();
2635
- if (opts.quickUin !== void 0 && opts.quickUin !== "") this.loginService.quickLoginWithUin(opts.quickUin).catch(() => {
2648
+ if (opts.quickUin !== void 0 && opts.quickUin !== "") this.loginService.quickLoginWithUin(opts.quickUin).then((result) => {
2649
+ if (result.loginErrorInfo.errMsg !== "") this.refresh();
2650
+ }).catch(() => {
2636
2651
  this.refresh();
2637
2652
  });
2638
2653
  else this.refresh();
2639
2654
  }
2640
- /** 刷新二维码(手动 / 过期自动)。 */
2655
+ /** 刷新二维码(手动 / 过期自动)。每次出码重启超时计时。 */
2641
2656
  refresh() {
2657
+ this.failureReasonValue = null;
2642
2658
  this.setState("waiting_scan");
2643
2659
  this.loginService.getQRCodePicture();
2660
+ this.restartTimeout();
2644
2661
  }
2645
2662
  /** 构建登录监听(普通 JS 对象,NAPI 反射)。 */
2646
2663
  buildListener() {
@@ -2659,6 +2676,7 @@ var QrLoginSession = class {
2659
2676
  this.setState("scanned");
2660
2677
  };
2661
2678
  listener.onQRCodeLoginSucceed = (result) => {
2679
+ this.clearTimeoutTimer();
2662
2680
  this.self = {
2663
2681
  uin: result.uin,
2664
2682
  uid: result.uid,
@@ -2671,20 +2689,41 @@ var QrLoginSession = class {
2671
2689
  this.refresh();
2672
2690
  return;
2673
2691
  }
2692
+ this.clearTimeoutTimer();
2693
+ this.failureReasonValue = null;
2674
2694
  this.setState("failed");
2675
2695
  };
2676
2696
  listener.onLoginFailed = () => {
2697
+ this.clearTimeoutTimer();
2698
+ this.failureReasonValue = null;
2677
2699
  this.setState("failed");
2678
2700
  };
2679
2701
  return listener;
2680
2702
  }
2681
2703
  /** 停止:注销监听(登录成功后调用,防重复回调)。 */
2682
2704
  stop() {
2705
+ this.clearTimeoutTimer();
2683
2706
  if (this.listenerId !== null) {
2684
2707
  this.loginService.removeKernelLoginListener(this.listenerId);
2685
2708
  this.listenerId = null;
2686
2709
  }
2687
2710
  }
2711
+ /** 重启超时计时器:超时未登录 → failed + 超时提示。 */
2712
+ restartTimeout() {
2713
+ this.clearTimeoutTimer();
2714
+ this.timeoutTimer = setTimeout(() => {
2715
+ this.timeoutTimer = null;
2716
+ this.failureReasonValue = QR_LOGIN_TIMEOUT_MESSAGE;
2717
+ this.setState("failed");
2718
+ }, this.timeoutMs);
2719
+ }
2720
+ /** 清理超时计时器(幂等)。 */
2721
+ clearTimeoutTimer() {
2722
+ if (this.timeoutTimer !== null) {
2723
+ clearTimeout(this.timeoutTimer);
2724
+ this.timeoutTimer = null;
2725
+ }
2726
+ }
2688
2727
  setState(next) {
2689
2728
  this.state = next;
2690
2729
  for (const cb of this.stateHandlers) cb(next);
@@ -3169,6 +3208,8 @@ function getOrCreateLoginService(exports) {
3169
3208
  var NapukettoCore = class NapukettoCore {
3170
3209
  /** 装配根(协议层 / apis / cache 的消费入口)。 */
3171
3210
  ctx;
3211
+ /** QR 登录会话句柄(登录期间非空;登录终态后清空)。refreshQr() 据此可达。 */
3212
+ qrSession = null;
3172
3213
  /** 由 create() 装配,不直接 new。 */
3173
3214
  constructor(ctx) {
3174
3215
  this.ctx = ctx;
@@ -3267,25 +3308,37 @@ var NapukettoCore = class NapukettoCore {
3267
3308
  if (this.ctx.wrapper === null) throw kernelError("wrapper 未装配,无法设置 session", "INVALID_STATE");
3268
3309
  this.ctx.wrapper.session = session;
3269
3310
  }
3311
+ /**
3312
+ * 手动刷新二维码(QR 登录期间可用)。返回是否成功触发(false = 当前不在扫码态)。
3313
+ * koishi 前端「刷新二维码」按钮经 loader IPC `login.refreshQr` 动作直达。
3314
+ */
3315
+ refreshQr() {
3316
+ if (this.qrSession === null) return false;
3317
+ this.qrSession.refresh();
3318
+ return true;
3319
+ }
3270
3320
  /** QR 登录:QrLoginSession 状态机 + 二维码写缓存目录(无 UI,cli 可打印路径)。 */
3271
3321
  async loginByQr(opts) {
3272
3322
  const { wrapper } = this.ctx;
3273
3323
  if (wrapper === null || wrapper.loginService === null) throw kernelError("wrapper/loginService 不可用,无法 QR 登录", "INVALID_STATE");
3274
3324
  const session = new QrLoginSession(wrapper.loginService);
3325
+ this.qrSession = session;
3275
3326
  const qrPath = opts.qrCodePath ?? this.ctx.paths.file("cache", "qrcode.png");
3276
3327
  this.subscribeQrProgress(session, opts, qrPath);
3277
- const startOpts = {};
3278
- if (opts.quickUin !== void 0) startOpts.quickUin = opts.quickUin;
3279
- session.start(startOpts);
3280
- await waitQrLoggedIn(session);
3281
- const self = session.selfInfo;
3282
- if (self === null) throw kernelError("QR 登录成功但 selfInfo 为空", "INVALID_STATE");
3283
- session.stop();
3284
- return {
3285
- uin: self.uin,
3286
- uid: self.uid,
3287
- nick: self.nick
3288
- };
3328
+ try {
3329
+ session.start();
3330
+ await waitQrLoggedIn(session);
3331
+ const self = session.selfInfo;
3332
+ if (self === null) throw kernelError("QR 登录成功但 selfInfo 为空", "INVALID_STATE");
3333
+ return {
3334
+ uin: self.uin,
3335
+ uid: self.uid,
3336
+ nick: self.nick
3337
+ };
3338
+ } finally {
3339
+ session.stop();
3340
+ this.qrSession = null;
3341
+ }
3289
3342
  }
3290
3343
  /** 订阅 QR 进度:二维码写盘 + 状态机经 onLoginProgress 转发。 */
3291
3344
  subscribeQrProgress(session, opts, qrPath) {
@@ -3299,7 +3352,11 @@ var NapukettoCore = class NapukettoCore {
3299
3352
  });
3300
3353
  session.onStateChange((state) => {
3301
3354
  this.ctx.logger.info({ state }, "QR 登录状态");
3302
- opts.onLoginProgress?.({ state });
3355
+ const message = state === "failed" ? session.failureReason ?? void 0 : void 0;
3356
+ opts.onLoginProgress?.({
3357
+ state,
3358
+ ...message !== void 0 ? { message } : {}
3359
+ });
3303
3360
  });
3304
3361
  }
3305
3362
  /** 停止:日志收尾(资源清理留给 P2 常驻管理)。 */