Skip to content

Commit 421f3ce

Browse files
chaodu-obk[bot]SunnyYYLinchaodu-agent
authored
fix(feishu): start websocket in unified mode (#1443)
* fix(feishu): start websocket in unified mode (cherry picked from commit a248a33) * fix(review): address Feishu lifecycle and docs findings * fix(feishu): bound unified identity resolution * refactor(feishu): name identity timeout * fix(feishu): cfg-gate timeout constant --------- Co-authored-by: SunnyYYLin <sunnylinyy@outlook.com> Co-authored-by: chaodu-agent <274062505+chaodu-agent@users.noreply.github.com>
1 parent 136554e commit 421f3ce

2 files changed

Lines changed: 66 additions & 7 deletions

File tree

docs/feishu.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Feishu / Lark
22

33

4-
> **Unified Mode (v0.9.0+):** The OAB binary now embeds the feishu adapter directly. Set `FEISHU_APP_ID` as an env var — no separate gateway container or `[gateway]` config needed. See [Telegram docs](telegram.md#unified-mode-recommended) for the pattern.
4+
> **Unified Mode (v0.9.0+, WebSocket support v0.10.0+):** The OAB binary now embeds the feishu adapter directly. Set `FEISHU_APP_ID` as an env var — no separate gateway container or `[gateway]` config needed. See [Telegram docs](telegram.md#unified-mode-recommended) for the pattern.
55
66
### Unified Config (Kiro + feishu)
77

@@ -313,6 +313,7 @@ Bot identification requires explicit configuration via `FEISHU_TRUSTED_BOT_IDS`
313313
| Problem | Fix |
314314
|---|---|
315315
| Bot doesn't respond | Check `FEISHU_APP_ID`/`FEISHU_APP_SECRET` are correct. Check gateway logs for token errors. |
316+
| Unified mode receives no events | Use a release with unified WebSocket support (v0.10.0+) or use the standalone gateway as a workaround. See #1356 for the transport-mode follow-up. |
316317
| Bot doesn't respond in groups | Ensure bot is @mentioned, or set `requireMention: false`. Check `botUsername` matches bot's `open_id`. |
317318
| WebSocket keeps reconnecting | Check event subscription is set to **Long Connection** mode. Check app is published and approved. |
318319
| Webhook verification fails | Ensure `verificationToken` and `encryptKey` match Feishu app config. |

src/main.rs

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ use std::path::PathBuf;
3636
use std::sync::{Arc, Mutex};
3737
use tracing::{error, info, warn};
3838

39+
#[cfg(feature = "feishu")]
40+
const FEISHU_IDENTITY_RESOLUTION_TIMEOUT_SECS: u64 = 15;
41+
3942
/// Wait for SIGINT (ctrl_c) or, on unix, SIGTERM.
4043
async fn shutdown_signal() {
4144
#[cfg(unix)]
@@ -899,6 +902,11 @@ async fn main() -> anyhow::Result<()> {
899902
// Spawn embedded webhook server when gateway adapters are compiled in (unified mode).
900903
// In unified mode, platform webhooks hit this axum server directly → Dispatcher.submit(),
901904
// bypassing the WebSocket hop of the two-process model.
905+
#[cfg(feature = "feishu")]
906+
let mut unified_feishu_shutdown: Option<tokio::sync::watch::Sender<bool>> = None;
907+
#[cfg(feature = "feishu")]
908+
let mut unified_feishu_handle: Option<tokio::task::JoinHandle<()>> = None;
909+
902910
#[cfg(any(
903911
feature = "telegram",
904912
feature = "line",
@@ -1063,12 +1071,12 @@ async fn main() -> anyhow::Result<()> {
10631071
if gw_state.feishu.is_some() {
10641072
// NOTE (#1356 L1 audit): unlike the standalone gateway (which
10651073
// mounts this route only in Webhook connection mode), the
1066-
// unified binary mounts it unconditionally — and never spawns
1067-
// the Websocket client. Deployments relying on Feishu-side
1068-
// webhook delivery while FEISHU_CONNECTION_MODE is unset
1069-
// (default: websocket) work only because of this mount, so
1070-
// gating it is a behavior change that needs its own
1071-
// deprecation path — tracked on #1356, not changed here.
1074+
// unified binary mounts it unconditionally. In WebSocket mode,
1075+
// the client is also started below. Deployments relying on
1076+
// Feishu-side webhook delivery while FEISHU_CONNECTION_MODE is
1077+
// unset (default: websocket) work because this route remains
1078+
// mounted, so gating it is a behavior change that needs its
1079+
// own deprecation path — tracked on #1356, not changed here.
10721080
let path = gw_state
10731081
.feishu
10741082
.as_ref()
@@ -1081,6 +1089,47 @@ async fn main() -> anyhow::Result<()> {
10811089
);
10821090
}
10831091

1092+
// Feishu bot identity + WebSocket long-connection (unified mode).
1093+
// This mirrors the standalone gateway: the default websocket mode
1094+
// otherwise mounts only the webhook route and receives no events.
1095+
#[cfg(feature = "feishu")]
1096+
if let Some(ref f) = gw_state.feishu {
1097+
use openab_gateway::adapters::feishu;
1098+
match tokio::time::timeout(
1099+
std::time::Duration::from_secs(FEISHU_IDENTITY_RESOLUTION_TIMEOUT_SECS),
1100+
f.resolve_bot_identity(),
1101+
)
1102+
.await
1103+
{
1104+
Ok(()) => {}
1105+
Err(_) => warn!(
1106+
"unified: feishu bot identity resolution timed out; continuing without bot identity"
1107+
),
1108+
}
1109+
if f.config.streaming_mode != feishu::StreamingMode::Post {
1110+
let idle_ms = f.config.card_idle_finalize_ms;
1111+
tokio::spawn(feishu::run_idle_reaper(
1112+
f.stream_sessions.clone(),
1113+
f.token_cache.clone(),
1114+
f.client.clone(),
1115+
f.config.api_base(),
1116+
idle_ms,
1117+
));
1118+
info!(idle_ms, "unified: feishu card-streaming idle reaper started");
1119+
}
1120+
if f.config.connection_mode == feishu::ConnectionMode::Websocket {
1121+
let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
1122+
match feishu::start_websocket(f, event_tx.clone(), shutdown_rx).await {
1123+
Ok(handle) => {
1124+
info!("unified: feishu websocket task spawned");
1125+
unified_feishu_shutdown = Some(shutdown_tx);
1126+
unified_feishu_handle = Some(handle);
1127+
}
1128+
Err(e) => error!(err = %e, "unified: feishu websocket startup failed"),
1129+
}
1130+
}
1131+
}
1132+
10841133
#[cfg(feature = "wecom")]
10851134
if let Some(ref w) = gw_state.wecom {
10861135
info!(path = %w.config.webhook_path, "unified: wecom adapter enabled");
@@ -1405,6 +1454,15 @@ async fn main() -> anyhow::Result<()> {
14051454
let _ = std::fs::remove_file(ctl::socket_path());
14061455
}
14071456
let _ = shutdown_tx.send(true);
1457+
#[cfg(feature = "feishu")]
1458+
{
1459+
if let Some(feishu_shutdown_tx) = unified_feishu_shutdown.take() {
1460+
let _ = feishu_shutdown_tx.send(true);
1461+
}
1462+
if let Some(feishu_handle) = unified_feishu_handle.take() {
1463+
let _ = tokio::time::timeout(std::time::Duration::from_secs(5), feishu_handle).await;
1464+
}
1465+
}
14081466
if let Some(handle) = slack_handle {
14091467
let _ = tokio::time::timeout(std::time::Duration::from_secs(5), handle).await;
14101468
}

0 commit comments

Comments
 (0)