Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions src/apps/desktop/src/api/remote_connect_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1924,12 +1924,15 @@ pub struct ConfigureBotRequest {
#[derive(Debug, Deserialize)]
pub struct WeixinQrStartRequest {
pub base_url: Option<String>,
pub existing_ilink_token: Option<String>,
pub existing_bot_account_id: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct WeixinQrPollRequest {
pub session_key: String,
pub base_url: Option<String>,
pub verify_code: Option<String>,
}

#[tauri::command]
Expand Down Expand Up @@ -1984,16 +1987,20 @@ pub async fn remote_connect_configure_bot(request: ConfigureBotRequest) -> Resul
pub async fn remote_connect_weixin_qr_start(
request: WeixinQrStartRequest,
) -> Result<weixin::WeixinQrStartResponse, String> {
weixin::weixin_qr_start(request.base_url)
.await
.map_err(|e| format!("weixin qr start: {e}"))
weixin::weixin_qr_start_with_existing(
request.base_url,
request.existing_ilink_token,
request.existing_bot_account_id,
)
.await
.map_err(|e| format!("weixin qr start: {e}"))
}

#[tauri::command]
pub async fn remote_connect_weixin_qr_poll(
request: WeixinQrPollRequest,
) -> Result<weixin::WeixinQrPollResponse, String> {
weixin::weixin_qr_poll(&request.session_key, request.base_url)
weixin::weixin_qr_poll(&request.session_key, request.base_url, request.verify_code)
.await
.map_err(|e| format!("weixin qr poll: {e}"))
}
Expand Down
62 changes: 49 additions & 13 deletions src/crates/assembly/core/src/service/remote_connect/bot/weixin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,28 @@ pub struct WeixinBot {
}

pub async fn weixin_qr_start(base_url_override: Option<String>) -> Result<WeixinQrStartResponse> {
weixin_provider::weixin_qr_start(base_url_override).await
weixin_provider::weixin_qr_start(base_url_override, None, None).await
}

pub async fn weixin_qr_start_with_existing(
base_url_override: Option<String>,
existing_ilink_token: Option<String>,
existing_bot_account_id: Option<String>,
) -> Result<WeixinQrStartResponse> {
weixin_provider::weixin_qr_start(
base_url_override,
existing_ilink_token,
existing_bot_account_id,
)
.await
}

pub async fn weixin_qr_poll(
session_key: &str,
base_url_override: Option<String>,
verify_code: Option<String>,
) -> Result<WeixinQrPollResponse> {
weixin_provider::weixin_qr_poll(session_key, base_url_override).await
weixin_provider::weixin_qr_poll(session_key, base_url_override, verify_code).await
}

impl WeixinBot {
Expand All @@ -61,11 +75,12 @@ impl WeixinBot {
}

pub(crate) fn new_fenced(config: WeixinConfig, runtime_fence: BotRuntimeFence) -> Self {
let context_tokens = weixin_provider::load_context_tokens(&config.bot_account_id);
Self {
api: Arc::new(WeixinProviderClient::new(config)),
pending_pairings: Arc::new(RwLock::new(HashMap::new())),
chat_states: Arc::new(RwLock::new(HashMap::new())),
context_tokens: Arc::new(RwLock::new(HashMap::new())),
context_tokens: Arc::new(RwLock::new(context_tokens)),
runtime_fence,
}
}
Expand Down Expand Up @@ -137,6 +152,10 @@ impl WeixinBot {
.unwrap_or(false)
{
tokens.remove(peer_id);
weixin_provider::save_context_tokens(
&self.api.config().bot_account_id,
&tokens,
);
warn!(
"weixin: dropped stale context_token for peer {peer_id} after send error: {err}"
);
Expand All @@ -147,6 +166,23 @@ impl WeixinBot {
Ok(())
}

async fn remember_context_token(&self, peer_id: &str, token: String) {
if !self.runtime_fence.is_lifecycle_current() {
return;
}
let mut tokens = self.context_tokens.write().await;
tokens.insert(peer_id.to_string(), token);
weixin_provider::save_context_tokens(&self.api.config().bot_account_id, &tokens);
}

pub async fn notify_start(&self) -> Result<()> {
self.api.notify_start().await
}

pub async fn notify_stop(&self) -> Result<()> {
self.api.notify_stop().await
}

async fn context_token_for_peer(&self, peer_id: &str) -> Result<String> {
self.context_tokens
.read()
Expand Down Expand Up @@ -306,6 +342,7 @@ impl WeixinBot {
) -> Result<String> {
info!("Weixin bot waiting for pairing code (getupdates)...");
let mut buf = weixin_provider::load_sync_buf(&self.api.config().bot_account_id);
let mut long_poll_timeout = Duration::from_secs(LONG_POLL_TIMEOUT_SECS);

loop {
if *stop_rx.borrow() {
Expand All @@ -318,7 +355,7 @@ impl WeixinBot {
}
result = self.api.get_updates_once(
&buf,
Duration::from_secs(LONG_POLL_TIMEOUT_SECS),
long_poll_timeout,
) => result,
};

Expand All @@ -330,6 +367,8 @@ impl WeixinBot {
continue;
}
};
long_poll_timeout =
weixin_provider::suggested_long_poll_timeout(&resp, long_poll_timeout);

let ret = resp["ret"].as_i64().unwrap_or(0);
let errcode = resp["errcode"].as_i64().unwrap_or(0);
Expand Down Expand Up @@ -361,10 +400,7 @@ impl WeixinBot {
continue;
};
if let Some(token) = weixin_provider::context_token(msg) {
self.context_tokens
.write()
.await
.insert(peer.clone(), token);
self.remember_context_token(&peer, token).await;
}
let text = weixin_provider::body_from_message(msg).trim().to_string();
let language = current_bot_language().await;
Expand Down Expand Up @@ -425,6 +461,7 @@ impl WeixinBot {
info!("Weixin message loop started");
let mut stop = stop_rx;
let mut buf = weixin_provider::load_sync_buf(&self.api.config().bot_account_id);
let mut long_poll_timeout = Duration::from_secs(LONG_POLL_TIMEOUT_SECS);

loop {
if *stop.borrow() {
Expand All @@ -435,7 +472,7 @@ impl WeixinBot {
_ = stop.changed() => break,
result = self.api.get_updates_once(
&buf,
Duration::from_secs(LONG_POLL_TIMEOUT_SECS),
long_poll_timeout,
) => result,
};

Expand All @@ -447,6 +484,8 @@ impl WeixinBot {
continue;
}
};
long_poll_timeout =
weixin_provider::suggested_long_poll_timeout(&resp, long_poll_timeout);

let ret = resp["ret"].as_i64().unwrap_or(0);
let errcode = resp["errcode"].as_i64().unwrap_or(0);
Expand Down Expand Up @@ -480,10 +519,7 @@ impl WeixinBot {
continue;
};
if let Some(token) = weixin_provider::context_token(msg) {
self.context_tokens
.write()
.await
.insert(peer.clone(), token);
self.remember_context_token(&peer, token).await;
}
let msg_value = msg.clone();
let bot = self.clone();
Expand Down
36 changes: 34 additions & 2 deletions src/crates/assembly/core/src/service/remote_connect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ pub use remote_server::RemoteServer;
use anyhow::Result;
use bitfun_services_integrations::remote_connect::upload_mobile_web_to_relay;
use embedded_relay_host::EmbeddedRelayHost;
use log::{debug, error, info};
use log::{debug, error, info, warn};
use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
Expand Down Expand Up @@ -1330,6 +1330,11 @@ impl RemoteConnectService {
if let Some(handle) = self.bot_weixin_handle.write().await.take() {
handle.stop();
}
if let Some(previous_bot) = self.weixin_bot.write().await.take() {
if let Err(err) = previous_bot.notify_stop().await {
warn!("Weixin notify-stop failed during replacement: {err}");
}
}

let wx_cfg = bot::weixin::WeixinConfig {
ilink_token: ilink_token.clone(),
Expand Down Expand Up @@ -1363,6 +1368,9 @@ impl RemoteConnectService {
*wx_bot_ref.write().await = Some(wx_bot.clone());

tokio::spawn(async move {
if let Err(err) = bot_for_pair.notify_start().await {
warn!("Weixin notify-start failed; continuing: {err}");
}
let mut stop_rx = stop_rx;
match bot_for_pair.wait_for_pairing(&mut stop_rx).await {
Ok(peer_id) => {
Expand All @@ -1381,6 +1389,11 @@ impl RemoteConnectService {
info!("Weixin pairing ended: {e}");
}
}
if bot_slot.is_current(generation) {
if let Err(err) = bot_for_pair.notify_stop().await {
warn!("Weixin notify-stop failed: {err}");
}
}
});

*self.bot_weixin_handle.write().await = Some(BotHandle { stop_tx });
Expand Down Expand Up @@ -1503,6 +1516,11 @@ impl RemoteConnectService {
if let Some(handle) = self.bot_weixin_handle.write().await.take() {
handle.stop();
}
if let Some(previous_bot) = self.weixin_bot.write().await.take() {
if let Err(err) = previous_bot.notify_stop().await {
warn!("Weixin notify-stop failed during restore replacement: {err}");
}
}

let wx_cfg = bot::weixin::WeixinConfig {
ilink_token: ilink_token.clone(),
Expand Down Expand Up @@ -1533,9 +1551,19 @@ impl RemoteConnectService {
*self.bot_connected_info.write().await = Some(format!("Weixin({cid})"));

let bot_for_loop = wx_bot.clone();
let bot_for_notify = wx_bot.clone();
let bot_slot = self.bot_weixin_slot.clone();
tokio::spawn(async move {
if let Err(err) = bot_for_notify.notify_start().await {
warn!("Weixin notify-start failed during restore; continuing: {err}");
}
info!("Weixin bot restored from persistence, starting message loop");
bot_for_loop.run_message_loop(stop_rx).await;
if bot_slot.is_current(generation) {
if let Err(err) = bot_for_notify.notify_stop().await {
warn!("Weixin notify-stop failed after restored loop: {err}");
}
}
});

*self.bot_weixin_handle.write().await = Some(BotHandle { stop_tx });
Expand Down Expand Up @@ -1598,7 +1626,11 @@ impl RemoteConnectService {
if let Some(handle) = self.bot_weixin_handle.write().await.take() {
handle.stop();
}
*self.weixin_bot.write().await = None;
if let Some(weixin_bot) = self.weixin_bot.write().await.take() {
if let Err(err) = weixin_bot.notify_stop().await {
warn!("Weixin notify-stop failed during bot shutdown: {err}");
}
}
*self.bot_connected_info.write().await = None;

info!("Bot connections stopped");
Expand Down
Loading
Loading