@@ -15,9 +15,11 @@ export { pluginVersion };
1515const FEISHU_USER_AGENT = `openclaw-feishu-builtin/${ pluginVersion } /${ process . platform } ` ;
1616export { FEISHU_USER_AGENT } ;
1717
18- const FEISHU_WS_CONFIG = {
18+ const FEISHU_WS_CLIENT_CONFIG_DEFAULTS = {
1919 PingInterval : 30 ,
20- PingTimeout : 3 ,
20+ ReconnectCount : - 1 ,
21+ ReconnectInterval : 120 ,
22+ ReconnectNonce : 30 ,
2123} as const ;
2224
2325/** User-Agent header value for all Feishu API requests. */
@@ -87,6 +89,71 @@ type FeishuHttpInstanceLike = Pick<
8789 "request" | "get" | "post" | "put" | "patch" | "delete" | "head" | "options"
8890> ;
8991
92+ export type FeishuWsLifecycleHooks = {
93+ onReady ?: ( ) => void ;
94+ onError ?: ( err : Error ) => void ;
95+ onReconnecting ?: ( ) => void ;
96+ onReconnected ?: ( ) => void ;
97+ } ;
98+
99+ type FeishuHttpResponseTransform = < R > ( response : R ) => R ;
100+
101+ function isRecord ( value : unknown ) : value is Record < string , unknown > {
102+ return typeof value === "object" && value !== null ;
103+ }
104+
105+ function coercePositiveNumber ( value : unknown , fallback : number ) : number {
106+ return typeof value === "number" && Number . isFinite ( value ) && value > 0 ? value : fallback ;
107+ }
108+
109+ function coerceNonNegativeNumber ( value : unknown , fallback : number ) : number {
110+ return typeof value === "number" && Number . isFinite ( value ) && value >= 0 ? value : fallback ;
111+ }
112+
113+ function coerceReconnectCount ( value : unknown , fallback : number ) : number {
114+ if ( typeof value !== "number" || ! Number . isFinite ( value ) ) {
115+ return fallback ;
116+ }
117+ const normalized = Math . floor ( value ) ;
118+ return normalized >= - 1 ? normalized : fallback ;
119+ }
120+
121+ function sanitizeFeishuWsEndpointResponse < R > ( response : R ) : R {
122+ if ( ! isRecord ( response ) || ! isRecord ( response . data ) ) {
123+ return response ;
124+ }
125+ const clientConfig = response . data . ClientConfig ;
126+ if ( ! isRecord ( clientConfig ) ) {
127+ return response ;
128+ }
129+
130+ return {
131+ ...response ,
132+ data : {
133+ ...response . data ,
134+ ClientConfig : {
135+ ...clientConfig ,
136+ PingInterval : coercePositiveNumber (
137+ clientConfig . PingInterval ,
138+ FEISHU_WS_CLIENT_CONFIG_DEFAULTS . PingInterval ,
139+ ) ,
140+ ReconnectCount : coerceReconnectCount (
141+ clientConfig . ReconnectCount ,
142+ FEISHU_WS_CLIENT_CONFIG_DEFAULTS . ReconnectCount ,
143+ ) ,
144+ ReconnectInterval : coercePositiveNumber (
145+ clientConfig . ReconnectInterval ,
146+ FEISHU_WS_CLIENT_CONFIG_DEFAULTS . ReconnectInterval ,
147+ ) ,
148+ ReconnectNonce : coerceNonNegativeNumber (
149+ clientConfig . ReconnectNonce ,
150+ FEISHU_WS_CLIENT_CONFIG_DEFAULTS . ReconnectNonce ,
151+ ) ,
152+ } ,
153+ } ,
154+ } as R ;
155+ }
156+
90157async function getWsProxyAgent ( ) {
91158 return resolveAmbientNodeProxyAgent < Agent > ( ) ;
92159}
@@ -115,22 +182,30 @@ function resolveDomain(domain: FeishuDomain | undefined): Lark.Domain | string {
115182 * but injects a default request timeout and User-Agent header to prevent
116183 * indefinite hangs and set a standardized User-Agent per OAPI best practices.
117184 */
118- function createTimeoutHttpInstance ( defaultTimeoutMs : number ) : Lark . HttpInstance {
185+ function createTimeoutHttpInstance (
186+ defaultTimeoutMs : number ,
187+ transformResponse ?: FeishuHttpResponseTransform ,
188+ ) : Lark . HttpInstance {
119189 const base : FeishuHttpInstanceLike = feishuClientSdk . defaultHttpInstance ;
120190
121191 function injectTimeout < D > ( opts ?: Lark . HttpRequestOptions < D > ) : Lark . HttpRequestOptions < D > {
122192 return { timeout : defaultTimeoutMs , ...opts } as Lark . HttpRequestOptions < D > ;
123193 }
124194
195+ async function transform < R > ( promise : Promise < R > ) : Promise < R > {
196+ const response = await promise ;
197+ return transformResponse ? transformResponse ( response ) : response ;
198+ }
199+
125200 return {
126- request : ( opts ) => base . request ( injectTimeout ( opts ) ) ,
127- get : ( url , opts ) => base . get ( url , injectTimeout ( opts ) ) ,
128- post : ( url , data , opts ) => base . post ( url , data , injectTimeout ( opts ) ) ,
129- put : ( url , data , opts ) => base . put ( url , data , injectTimeout ( opts ) ) ,
130- patch : ( url , data , opts ) => base . patch ( url , data , injectTimeout ( opts ) ) ,
131- delete : ( url , opts ) => base . delete ( url , injectTimeout ( opts ) ) ,
132- head : ( url , opts ) => base . head ( url , injectTimeout ( opts ) ) ,
133- options : ( url , opts ) => base . options ( url , injectTimeout ( opts ) ) ,
201+ request : ( opts ) => transform ( base . request ( injectTimeout ( opts ) ) ) ,
202+ get : ( url , opts ) => transform ( base . get ( url , injectTimeout ( opts ) ) ) ,
203+ post : ( url , data , opts ) => transform ( base . post ( url , data , injectTimeout ( opts ) ) ) ,
204+ put : ( url , data , opts ) => transform ( base . put ( url , data , injectTimeout ( opts ) ) ) ,
205+ patch : ( url , data , opts ) => transform ( base . patch ( url , data , injectTimeout ( opts ) ) ) ,
206+ delete : ( url , opts ) => transform ( base . delete ( url , injectTimeout ( opts ) ) ) ,
207+ head : ( url , opts ) => transform ( base . head ( url , injectTimeout ( opts ) ) ) ,
208+ options : ( url , opts ) => transform ( base . options ( url , injectTimeout ( opts ) ) ) ,
134209 } ;
135210}
136211
@@ -224,8 +299,12 @@ export function createFeishuClient(creds: FeishuClientCredentials): Lark.Client
224299 * Create a Feishu WebSocket client for an account.
225300 * Note: WSClient is not cached since each call creates a new connection.
226301 */
227- export async function createFeishuWSClient ( account : ResolvedFeishuAccount ) : Promise < Lark . WSClient > {
302+ export async function createFeishuWSClient (
303+ account : ResolvedFeishuAccount ,
304+ lifecycleHooks : FeishuWsLifecycleHooks = { } ,
305+ ) : Promise < Lark . WSClient > {
228306 const { accountId, appId, appSecret, domain } = account ;
307+ const defaultHttpTimeoutMs = resolveConfiguredHttpTimeoutMs ( account ) ;
229308
230309 if ( ! appId || ! appSecret ) {
231310 throw new Error ( `Feishu credentials not configured for account "${ accountId } "` ) ;
@@ -237,10 +316,13 @@ export async function createFeishuWSClient(account: ResolvedFeishuAccount): Prom
237316 appSecret,
238317 domain : resolveDomain ( domain ) ,
239318 loggerLevel : feishuClientSdk . LoggerLevel . info ,
240- wsConfig : FEISHU_WS_CONFIG ,
319+ httpInstance : createTimeoutHttpInstance ( defaultHttpTimeoutMs , sanitizeFeishuWsEndpointResponse ) ,
320+ autoReconnect : true ,
321+ onReady : lifecycleHooks . onReady ,
322+ onError : lifecycleHooks . onError ,
323+ onReconnecting : lifecycleHooks . onReconnecting ,
324+ onReconnected : lifecycleHooks . onReconnected ,
241325 ...( agent ? { agent } : { } ) ,
242- } as ConstructorParameters < typeof feishuClientSdk . WSClient > [ 0 ] & {
243- wsConfig : typeof FEISHU_WS_CONFIG ;
244326 } ) ;
245327}
246328
0 commit comments