@agentunion/fastaun-browser 0.2.17 → 0.2.19

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.
Files changed (58) hide show
  1. package/dist/auth.d.ts +12 -0
  2. package/dist/auth.d.ts.map +1 -1
  3. package/dist/auth.js +370 -215
  4. package/dist/auth.js.map +1 -1
  5. package/dist/client.d.ts +24 -1
  6. package/dist/client.d.ts.map +1 -1
  7. package/dist/client.js +1307 -849
  8. package/dist/client.js.map +1 -1
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js +3 -1
  11. package/dist/config.js.map +1 -1
  12. package/dist/discovery.d.ts +3 -0
  13. package/dist/discovery.d.ts.map +1 -1
  14. package/dist/discovery.js +15 -1
  15. package/dist/discovery.js.map +1 -1
  16. package/dist/e2ee-group.d.ts +4 -0
  17. package/dist/e2ee-group.d.ts.map +1 -1
  18. package/dist/e2ee-group.js +327 -201
  19. package/dist/e2ee-group.js.map +1 -1
  20. package/dist/e2ee.d.ts +4 -0
  21. package/dist/e2ee.d.ts.map +1 -1
  22. package/dist/e2ee.js +196 -117
  23. package/dist/e2ee.js.map +1 -1
  24. package/dist/events.d.ts +3 -0
  25. package/dist/events.d.ts.map +1 -1
  26. package/dist/events.js +4 -1
  27. package/dist/events.js.map +1 -1
  28. package/dist/keystore/index.d.ts +11 -0
  29. package/dist/keystore/index.d.ts.map +1 -1
  30. package/dist/keystore/indexeddb.d.ts +38 -0
  31. package/dist/keystore/indexeddb.d.ts.map +1 -1
  32. package/dist/keystore/indexeddb.js +245 -98
  33. package/dist/keystore/indexeddb.js.map +1 -1
  34. package/dist/logger.d.ts +37 -0
  35. package/dist/logger.d.ts.map +1 -0
  36. package/dist/logger.js +112 -0
  37. package/dist/logger.js.map +1 -0
  38. package/dist/namespaces/auth.d.ts +13 -3
  39. package/dist/namespaces/auth.d.ts.map +1 -1
  40. package/dist/namespaces/auth.js +284 -106
  41. package/dist/namespaces/auth.js.map +1 -1
  42. package/dist/namespaces/custody.d.ts +3 -0
  43. package/dist/namespaces/custody.d.ts.map +1 -1
  44. package/dist/namespaces/custody.js +147 -75
  45. package/dist/namespaces/custody.js.map +1 -1
  46. package/dist/namespaces/meta.d.ts +3 -0
  47. package/dist/namespaces/meta.d.ts.map +1 -1
  48. package/dist/namespaces/meta.js +94 -43
  49. package/dist/namespaces/meta.js.map +1 -1
  50. package/dist/secret-store/indexeddb-store.d.ts +3 -0
  51. package/dist/secret-store/indexeddb-store.d.ts.map +1 -1
  52. package/dist/secret-store/indexeddb-store.js +57 -29
  53. package/dist/secret-store/indexeddb-store.js.map +1 -1
  54. package/dist/transport.d.ts +3 -0
  55. package/dist/transport.d.ts.map +1 -1
  56. package/dist/transport.js +74 -4
  57. package/dist/transport.js.map +1 -1
  58. package/package.json +37 -37
package/dist/auth.js CHANGED
@@ -4,6 +4,7 @@
4
4
  import { base64ToUint8, uint8ToBase64, pemToArrayBuffer, toBufferSource } from './crypto.js';
5
5
  import { AuthError, StateError, ValidationError, mapRemoteError } from './errors.js';
6
6
  import { ROOT_CA_PEM } from './certs/root.js';
7
+ const _noopLog = { error: () => { }, warn: () => { }, info: () => { }, debug: () => { } };
7
8
  import { isJsonObject, } from './types.js';
8
9
  // ── ASN.1 / PEM 工具 ────────────────────────────────────
9
10
  /** 拆分 PEM bundle 为独立的 PEM 字符串数组 */
