use crate::acl;
use crate::channel::Channel;
use crate::state::{ChannelId, ConnState, CorrelationContext, ImState, SendStatus, TemporaryId};
use helix_core::effect::TimerId;
use helix_core::{CoreError, EffectSink, Module, Tick};
mod chain;
mod trait_impl;
pub const RUNTIME_IDENTITY_COMMAND: &str = "__helix_runtime_identity";
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum ClientPlatform {
#[default]
Native,
Ffi,
Web,
}
impl ClientPlatform {
pub const fn as_str(self) -> &'static str {
match self {
Self::Native => "native",
Self::Ffi => "ffi",
Self::Web => "web",
}
}
}
pub struct ImConfig {
pub ws_url: String,
pub api_base_url: String,
pub default_api_base_url: String,
pub auth_user_id: String,
pub company_id: String,
pub user_name: String,
pub org_name: String,
pub dept_name: String,
pub client_platform: ClientPlatform,
pub ping_interval_ms: u64,
pub send_timeout_ms: u64,
pub offline_sync_diagnostics: crate::sync::SyncDiagnosticsConfig,
}
impl ImConfig {
pub fn user_identity(&self) -> crate::outbound::send_build::UserIdentity<'_> {
crate::outbound::send_build::UserIdentity {
user_id: &self.auth_user_id,
team_id: &self.company_id,
user_name: &self.user_name,
org_name: &self.org_name,
dept_name: &self.dept_name,
}
}
}
impl Default for ImConfig {
fn default() -> Self {
Self {
ws_url: String::new(),
api_base_url: String::new(),
default_api_base_url: String::new(),
auth_user_id: String::new(),
company_id: String::new(),
user_name: String::new(),
org_name: String::new(),
dept_name: String::new(),
client_platform: ClientPlatform::Native,
ping_interval_ms: 8_000,
send_timeout_ms: 15_000,
offline_sync_diagnostics: crate::sync::SyncDiagnosticsConfig::disabled(),
}
}
}
pub struct ImModule {
pub(crate) config: ImConfig,
pub(crate) state: ImState,
pub(crate) local_store_mode: crate::query::LocalStoreMode,
next_corr: u64,
next_timer: u64,
next_temporary_id: u64,
}
impl ImModule {
#[doc(hidden)]
pub fn queue_message_v3_commit(
&mut self,
ops: Vec<helix_core::effect::StorageOp>,
terminal_events: Vec<crate::event::MessageV3Event>,
out: &mut EffectSink,
) -> helix_core::Correlation {
let corr = self.alloc_corr_internal();
let terminal_events = terminal_events
.into_iter()
.map(crate::event::MessageV3Event::into_bytes)
.collect();
self.state.corr_map.insert(
corr,
CorrelationContext::MessageV3Commit { terminal_events },
);
out.push(helix_core::Effect::PersistAtomic { corr, ops });
corr
}
pub fn new(config: ImConfig) -> Self {
Self::new_with_local_store(config, crate::query::LocalStoreMode::Durable)
}
pub fn new_with_local_store(
config: ImConfig,
local_store_mode: crate::query::LocalStoreMode,
) -> Self {
Self {
config,
state: ImState::new(),
local_store_mode,
next_corr: 1,
next_timer: 100,
next_temporary_id: 1,
}
}
pub fn local_store_mode(&self) -> crate::query::LocalStoreMode {
self.local_store_mode
}
pub fn alloc_corr(&mut self) -> helix_core::Correlation {
let c = helix_core::Correlation::from_raw(self.next_corr);
self.next_corr += 1;
c
}
pub fn alloc_timer(&mut self) -> TimerId {
let t = TimerId::from_raw(self.next_timer);
self.next_timer += 1;
t
}
pub(crate) fn alloc_temporary_id(
&mut self,
now_ms: u64,
) -> Result<TemporaryId, crate::error::ImError> {
let sequence = self.next_temporary_id;
self.next_temporary_id = sequence.checked_add(1).ok_or_else(|| {
crate::error::ImError::Parse("temporary_id sequence exhausted".to_string())
})?;
Ok(TemporaryId::mint(now_ms, sequence))
}
pub fn register_channel(&mut self, id: ChannelId, initial_cursor: u64) {
self.state
.channels
.insert(id, Channel::new(id, initial_cursor));
}
pub fn cursor_for(&self, channel_id: ChannelId) -> Option<u64> {
self.state
.channels
.get(&channel_id)
.map(|ch| ch.cursor.value().0)
}
pub fn terminal_event_seq_for(&self, channel_id: ChannelId) -> Option<u64> {
self.state
.channels
.get(&channel_id)
.and_then(|channel| channel.terminal_event_seq())
.map(|seq| seq.0)
}
pub fn pending_send_count(&self) -> usize {
self.state.pending_sends.len()
}
pub fn chain_mutation_state(
&self,
client_mutation_id: &str,
) -> Option<crate::chain::ChainMutationState> {
self.state
.chain_mutations
.get(client_mutation_id)
.map(|mutation| mutation.state)
}
pub fn chain_operation_id(&self, client_mutation_id: &str) -> Option<&str> {
self.state
.chain_mutations
.get(client_mutation_id)
.map(|mutation| mutation.operation_id.as_str())
}
pub fn sync_inflight(&self) -> usize {
self.state.sync_scheduler.inflight()
}
pub fn sync_pending_len(&self) -> usize {
self.state.sync_scheduler.pending_len()
}
pub fn pending_send_status(&self, temporary_id: &str) -> Option<SendStatus> {
self.state
.pending_sends
.get(&TemporaryId(temporary_id.to_string()))
.map(|ps| ps.status)
}
pub fn increment_fetched_contains(&self, channel_id: ChannelId) -> bool {
self.state.increment_fetched.contains(&channel_id)
}
pub fn increment_target_for(&self, channel_id: ChannelId) -> Option<u64> {
self.state
.increment_target
.get(&channel_id)
.map(|seq| seq.0)
}
pub fn ingest_increment_end(&mut self, ch: Option<ChannelId>, out: &mut EffectSink) {
let api_base_url = self.config.api_base_url.as_str();
let auth_user_id = self.config.auth_user_id.as_str();
let next_corr = &mut self.next_corr;
let mut alloc_corr = || {
let corr = helix_core::Correlation::from_raw(*next_corr);
*next_corr += 1;
corr
};
let mut ctx = crate::ws::ImWsContext::new(
&mut self.state,
0,
api_base_url,
auth_user_id,
&mut alloc_corr,
);
crate::ws::handlers::increment_channel_end::apply_increment_end(&mut ctx, ch, out);
}
pub(crate) fn alloc_corr_internal(&mut self) -> helix_core::Correlation {
let c = helix_core::Correlation::from_raw(self.next_corr);
self.next_corr += 1;
c
}
pub(crate) fn with_state_and_corr_allocator<R>(
&mut self,
f: impl FnOnce(&mut ImState, &mut dyn FnMut() -> helix_core::Correlation) -> R,
) -> R {
let next_corr = &mut self.next_corr;
let mut alloc_corr = || {
let corr = helix_core::Correlation::from_raw(*next_corr);
*next_corr += 1;
corr
};
f(&mut self.state, &mut alloc_corr)
}
fn dispatch_ws_frame(
&mut self,
frame: &crate::ws::WsFrame,
now_ms: u64,
out: &mut EffectSink,
) -> Result<(), CoreError> {
let module_name = self.name();
let api_base_url = self.config.api_base_url.as_str();
let auth_user_id = self.config.auth_user_id.as_str();
let next_corr = &mut self.next_corr;
let mut alloc_corr = || {
let corr = helix_core::Correlation::from_raw(*next_corr);
*next_corr += 1;
corr
};
let (result, timeline_refreshes) = {
let mut ctx = crate::ws::ImWsContext::new(
&mut self.state,
now_ms,
api_base_url,
auth_user_id,
&mut alloc_corr,
);
let result = crate::ws::dispatch_ws(&mut ctx, frame, out);
let timeline_refreshes = ctx.take_attached_timeline_refreshes();
(result, timeline_refreshes)
};
result.map_err(|e| CoreError::ModuleError {
module: module_name,
source: Box::new(e),
})?;
for (channel_id, causation_id) in timeline_refreshes {
self.refresh_attached_latest_timeline(channel_id, causation_id, out)
.map_err(|e| CoreError::ModuleError {
module: module_name,
source: Box::new(e),
})?;
}
Ok(())
}
}