1- import { getLogger } from '@/common/logger'
1+ import { getLogger , isDebugEnabled } from '@/common/logger'
22import { TcpConnection } from './connection'
33import { buildServicePacket , buildServicePacket13 , parseServicePacket , EncryptType , PacketContext , SsoPacket } from './packet'
44import { generateEcdhKeyPair , EcdhKeyPair } from './ecdh'
@@ -23,6 +23,8 @@ export interface DirectClientConfig {
2323 dataDir ?: string
2424 /** 当前账号 uin, 可选. */
2525 uin ?: number
26+ /** 接入点 CDN: 'cf' (默认) / 'china'. 传给 SignProxy 决定 base_url + TLS pin. */
27+ cdn ?: string
2628}
2729
2830const DEFAULT_CONFIG : DirectClientConfig = {
@@ -63,6 +65,12 @@ export class DirectProtocolClient extends EventEmitter {
6365 private signTokenRefreshInflight : Promise < void > | null = null
6466 private signTokenLastFetchAt = 0
6567 private heartbeatAliveTimer : NodeJS . Timeout | null = null
68+ // 计时诊断: 按 seq 记响应帧「进 handlePacket」的时刻, 拆 wire vs parse.
69+ private frameArriveAt : Map < number , number > = new Map ( )
70+ // event loop lag 采样: setInterval 期望 50ms 一跳, 实测跳间隔 - 50 = 主线程被阻塞的量.
71+ private maxLoopLag = 0
72+ private loopLagTimer : NodeJS . Timeout | null = null
73+ private lastLoopTick = 0
6674
6775 constructor ( config : Partial < DirectClientConfig > = { } ) {
6876 super ( )
@@ -96,6 +104,7 @@ export class DirectProtocolClient extends EventEmitter {
96104 authToken : this . config . authToken ,
97105 machineGuid : this . guid ,
98106 uin : this . config . uin ,
107+ cdn : this . config . cdn ,
99108 sendPacket : async ( { cmd, body } ) => {
100109 const resp = ( await this . sendCommand ( cmd , Buffer . from ( body ) ) ) . payload
101110 logger . debug ( `[relay] ${ cmd } : req=${ body . length } B resp=${ resp . length } B hex=%h` , resp )
@@ -111,6 +120,7 @@ export class DirectProtocolClient extends EventEmitter {
111120 await this . ensureSignSetup ( )
112121 await this . conn . connect ( { useIPv6 : this . config . useIPv6 } )
113122 this . emit ( 'connected' )
123+ this . startLoopLagMonitor ( )
114124
115125 // Send initial heartbeat (required before other commands)
116126 await this . sendHeartbeat ( )
@@ -135,6 +145,28 @@ export class DirectProtocolClient extends EventEmitter {
135145 }
136146 }
137147
148+ // 诊断用: 每 50ms 一跳, 记录实际间隔超出 50ms 的部分 = 主线程被同步阻塞的时长.
149+ // native signRequest 若是同步阻塞调用, 会在这里体现, 且会推迟响应帧回调 (灌进 wire/netRTT).
150+ // 只在 --debug 下开: 常驻 50ms setInterval 在生产会白费 CPU.
151+ private startLoopLagMonitor ( ) : void {
152+ if ( this . loopLagTimer || ! isDebugEnabled ( ) ) return
153+ const EXPECT = 50
154+ this . lastLoopTick = Date . now ( )
155+ this . loopLagTimer = setInterval ( ( ) => {
156+ const now = Date . now ( )
157+ const lag = now - this . lastLoopTick - EXPECT
158+ if ( lag > this . maxLoopLag ) this . maxLoopLag = lag
159+ this . lastLoopTick = now
160+ } , EXPECT )
161+ this . loopLagTimer . unref ?.( )
162+ }
163+
164+ private takeMaxLoopLag ( ) : number {
165+ const v = this . maxLoopLag
166+ this . maxLoopLag = 0
167+ return v
168+ }
169+
138170
139171 async sendHeartbeat ( ) : Promise < void > {
140172 const seq = this . nextSeq ( )
@@ -727,14 +759,20 @@ export class DirectProtocolClient extends EventEmitter {
727759 const ctx = this . getPacketContext ( )
728760 const enc = encryptType ?? ( this . session ? EncryptType . EncryptD2Key : EncryptType . EncryptEmpty )
729761
762+ const t0 = Date . now ( )
763+ let tTokenDone = t0
764+ let tSignDone = t0
765+
730766 let signResult : SignResult | null = null
731767 if ( this . config . authToken && this . SIGN_ALLOWLIST . has ( cmd ) ) {
732768 // uin 优先 session (登录成功后), 未登录时 fallback 到构造时传的 config.uin.
733769 // 快速登录场景 session 解不开时 session 为 null, 但 config.uin 已从明文元数据 / -q 拿到,
734770 // 缺了它 sign 服务器会 400 'missing uin' 直接拒
735771 const uin = this . session ?. uin ? Number ( this . session . uin ) : ( this . config . uin || undefined )
736772 await this . ensureSignTokenFresh ( uin )
773+ tTokenDone = Date . now ( )
737774 signResult = await requestSign ( cmd , payload , seq , this . guid , AppInfo . qua , uin , this . session ?. signToken12B )
775+ tSignDone = Date . now ( )
738776 if ( signResult ?. token . length === 0 ) {
739777 signResult . token = Buffer . from ( this . session ?. signToken12B ?? '' )
740778 }
@@ -753,25 +791,41 @@ export class DirectProtocolClient extends EventEmitter {
753791 logger . debug ( `[SSO send] ${ cmd } seq=${ seq } frame=${ packet . length } B hex=%h` , packet )
754792 }
755793
794+ const tSendStart = Date . now ( )
756795 return new Promise ( ( resolve , reject ) => {
757796 const timer = setTimeout ( ( ) => {
758797 this . pendingPackets . delete ( seq )
759798 reject ( new Error ( `Command ${ cmd } timed out after ${ timeout } ms` ) )
760799 } , timeout )
761800
762- this . pendingPackets . set ( seq , { resolve, reject, timeout : timer } )
801+ const finish = ( fn : ( v : any ) => void ) => ( v : any ) => {
802+ if ( isDebugEnabled ( ) ) {
803+ const tDone = Date . now ( )
804+ const arrive = this . frameArriveAt . get ( seq )
805+ this . frameArriveAt . delete ( seq )
806+ const wire = arrive ? arrive - tSendStart : - 1 // send -> 字节到达 socket (网络+服务端+event-loop 投递)
807+ const parse = arrive ? tDone - arrive : - 1 // 字节到达 -> resolve (解密/解析, 纯本地)
808+ logger . debug (
809+ `[timing] ${ cmd } seq=${ seq } : token=${ tTokenDone - t0 } ms sign=${ tSignDone - tTokenDone } ms wire=${ wire } ms parse=${ parse } ms netRTT=${ tDone - tSendStart } ms maxLoopLag=${ this . takeMaxLoopLag ( ) } ms total=${ tDone - t0 } ms`
810+ )
811+ }
812+ fn ( v )
813+ }
814+
815+ this . pendingPackets . set ( seq , { resolve : finish ( resolve ) , reject : finish ( reject ) , timeout : timer } )
763816 this . conn . send ( packet )
764817 } )
765818 }
766819
767820 private handlePacket ( frame : Buffer ) : void {
821+ const tArrive = Date . now ( )
768822 const d2Key = this . session ?. d2Key || Buffer . alloc ( 16 )
769823 const parsed = parseServicePacket ( frame , d2Key )
770824 if ( ! parsed ) {
771825 this . emit ( 'error' , new Error ( 'Failed to parse incoming packet' ) )
772826 return
773827 }
774-
828+ if ( isDebugEnabled ( ) ) this . frameArriveAt . set ( parsed . seq , tArrive )
775829 const pending = this . pendingPackets . get ( parsed . seq )
776830 if ( pending ) {
777831 clearTimeout ( pending . timeout )
0 commit comments