@@ -334,6 +335,8 @@ function gatewayHttpUrl(gatewayUrl, path) {
334
335
  * - 证书自动续期
335
336
  */
336
337
  export class AuthFlow {
338
+ _log = _noopLog;
339
+ setLogger(log) { this._log = log; }
337
340
  static _INSTANCE_STATE_FIELDS = [
338
341
  'access_token',
339
342
  'refresh_token',
@@ -368,14 +371,23 @@ export class AuthFlow {
368
371
  // ── 公开 API ──────────────────────────────────────
369
372
  /** 加载本地身份信息 */
370
373
  async loadIdentity(aid) {
371
- const identity = await this._loadIdentityOrRaise(aid);
372
- const cert = await this._keystore.loadCert(identity.aid);
373
- if (cert)
374
- identity.cert = cert;
375
- const instanceState = await this._loadInstanceState(identity.aid);
376
- if (instanceState)
377
- Object.assign(identity, instanceState);
378
- return identity;
374
+ const tStart = Date.now();
375
+ this._log.debug(`loadIdentity enter: aid=${aid ?? '<current>'}`);
376
+ try {
377
+ const identity = await this._loadIdentityOrRaise(aid);
378
+ const cert = await this._keystore.loadCert(identity.aid);
379
+ if (cert)
380
+ identity.cert = cert;
381
+ const instanceState = await this._loadInstanceState(identity.aid);
382
+ if (instanceState)
383
+ Object.assign(identity, instanceState);
384
+ this._log.debug(`loadIdentity exit: elapsed=${Date.now() - tStart}ms aid=${identity.aid} has_cert=${!!identity.cert}`);
385
+ return identity;
386
+ }
387
+ catch (err) {
388
+ this._log.debug(`loadIdentity exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
389
+ throw err;
390
+ }
379
391
  }
380
392
  /** 加载身份,不存在时返回 null */
381
393
  async loadIdentityOrNull(aid) {
@@ -410,265 +422,381 @@ export class AuthFlow {
410
422
  * 3. 保存返回的证书
411
423
  */
412
424
  async createAid(gatewayUrl, aid) {
425
+ const tStart = Date.now();
426
+ this._log.debug(`createAid enter: aid=${aid} gateway=${gatewayUrl}`);
413
427
  AuthFlow._validateAidName(aid);
414
- const identity = await this._ensureLocalIdentity(aid);
415
- if (identity.cert) {
416
- return { aid: identity.aid, cert: identity.cert };
417
- }
418
- // 本地有密钥但无证书 — 尝试注册
419
428
  try {
420
- const created = await this._createAid(gatewayUrl, identity);
421
- Object.assign(identity, created);
422
- }
423
- catch (e) {
424
- if (e instanceof Error && e.message.includes('already exists')) {
425
- // AID 已在服务端注册,尝试下载证书恢复
426
- try {
427
- const recovered = await this._recoverCertViaDownload(gatewayUrl, identity);
428
- Object.assign(identity, recovered);
429
+ const identity = await this._ensureLocalIdentity(aid);
430
+ if (identity.cert) {
431
+ this._log.debug(`createAid exit: elapsed=${Date.now() - tStart}ms aid=${aid} reason=already_has_cert`);
432
+ return { aid: identity.aid, cert: identity.cert };
433
+ }
434
+ // 本地有密钥但无证书 — 尝试注册
435
+ try {
436
+ const created = await this._createAid(gatewayUrl, identity);
437
+ Object.assign(identity, created);
438
+ }
439
+ catch (e) {
440
+ if (e instanceof Error && e.message.includes('already exists')) {
441
+ // AID 已在服务端注册,尝试下载证书恢复
442
+ try {
443
+ const recovered = await this._recoverCertViaDownload(gatewayUrl, identity);
444
+ Object.assign(identity, recovered);
445
+ }
446
+ catch {
447
+ this._log.debug(`createAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=already_registered_recover_failed`);
448
+ throw new StateError(`AID ${aid} already registered on server but local certificate is missing. ` +
449
+ `Certificate download recovery failed. Options: ` +
450
+ `(1) use a different AID name, or ` +
451
+ `(2) restart server to clear registration.`);
452
+ }
429
453
  }
430
- catch {
431
- throw new StateError(`AID ${aid} already registered on server but local certificate is missing. ` +
432
- `Certificate download recovery failed. Options: ` +
433
- `(1) use a different AID name, or ` +
434
- `(2) restart server to clear registration.`);
454
+ else {
455
+ throw e;
435
456
  }
436
457
  }
437
- else {
438
- throw e;
439
- }
458
+ await this._persistIdentity(identity);
459
+ this._aid = identity.aid;
460
+ this._log.debug(`createAid exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
461
+ return { aid: identity.aid, cert: identity.cert };
462
+ }
463
+ catch (err) {
464
+ this._log.debug(`createAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
465
+ throw err;
440
466
  }
441
- await this._persistIdentity(identity);
442
- this._aid = identity.aid;
443
- return { aid: identity.aid, cert: identity.cert };
444
467
  }
445
468
  /**
446
469
  * 认证已有 AID — login1/login2 双阶段流程。
470
+ *
471
+ * 优先复用 keystore 里的 cached access_token(未过期且有 refresh_token),
472
+ * 避免每次 authenticate 都走两阶段重登的网络往返。与 Python SDK 行为对齐。
447
473
  */
448
474
  async authenticate(gatewayUrl, aid) {
449
- const identity = await this._loadIdentityOrRaise(aid);
450
- if (!identity.cert) {
451
- // 尝试下载恢复证书
452
- try {
453
- const recovered = await this._recoverCertViaDownload(gatewayUrl, identity);
454
- Object.assign(identity, recovered);
455
- await this._persistIdentity(identity);
475
+ const tStart = Date.now();
476
+ this._log.debug(`authenticate enter: aid=${aid ?? '<current>'} gateway=${gatewayUrl}`);
477
+ try {
478
+ const identity = await this._loadIdentityOrRaise(aid);
479
+ // 优先复用 cached access_token(未过期且有 refresh_token)
480
+ // 避免每次调 authenticate 都走两阶段重登
481
+ // 注意:_loadIdentityOrRaise 直接走 keystore.loadIdentity,不包含 instance_state
482
+ // (IndexedDB 把 access_token / refresh_token / expires_at 拆到 STORE_INSTANCE_STATE)
483
+ // 这里需要主动 _loadInstanceState 拿到 token,否则永远走 _login。
484
+ const instanceState = await this._loadInstanceState(identity.aid);
485
+ const identityWithState = instanceState
486
+ ? { ...identity, ...instanceState }
487
+ : identity;
488
+ const cachedToken = this._getCachedAccessToken(identityWithState);
489
+ const cachedRefresh = String(identityWithState.refresh_token ?? '');
490
+ if (cachedToken && cachedRefresh) {
491
+ this._log.debug(`authenticate reusing cached token: aid=${identity.aid} expires_at=${identityWithState.access_token_expires_at}`);
492
+ this._aid = identity.aid;
493
+ return {
494
+ aid: identity.aid,
495
+ access_token: cachedToken,
496
+ refresh_token: cachedRefresh,
497
+ expires_at: identityWithState.access_token_expires_at,
498
+ gateway: gatewayUrl,
499
+ };
456
500
  }
457
- catch (e) {
458
- throw new StateError(`local certificate missing and recovery failed: ${e}. ` +
459
- `Run auth.createAid() to register a new identity.`);
501
+ if (!identity.cert) {
502
+ // 尝试下载恢复证书
503
+ try {
504
+ const recovered = await this._recoverCertViaDownload(gatewayUrl, identity);
505
+ Object.assign(identity, recovered);
506
+ await this._persistIdentity(identity);
507
+ }
508
+ catch (e) {
509
+ throw new StateError(`local certificate missing and recovery failed: ${e}. ` +
510
+ `Run auth.createAid() to register a new identity.`);
511
+ }
460
512
  }
461
- }
462
- let login;
463
- try {
464
- login = await this._login(gatewayUrl, identity);
465
- }
466
- catch (e) {
467
- // 证书未在服务端注册或公钥不匹配 — 自动重新注册
468
- if (e instanceof AuthError && (String(e.message).includes('not registered') || String(e.message).includes('public key mismatch'))) {
469
- console.warn(`[auth] 证书未在服务端注册,自动重新注册: aid=${identity.aid}`);
470
- const created = await this._createAid(gatewayUrl, identity);
471
- identity.cert = created.cert;
472
- await this._persistIdentity(identity);
513
+ let login;
514
+ try {
473
515
  login = await this._login(gatewayUrl, identity);
474
516
  }
475
- else {
476
- throw e;
517
+ catch (e) {
518
+ // 证书未在服务端注册或公钥不匹配 — 自动重新注册
519
+ if (e instanceof AuthError && (String(e.message).includes('not registered') || String(e.message).includes('public key mismatch'))) {
520
+ this._log.warn(`[auth] cert not registered on server, auto re-register: aid=${identity.aid}`);
521
+ const created = await this._createAid(gatewayUrl, identity);
522
+ identity.cert = created.cert;
523
+ await this._persistIdentity(identity);
524
+ login = await this._login(gatewayUrl, identity);
525
+ }
526
+ else {
527
+ throw e;
528
+ }
477
529
  }
530
+ this._rememberTokens(identity, login);
531
+ await this._validateNewCert(identity, gatewayUrl);
532
+ await this._persistIdentity(identity);
533
+ this._aid = identity.aid;
534
+ this._log.debug(`authenticate exit: elapsed=${Date.now() - tStart}ms aid=${identity.aid}`);
535
+ return {
536
+ aid: identity.aid,
537
+ access_token: identity.access_token,
538
+ refresh_token: identity.refresh_token,
539
+ expires_at: identity.access_token_expires_at,
540
+ gateway: gatewayUrl,
541
+ };
542
+ }
543
+ catch (err) {
544
+ this._log.debug(`authenticate exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
545
+ throw err;
478
546
  }
479
- this._rememberTokens(identity, login);
480
- await this._validateNewCert(identity, gatewayUrl);
481
- await this._persistIdentity(identity);
482
- this._aid = identity.aid;
483
- return {
484
- aid: identity.aid,
485
- access_token: identity.access_token,
486
- refresh_token: identity.refresh_token,
487
- expires_at: identity.access_token_expires_at,
488
- gateway: gatewayUrl,
489
- };
490
547
  }
491
548
  /**
492
549
  * 确保已认证(如无身份则先注册再登录)。
493
550
  */
494
551
  async ensureAuthenticated(gatewayUrl) {
495
- const identity = await this._ensureIdentity();
496
- if (!identity.cert) {
497
- const created = await this._createAid(gatewayUrl, identity);
498
- Object.assign(identity, created);
552
+ const tStart = Date.now();
553
+ this._log.debug(`ensureAuthenticated enter: gateway=${gatewayUrl}`);
554
+ try {
555
+ const identity = await this._ensureIdentity();
556
+ if (!identity.cert) {
557
+ const created = await this._createAid(gatewayUrl, identity);
558
+ Object.assign(identity, created);
559
+ await this._persistIdentity(identity);
560
+ }
561
+ const login = await this._login(gatewayUrl, identity);
562
+ this._rememberTokens(identity, login);
563
+ await this._validateNewCert(identity, gatewayUrl);
499
564
  await this._persistIdentity(identity);
565
+ const token = (identity.access_token || identity.token || identity.kite_token);
566
+ if (!token)
567
+ throw new AuthError('login2 did not return access token');
568
+ this._log.debug(`ensureAuthenticated exit: elapsed=${Date.now() - tStart}ms aid=${identity.aid}`);
569
+ return { token, identity };
570
+ }
571
+ catch (err) {
572
+ this._log.debug(`ensureAuthenticated exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
573
+ throw err;
500
574
  }
501
- const login = await this._login(gatewayUrl, identity);
502
- this._rememberTokens(identity, login);
503
- await this._validateNewCert(identity, gatewayUrl);
504
- await this._persistIdentity(identity);
505
- const token = (identity.access_token || identity.token || identity.kite_token);
506
- if (!token)
507
- throw new AuthError('login2 did not return access token');
508
- return { token, identity };
509
575
  }
510
576
  /**
511
577
  * 使用已有 token 初始化 WebSocket 会话。
512
578
  */
513
579
  async initializeWithToken(transport, challenge, accessToken, opts) {
514
- const params = isJsonObject(challenge?.params) ? challenge.params : {};
515
- const nonce = String(params.nonce ?? '');
516
- if (!nonce)
517
- throw new AuthError('gateway challenge missing nonce');
518
- this.setInstanceContext({
519
- deviceId: String(opts?.deviceId ?? this._deviceId ?? ''),
520
- slotId: String(opts?.slotId ?? this._slotId ?? ''),
521
- });
522
- await this._initializeSession(transport, nonce, accessToken, {
523
- deviceId: String(opts?.deviceId ?? this._deviceId ?? ''),
524
- slotId: String(opts?.slotId ?? this._slotId ?? ''),
525
- deliveryMode: opts?.deliveryMode ?? null,
526
- });
580
+ const tStart = Date.now();
581
+ this._log.debug(`initializeWithToken enter: device_id=${opts?.deviceId ?? this._deviceId} slot_id=${opts?.slotId ?? this._slotId}`);
582
+ try {
583
+ const params = isJsonObject(challenge?.params) ? challenge.params : {};
584
+ const nonce = String(params.nonce ?? '');
585
+ if (!nonce)
586
+ throw new AuthError('gateway challenge missing nonce');
587
+ this.setInstanceContext({
588
+ deviceId: String(opts?.deviceId ?? this._deviceId ?? ''),
589
+ slotId: String(opts?.slotId ?? this._slotId ?? ''),
590
+ });
591
+ await this._initializeSession(transport, nonce, accessToken, {
592
+ deviceId: String(opts?.deviceId ?? this._deviceId ?? ''),
593
+ slotId: String(opts?.slotId ?? this._slotId ?? ''),
594
+ deliveryMode: opts?.deliveryMode ?? null,
595
+ connectionKind: opts?.connectionKind ?? 'long',
596
+ shortTtlMs: opts?.shortTtlMs ?? 0,
597
+ extraInfo: opts?.extraInfo,
598
+ });
599
+ this._log.debug(`initializeWithToken exit: elapsed=${Date.now() - tStart}ms`);
600
+ }
601
+ catch (err) {
602
+ this._log.debug(`initializeWithToken exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
603
+ throw err;
604
+ }
527
605
  }
528
606
  /**
529
607
  * 连接会话 — 多策略认证:显式 token → 缓存 token → refresh → 重新登录。
530
608
  */
531
609
  async connectSession(transport, challenge, gatewayUrl, accessToken) {
532
- const params = isJsonObject(challenge?.params) ? challenge.params : {};
533
- const nonce = String(params.nonce ?? '');
534
- if (!nonce)
535
- throw new AuthError('gateway challenge missing nonce');
536
- const connectOptions = isJsonObject(accessToken)
537
- ? accessToken
538
- : { accessToken };
539
- const deviceId = String(connectOptions.deviceId ?? this._deviceId ?? '');
540
- const slotId = String(connectOptions.slotId ?? this._slotId ?? '');
541
- const deliveryMode = connectOptions.deliveryMode ?? null;
542
- this.setInstanceContext({ deviceId, slotId });
543
- let identity;
610
+ const tStart = Date.now();
611
+ const explicitTokenInput = isJsonObject(accessToken) ? !!accessToken.accessToken : !!accessToken;
612
+ this._log.debug(`connectSession enter: gateway=${gatewayUrl} has_explicit_token=${explicitTokenInput}`);
544
613
  try {
545
- identity = await this.loadIdentity();
546
- }
547
- catch {
548
- identity = null;
549
- }
550
- // 策略 1: 显式 token
551
- const explicitToken = String(connectOptions.accessToken ?? '');
552
- if (explicitToken && identity !== null) {
614
+ const params = isJsonObject(challenge?.params) ? challenge.params : {};
615
+ const nonce = String(params.nonce ?? '');
616
+ if (!nonce)
617
+ throw new AuthError('gateway challenge missing nonce');
618
+ const connectOptions = (isJsonObject(accessToken)
619
+ ? accessToken
620
+ : { accessToken });
621
+ const deviceId = String(connectOptions.deviceId ?? this._deviceId ?? '');
622
+ const slotId = String(connectOptions.slotId ?? this._slotId ?? '');
623
+ const deliveryMode = connectOptions.deliveryMode ?? null;
624
+ const connectionKind = connectOptions.connectionKind ?? 'long';
625
+ const shortTtlMs = connectOptions.shortTtlMs ?? 0;
626
+ const extraInfo = connectOptions.extraInfo;
627
+ this.setInstanceContext({ deviceId, slotId });
628
+ let identity;
553
629
  try {
554
- await this._initializeSession(transport, nonce, explicitToken, {
555
- deviceId,
556
- slotId,
557
- deliveryMode,
558
- });
559
- identity.access_token = explicitToken;
560
- await this._persistIdentity(identity);
561
- return { token: explicitToken, identity };
630
+ identity = await this.loadIdentity();
562
631
  }
563
- catch (e) {
564
- if (!(e instanceof AuthError))
565
- throw e;
566
- // 显式 token 失败,继续尝试其他方式
632
+ catch {
633
+ identity = null;
567
634
  }
568
- }
569
- // 无本地身份 先注册 + 登录
570
- if (identity === null) {
571
- const authContext = await this.ensureAuthenticated(gatewayUrl);
572
- const token = authContext.token;
573
- await this._initializeSession(transport, nonce, token, {
574
- deviceId,
575
- slotId,
576
- deliveryMode,
577
- });
578
- return authContext;
579
- }
580
- // 策略 2: 缓存 token
581
- const cachedToken = this._getCachedAccessToken(identity);
582
- if (cachedToken) {
583
- try {
584
- await this._initializeSession(transport, nonce, cachedToken, {
635
+ // 策略 1: 显式 token
636
+ const explicitToken = String(connectOptions.accessToken ?? '');
637
+ if (explicitToken && identity !== null) {
638
+ try {
639
+ await this._initializeSession(transport, nonce, explicitToken, {
640
+ deviceId,
641
+ slotId,
642
+ deliveryMode,
643
+ connectionKind,
644
+ shortTtlMs,
645
+ extraInfo,
646
+ });
647
+ identity.access_token = explicitToken;
648
+ await this._persistIdentity(identity);
649
+ this._log.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=explicit_token aid=${identity.aid}`);
650
+ return { token: explicitToken, identity };
651
+ }
652
+ catch (e) {
653
+ if (!(e instanceof AuthError))
654
+ throw e;
655
+ // 显式 token 失败,继续尝试其他方式
656
+ }
657
+ }
658
+ // 无本地身份 — 先注册 + 登录
659
+ if (identity === null) {
660
+ const authContext = await this.ensureAuthenticated(gatewayUrl);
661
+ const token = authContext.token;
662
+ await this._initializeSession(transport, nonce, token, {
585
663
  deviceId,
586
664
  slotId,
587
665
  deliveryMode,
666
+ connectionKind,
667
+ shortTtlMs,
668
+ extraInfo,
588
669
  });
589
- return { token: cachedToken, identity };
590
- }
591
- catch (e) {
592
- if (!(e instanceof AuthError))
593
- throw e;
594
- // 缓存 token 失败,尝试刷新
670
+ this._log.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=ensure_authenticated`);
671
+ return authContext;
595
672
  }
596
- }
597
- // 策略 3: refresh token
598
- const refreshToken = String(identity.refresh_token ?? '');
599
- if (refreshToken) {
600
- try {
601
- identity = await this.refreshCachedTokens(gatewayUrl, identity);
602
- const refreshedToken = this._getCachedAccessToken(identity);
603
- if (refreshedToken) {
604
- await this._initializeSession(transport, nonce, refreshedToken, {
673
+ // 策略 2: 缓存 token
674
+ const cachedToken = this._getCachedAccessToken(identity);
675
+ if (cachedToken) {
676
+ try {
677
+ await this._initializeSession(transport, nonce, cachedToken, {
605
678
  deviceId,
606
679
  slotId,
607
680
  deliveryMode,
681
+ connectionKind,
682
+ shortTtlMs,
683
+ extraInfo,
608
684
  });
609
- return { token: refreshedToken, identity };
685
+ this._log.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=cached_token aid=${identity.aid}`);
686
+ return { token: cachedToken, identity };
687
+ }
688
+ catch (e) {
689
+ if (!(e instanceof AuthError))
690
+ throw e;
691
+ // 缓存 token 失败,尝试刷新
610
692
  }
611
693
  }
612
- catch (e) {
613
- if (!(e instanceof AuthError))
614
- throw e;
615
- // refresh 失败,重新登录
694
+ // 策略 3: refresh token
695
+ const refreshToken = String(identity.refresh_token ?? '');
696
+ if (refreshToken) {
697
+ try {
698
+ identity = await this.refreshCachedTokens(gatewayUrl, identity);
699
+ const refreshedToken = this._getCachedAccessToken(identity);
700
+ if (refreshedToken) {
701
+ await this._initializeSession(transport, nonce, refreshedToken, {
702
+ deviceId,
703
+ slotId,
704
+ deliveryMode,
705
+ connectionKind,
706
+ shortTtlMs,
707
+ extraInfo,
708
+ });
709
+ this._log.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=refresh aid=${identity.aid}`);
710
+ return { token: refreshedToken, identity };
711
+ }
712
+ }
713
+ catch (e) {
714
+ if (!(e instanceof AuthError))
715
+ throw e;
716
+ // refresh 失败,重新登录
717
+ }
616
718
  }
719
+ // 策略 4: 重新登录
720
+ const login = await this.authenticate(gatewayUrl, identity.aid);
721
+ const token = String(login.access_token ?? '');
722
+ if (!token)
723
+ throw new AuthError('authenticate did not return access_token');
724
+ await this._initializeSession(transport, nonce, token, {
725
+ deviceId,
726
+ slotId,
727
+ deliveryMode,
728
+ connectionKind,
729
+ shortTtlMs,
730
+ extraInfo,
731
+ });
732
+ identity = await this.loadIdentity(identity.aid);
733
+ this._log.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=re_login aid=${identity.aid}`);
734
+ return { token, identity };
735
+ }
736
+ catch (err) {
737
+ this._log.debug(`connectSession exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
738
+ throw err;
617
739
  }
618
- // 策略 4: 重新登录
619
- const login = await this.authenticate(gatewayUrl, identity.aid);
620
- const token = String(login.access_token ?? '');
621
- if (!token)
622
- throw new AuthError('authenticate did not return access_token');
623
- await this._initializeSession(transport, nonce, token, {
624
- deviceId,
625
- slotId,
626
- deliveryMode,
627
- });
628
- identity = await this.loadIdentity(identity.aid);
629
- return { token, identity };
630
740
  }
631
741
  /**
632
742
  * 刷新 token。
633
743
  */
634
744
  async refreshCachedTokens(gatewayUrl, identity) {
635
- const refreshToken = String(identity.refresh_token ?? '');
636
- if (!refreshToken)
637
- throw new AuthError('missing refresh_token');
638
- const refreshed = await this._refreshAccessToken(gatewayUrl, refreshToken);
639
- this._rememberTokens(identity, refreshed);
640
- await this._validateNewCert(identity, gatewayUrl);
641
- await this._persistIdentity(identity);
642
- return identity;
745
+ const tStart = Date.now();
746
+ this._log.debug(`refreshCachedTokens enter: aid=${identity.aid} gateway=${gatewayUrl}`);
747
+ try {
748
+ const refreshToken = String(identity.refresh_token ?? '');
749
+ if (!refreshToken)
750
+ throw new AuthError('missing refresh_token');
751
+ const refreshed = await this._refreshAccessToken(gatewayUrl, refreshToken);
752
+ this._rememberTokens(identity, refreshed);
753
+ await this._validateNewCert(identity, gatewayUrl);
754
+ await this._persistIdentity(identity);
755
+ this._log.debug(`refreshCachedTokens exit: elapsed=${Date.now() - tStart}ms aid=${identity.aid}`);
756
+ return identity;
757
+ }
758
+ catch (err) {
759
+ this._log.debug(`refreshCachedTokens exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
760
+ throw err;
761
+ }
643
762
  }
644
763
  /**
645
764
  * 统一的对端证书验证入口:时间有效性 + 链验证 + CRL + OCSP + AID 绑定。
646
765
  */
647
766
  async verifyPeerCertificate(gatewayUrl, certPem, expectedAid) {
648
- const cert = parseCertDer(certPem);
649
- const now = Date.now() / 1000;
650
- ensureCertTimeValid(cert, 'peer certificate', now);
651
- await this._verifyAuthCertChain(gatewayUrl, cert, expectedAid);
767
+ const tStart = Date.now();
768
+ this._log.debug(`verifyPeerCertificate enter: aid=${expectedAid} gateway=${gatewayUrl}`);
652
769
  try {
653
- await this._verifyAuthCertRevocation(gatewayUrl, cert, expectedAid);
654
- }
655
- catch (e) {
656
- const errMsg = e instanceof Error ? e.message : String(e);
657
- if (/revoked/i.test(errMsg))
658
- throw e instanceof AuthError ? e : new AuthError(errMsg);
659
- console.warn('[aun_core.auth] CRL 检查不可用,降级继续:', errMsg);
660
- }
661
- try {
662
- await this._verifyAuthCertOcsp(gatewayUrl, cert, expectedAid);
663
- }
664
- catch (e) {
665
- const errMsg = e instanceof Error ? e.message : String(e);
666
- if (/revoked/i.test(errMsg))
667
- throw e instanceof AuthError ? e : new AuthError(errMsg);
668
- console.warn('[aun_core.auth] OCSP 检查不可用,降级继续:', errMsg);
770
+ const cert = parseCertDer(certPem);
771
+ const now = Date.now() / 1000;
772
+ ensureCertTimeValid(cert, 'peer certificate', now);
773
+ await this._verifyAuthCertChain(gatewayUrl, cert, expectedAid);
774
+ try {
775
+ await this._verifyAuthCertRevocation(gatewayUrl, cert, expectedAid);
776
+ }
777
+ catch (e) {
778
+ const errMsg = e instanceof Error ? e.message : String(e);
779
+ if (/revoked/i.test(errMsg))
780
+ throw e instanceof AuthError ? e : new AuthError(errMsg);
781
+ this._log.warn('[aun_core.auth] CRL check unavailable, degrade and continue:', errMsg);
782
+ }
783
+ try {
784
+ await this._verifyAuthCertOcsp(gatewayUrl, cert, expectedAid);
785
+ }
786
+ catch (e) {
787
+ const errMsg = e instanceof Error ? e.message : String(e);
788
+ if (/revoked/i.test(errMsg))
789
+ throw e instanceof AuthError ? e : new AuthError(errMsg);
790
+ this._log.warn('[aun_core.auth] OCSP check unavailable, degrade and continue:', errMsg);
791
+ }
792
+ if (cert.subjectCN !== expectedAid) {
793
+ throw new AuthError(`peer cert CN mismatch: expected ${expectedAid}, got ${cert.subjectCN || 'none'}`);
794
+ }
795
+ this._log.debug(`verifyPeerCertificate exit: elapsed=${Date.now() - tStart}ms aid=${expectedAid}`);
669
796
  }
670
- if (cert.subjectCN !== expectedAid) {
671
- throw new AuthError(`peer cert CN mismatch: expected ${expectedAid}, got ${cert.subjectCN || 'none'}`);
797
+ catch (err) {
798
+ this._log.debug(`verifyPeerCertificate exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
799
+ throw err;
672
800
  }
673
801
  }
674
802
  // ── 内部方法:短连接 RPC ──────────────────────────
@@ -850,7 +978,9 @@ export class AuthFlow {
850
978
  }
851
979
  /** 初始化 WebSocket 会话(auth.connect RPC) */
852
980
  async _initializeSession(transport, nonce, token, opts) {
853
- const result = await transport.call('auth.connect', {
981
+ const connectionKind = opts?.connectionKind ?? 'long';
982
+ const shortTtlMs = opts?.shortTtlMs ?? 0;
983
+ const request = {
854
984
  nonce,
855
985
  auth: { method: 'kite_token', token },
856
986
  protocol: { min: '1.0', max: '1.0' },
@@ -858,7 +988,22 @@ export class AuthFlow {
858
988
  client: { slot_id: String(opts?.slotId ?? this._slotId ?? '') },
859
989
  delivery_mode: opts?.deliveryMode ?? { mode: 'fanout' },
860
990
  capabilities: { e2ee: true, group_e2ee: true },
861
- });
991
+ };
992
+ // 长短连接选项:默认 long 时不写入 options(保持 wire 兼容)
993
+ if (connectionKind === 'short') {
994
+ const options = { kind: 'short' };
995
+ if (shortTtlMs > 0) {
996
+ options.short_ttl_ms = shortTtlMs;
997
+ }
998
+ request.options = options;
999
+ }
1000
+ // extra_info:应用层自定义信息(PID/HOME/备注等),踢人时透传给被踢方
1001
+ const extraInfo = opts?.extraInfo;
1002
+ if (extraInfo && Object.keys(extraInfo).length > 0) {
1003
+ request.extra_info = extraInfo;
1004
+ }
1005
+ this._log.debug(`auth.connect send: device_id=${opts?.deviceId ?? ''} slot_id=${opts?.slotId ?? ''} kind=${connectionKind}`);
1006
+ const result = await transport.call('auth.connect', request);
862
1007
  const status = isJsonObject(result) ? result.status : undefined;
863
1008
  if (status !== 'ok') {
864
1009
  throw new AuthError(`initialize failed: ${JSON.stringify(result)}`);
@@ -889,7 +1034,7 @@ export class AuthFlow {
889
1034
  const errMsg = e instanceof Error ? e.message : String(e);
890
1035
  if (/revoked/i.test(errMsg))
891
1036
  throw e instanceof AuthError ? e : new AuthError(errMsg);
892
- console.warn('[aun_core.auth] CRL 检查不可用,降级继续:', errMsg);
1037
+ this._log.warn('[aun_core.auth] CRL check unavailable, degrade and continue:', errMsg);
893
1038
  }
894
1039
  // 验证 OCSP
895
1040
  try {
@@ -899,7 +1044,7 @@ export class AuthFlow {
899
1044
  const errMsg = e instanceof Error ? e.message : String(e);
900
1045
  if (/revoked/i.test(errMsg))
901
1046
  throw e instanceof AuthError ? e : new AuthError(errMsg);
902
- console.warn('[aun_core.auth] OCSP 检查不可用,降级继续:', errMsg);
1047
+ this._log.warn('[aun_core.auth] OCSP check unavailable, degrade and continue:', errMsg);
903
1048
  }
904
1049
  // 验证 client_nonce 签名
905
1050
  try {
@@ -1224,7 +1369,7 @@ export class AuthFlow {
1224
1369
  const errMsg = e instanceof Error ? e.message : String(e);
1225
1370
  if (/revoked/i.test(errMsg))
1226
1371
  throw e instanceof AuthError ? e : new AuthError(errMsg);
1227
- console.warn('[aun_core.auth] CRL 检查不可用,降级继续:', errMsg);
1372
+ this._log.warn('[aun_core.auth] CRL check unavailable, degrade and continue:', errMsg);
1228
1373
  }
1229
1374
  try {
1230
1375
  await this._verifyAuthCertOcsp(gatewayUrl, cert);
@@ -1233,7 +1378,7 @@ export class AuthFlow {
1233
1378
  const errMsg = e instanceof Error ? e.message : String(e);
1234
1379
  if (/revoked/i.test(errMsg))
1235
1380
  throw e instanceof AuthError ? e : new AuthError(errMsg);
1236
- console.warn('[aun_core.auth] OCSP 检查不可用,降级继续:', errMsg);
1381
+ this._log.warn('[aun_core.auth] OCSP check unavailable, degrade and continue:', errMsg);
1237
1382
  }
1238
1383
  }
1239
1384
  // 验证通过,正式接受
@@ -1241,7 +1386,7 @@ export class AuthFlow {
1241
1386
  }
1242
1387
  catch (e) {
1243
1388
  // 验证失败,静默拒绝(不影响主流程)
1244
- console.warn(`拒绝服务端返回的 new_cert (${identity.aid}):`, e);
1389
+ this._log.warn(`reject server-returned new_cert (${identity.aid}):`, e);
1245
1390
  }
1246
1391
  // active_cert 同步:验证公钥匹配后更新本地 cert
1247
1392
  const activeCertPem = identity._pending_active_cert;
@@ -1256,12 +1401,12 @@ export class AuthFlow {
1256
1401
  identity.cert = activeCertPem;
1257
1402
  }
1258
1403
  else {
1259
- console.warn(`[auth] 服务端 active_cert 公钥与本地私钥不匹配,拒绝同步 (aid=${identity.aid})`);
1404
+ this._log.warn(`[auth] active_cert public key mismatches local private key, reject sync (aid=${identity.aid})`);
1260
1405
  }
1261
1406
  }
1262
1407
  }
1263
1408
  catch (e) {
1264
- console.warn(`[auth] active_cert 同步异常 (${identity.aid}):`, e);
1409
+ this._log.warn(`[auth] active_cert sync exception (${identity.aid}):`, e);
1265
1410
  }
1266
1411
  }
1267
1412
  }
@@ -1291,13 +1436,23 @@ export class AuthFlow {
1291
1436
  /** 确保本地有密钥对(没有则生成) */
1292
1437
  async _ensureLocalIdentity(aid) {
1293
1438
  const existing = await this._keystore.loadIdentity(aid);
1294
- if (existing) {
1439
+ // 必须确认有 keypair(private_key_pem + public_key_der_b64)才算"已存在"
1440
+ // 否则 keystore 可能只有 metadata(如 gateway_url)但没有真正的密钥材料
1441
+ if (existing && existing.private_key_pem && existing.public_key_der_b64) {
1295
1442
  this._aid = aid;
1296
1443
  return existing;
1297
1444
  }
1298
1445
  const identity = await this._crypto.generateIdentity();
1299
1446
  identity.aid = aid;
1300
- await this._persistIdentity(identity);
1447
+ // 保留 keystore 已有的 metadata(如 gateway_url),避免覆盖
1448
+ if (existing) {
1449
+ for (const [k, v] of Object.entries(existing)) {
1450
+ if (k !== 'aid' && !(k in identity)) {
1451
+ identity[k] = v;
1452
+ }
1453
+ }
1454
+ }
1455
+ await this._persistIdentity(identity); // 立即持久化 keypair,避免服务端拒绝后丢失
1301
1456
  this._aid = aid;
1302
1457
  return identity;
1303
1458
  }