use std::sync::Arc;
use std::time::Duration;
use futures::{SinkExt, StreamExt};
use tokio::sync::Mutex;
use tokio_tungstenite::tungstenite::Message;
use nodedb_types::sync::wire::{SyncFrame, SyncMessageType};
use super::dispatch::receive_loop;
use super::push::{delta_push_loop, ping_loop};
use crate::error::LiteError;
use crate::sync::client::{SyncClient, SyncState};
use crate::sync::delegate::SyncDelegate;
pub(super) async fn connect_and_run(
client: &Arc<SyncClient>,
delegate: &Arc<dyn SyncDelegate>,
) -> Result<(), LiteError> {
let (persisted_producer_id, persisted_accepted_epoch) = delegate.load_producer_state().await;
client
.load_producer_state(persisted_producer_id, persisted_accepted_epoch)
.await;
client.reset_sequence_tracking().await;
client.reset_flow_control().await;
client.clear_fenced();
client.array_push().lock().await.reset();
delegate.clear_engine_in_flight().await;
let (ws_stream, _response) = tokio_tungstenite::connect_async(&client.config().url)
.await
.map_err(|e| LiteError::Sync {
detail: format!("WebSocket connect failed: {e}"),
})?;
let (mut sink, mut stream) = ws_stream.split();
let handshake = client.build_handshake(&delegate.sync_identity()).await;
let frame = SyncFrame::try_encode(SyncMessageType::Handshake, &handshake).ok_or_else(|| {
LiteError::Sync {
detail: "failed to encode handshake frame".to_string(),
}
})?;
sink.send(Message::Binary(frame.to_bytes().into()))
.await
.map_err(|e| LiteError::Sync {
detail: format!("handshake send failed: {e}"),
})?;
let ack_msg = tokio::time::timeout(Duration::from_secs(10), stream.next())
.await
.map_err(|_| LiteError::Sync {
detail: "handshake timeout".to_string(),
})?
.ok_or_else(|| LiteError::Sync {
detail: "connection closed before handshake ack".to_string(),
})?
.map_err(|e| LiteError::Sync {
detail: format!("handshake read error: {e}"),
})?;
let ack_bytes = match &ack_msg {
Message::Binary(b) => b.as_ref(),
Message::Text(_)
| Message::Ping(_)
| Message::Pong(_)
| Message::Close(_)
| Message::Frame(_) => {
return Err(LiteError::Sync {
detail: "expected binary handshake ack".to_string(),
});
}
};
let ack_frame = SyncFrame::from_bytes(ack_bytes).ok_or_else(|| LiteError::Sync {
detail: "invalid handshake ack frame".to_string(),
})?;
if ack_frame.msg_type != SyncMessageType::HandshakeAck {
return Err(LiteError::Sync {
detail: format!("expected HandshakeAck, got {:?}", ack_frame.msg_type),
});
}
let ack: nodedb_types::sync::wire::HandshakeAckMsg =
ack_frame.decode_body().ok_or_else(|| LiteError::Sync {
detail: "failed to decode HandshakeAck".to_string(),
})?;
if !client.handle_handshake_ack(&ack).await {
return Err(LiteError::Sync {
detail: format!("handshake rejected: {}", ack.error.unwrap_or_default()),
});
}
if ack.fork_detected {
tracing::warn!(
session = %ack.session_id,
"Origin reports this instance as forked — regenerating the producer identity"
);
delegate.regenerate_identity().await;
return Err(LiteError::Sync {
detail: "producer identity regenerated after fork; reconnecting".to_string(),
});
}
delegate
.persist_producer_state(ack.producer_id, ack.accepted_epoch)
.await;
client
.reset_array_catchups(delegate.begin_array_catchup())
.await;
let sink = Arc::new(Mutex::new(sink));
let push_sink = Arc::clone(&sink);
let push_client = Arc::clone(client);
let push_delegate = Arc::clone(delegate);
let push_handle = tokio::spawn(async move {
delta_push_loop(&push_client, &push_delegate, &push_sink).await;
});
let ping_sink = Arc::clone(&sink);
let ping_client = Arc::clone(client);
let ping_handle = tokio::spawn(async move {
ping_loop(&ping_client, &ping_sink).await;
});
let children = AbortChildrenOnDrop {
handles: vec![push_handle, ping_handle],
};
let recv_result = receive_loop(client, delegate, &mut stream).await;
drop(children);
client.set_state(SyncState::Disconnected).await;
recv_result
}
struct AbortChildrenOnDrop {
handles: Vec<tokio::task::JoinHandle<()>>,
}
impl Drop for AbortChildrenOnDrop {
fn drop(&mut self) {
for handle in &self.handles {
handle.abort();
}
}
}