diff --git a/Cargo.lock b/Cargo.lock index 4cd52f0..d8fbc00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,6 +119,13 @@ dependencies = [ "zeroize", ] +[[package]] +name = "client" +version = "0.1.0" +dependencies = [ + "libchat", +] + [[package]] name = "const-oid" version = "0.9.6" diff --git a/Cargo.toml b/Cargo.toml index 274eb12..ca37bad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,15 +3,17 @@ resolver = "3" members = [ - "conversations", - "crypto", - "double-ratchets", - "storage", + "core/conversations", + "core/crypto", + "core/double-ratchets", + "core/storage", + "crates/client", ] [workspace.dependencies] blake2 = "0.10" -storage = { path = "storage" } +libchat = { path = "core/conversations" } +storage = { path = "core/storage" } # Panicking across FFI boundaries is UB; abort is the correct strategy for a # C FFI library. diff --git a/conversations/src/ffi/mod.rs b/conversations/src/ffi/mod.rs deleted file mode 100644 index b5614dd..0000000 --- a/conversations/src/ffi/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod utils; diff --git a/conversations/src/ffi/utils.rs b/conversations/src/ffi/utils.rs deleted file mode 100644 index 7989631..0000000 --- a/conversations/src/ffi/utils.rs +++ /dev/null @@ -1,8 +0,0 @@ -use safer_ffi::prelude::*; - -#[derive_ReprC] -#[repr(C)] -pub struct CResult { - pub ok: Option, - pub err: Option, -} diff --git a/conversations/src/storage/migrations/001_initial_schema.sql b/conversations/src/storage/migrations/001_initial_schema.sql deleted file mode 100644 index 5a97bfe..0000000 --- a/conversations/src/storage/migrations/001_initial_schema.sql +++ /dev/null @@ -1,9 +0,0 @@ --- Initial schema for chat storage --- Migration: 001_initial_schema - --- Identity table (single row) -CREATE TABLE IF NOT EXISTS identity ( - id INTEGER PRIMARY KEY CHECK (id = 1), - name TEXT NOT NULL, - secret_key BLOB NOT NULL -); diff --git a/conversations/src/storage/migrations/002_ephemeral_keys.sql b/conversations/src/storage/migrations/002_ephemeral_keys.sql deleted file mode 100644 index 14ecbb0..0000000 --- a/conversations/src/storage/migrations/002_ephemeral_keys.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Ephemeral keys for inbox handshakes --- Migration: 002_ephemeral_keys - -CREATE TABLE IF NOT EXISTS ephemeral_keys ( - public_key_hex TEXT PRIMARY KEY, - secret_key BLOB NOT NULL -); diff --git a/conversations/src/storage/migrations/003_conversations.sql b/conversations/src/storage/migrations/003_conversations.sql deleted file mode 100644 index 71da87f..0000000 --- a/conversations/src/storage/migrations/003_conversations.sql +++ /dev/null @@ -1,9 +0,0 @@ --- Conversations metadata --- Migration: 003_conversations - -CREATE TABLE IF NOT EXISTS conversations ( - local_convo_id TEXT PRIMARY KEY, - remote_convo_id TEXT NOT NULL, - convo_type TEXT NOT NULL, - created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) -); diff --git a/conversations/src/storage/mod.rs b/conversations/src/storage/mod.rs deleted file mode 100644 index 9364aeb..0000000 --- a/conversations/src/storage/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Storage module for persisting chat state. - -mod db; -mod migrations; -pub(crate) mod types; - -pub(crate) use db::ChatStorage; diff --git a/core/README.md b/core/README.md new file mode 100644 index 0000000..0115b00 --- /dev/null +++ b/core/README.md @@ -0,0 +1,7 @@ +# Core + +Crates in this directory will one day be separated into a separate shared repository. + +They could be moved now, but it's desirable to have a monorepo setup at this time. + +These crates MUST not depend on any code outside of this folder. \ No newline at end of file diff --git a/conversations/Cargo.toml b/core/conversations/Cargo.toml similarity index 100% rename from conversations/Cargo.toml rename to core/conversations/Cargo.toml diff --git a/conversations/src/api.rs b/core/conversations/src/api.rs similarity index 89% rename from conversations/src/api.rs rename to core/conversations/src/api.rs index 75319f9..bd1e300 100644 --- a/conversations/src/api.rs +++ b/core/conversations/src/api.rs @@ -13,12 +13,9 @@ use safer_ffi::{ prelude::{c_slice, repr_c}, }; -use storage::StorageConfig; - use crate::{ context::{Context, Introduction}, errors::ChatError, - ffi::utils::CResult, types::ContentData, }; @@ -57,41 +54,6 @@ pub fn create_context(name: repr_c::String) -> repr_c::Box { Box::new(ContextHandle(Context::new_with_name(&*name))).into() } -/// Creates a new libchat Context with file-based persistent storage. -/// -/// The identity will be loaded from storage if it exists, or created and saved if not. -/// -/// # Parameters -/// - name: Friendly name for the identity (used if creating new identity) -/// - db_path: Path to the SQLite database file -/// - db_secret: Secret key for encrypting the database -/// -/// # Returns -/// CResult with context handle on success, or error string on failure. -/// On success, the context handle must be freed with `destroy_context()` after usage. -/// On error, the error string must be freed with `destroy_string()` after usage. -#[ffi_export] -pub fn create_context_with_storage( - name: repr_c::String, - db_path: repr_c::String, - db_secret: repr_c::String, -) -> CResult, repr_c::String> { - let config = StorageConfig::Encrypted { - path: db_path.to_string(), - key: db_secret.to_string(), - }; - match Context::open(&*name, config) { - Ok(ctx) => CResult { - ok: Some(Box::new(ContextHandle(ctx)).into()), - err: None, - }, - Err(e) => CResult { - ok: None, - err: Some(e.to_string().into()), - }, - } -} - /// Returns the friendly name of the contexts installation. /// #[ffi_export] diff --git a/conversations/src/context.rs b/core/conversations/src/context.rs similarity index 98% rename from conversations/src/context.rs rename to core/conversations/src/context.rs index e695c28..23929e5 100644 --- a/conversations/src/context.rs +++ b/core/conversations/src/context.rs @@ -186,11 +186,9 @@ impl Context { /// Persists a conversation's metadata and ratchet state to DB. fn persist_convo(&mut self, convo: &dyn Convo) -> ConversationIdOwned { - let _ = self.storage.save_conversation( - convo.id(), - &convo.remote_id(), - convo.convo_type(), - ); + let _ = self + .storage + .save_conversation(convo.id(), &convo.remote_id(), convo.convo_type()); let _ = convo.save_ratchet_state(&mut self.ratchet_storage); Arc::from(convo.id()) } diff --git a/conversations/src/conversation.rs b/core/conversations/src/conversation.rs similarity index 100% rename from conversations/src/conversation.rs rename to core/conversations/src/conversation.rs diff --git a/core/conversations/src/conversation/group_test.rs b/core/conversations/src/conversation/group_test.rs new file mode 100644 index 0000000..e77984f --- /dev/null +++ b/core/conversations/src/conversation/group_test.rs @@ -0,0 +1,41 @@ +use crate::{ + conversation::{ChatError, ConversationId, Convo, Id}, + proto::EncryptedPayload, + types::{AddressedEncryptedPayload, ContentData}, +}; + +#[derive(Debug)] +pub struct GroupTestConvo {} + +impl GroupTestConvo { + pub fn new() -> Self { + Self {} + } +} + +impl Id for GroupTestConvo { + fn id(&self) -> ConversationId<'_> { + // implementation + "grouptest" + } +} + +impl Convo for GroupTestConvo { + fn send_message( + &mut self, + _content: &[u8], + ) -> Result, ChatError> { + Ok(vec![]) + } + + fn handle_frame( + &mut self, + _encoded_payload: EncryptedPayload, + ) -> Result, ChatError> { + Ok(None) + } + + fn remote_id(&self) -> String { + self.id().to_string() + } +} diff --git a/conversations/src/conversation/privatev1.rs b/core/conversations/src/conversation/privatev1.rs similarity index 100% rename from conversations/src/conversation/privatev1.rs rename to core/conversations/src/conversation/privatev1.rs diff --git a/conversations/src/crypto.rs b/core/conversations/src/crypto.rs similarity index 100% rename from conversations/src/crypto.rs rename to core/conversations/src/crypto.rs diff --git a/conversations/src/errors.rs b/core/conversations/src/errors.rs similarity index 100% rename from conversations/src/errors.rs rename to core/conversations/src/errors.rs diff --git a/conversations/src/identity.rs b/core/conversations/src/identity.rs similarity index 100% rename from conversations/src/identity.rs rename to core/conversations/src/identity.rs diff --git a/conversations/src/inbox.rs b/core/conversations/src/inbox.rs similarity index 100% rename from conversations/src/inbox.rs rename to core/conversations/src/inbox.rs diff --git a/conversations/src/inbox/handler.rs b/core/conversations/src/inbox/handler.rs similarity index 100% rename from conversations/src/inbox/handler.rs rename to core/conversations/src/inbox/handler.rs diff --git a/conversations/src/inbox/handshake.rs b/core/conversations/src/inbox/handshake.rs similarity index 100% rename from conversations/src/inbox/handshake.rs rename to core/conversations/src/inbox/handshake.rs diff --git a/conversations/src/inbox/introduction.rs b/core/conversations/src/inbox/introduction.rs similarity index 100% rename from conversations/src/inbox/introduction.rs rename to core/conversations/src/inbox/introduction.rs diff --git a/conversations/src/lib.rs b/core/conversations/src/lib.rs similarity index 96% rename from conversations/src/lib.rs rename to core/conversations/src/lib.rs index 5490629..de0c023 100644 --- a/conversations/src/lib.rs +++ b/core/conversations/src/lib.rs @@ -3,7 +3,6 @@ mod context; mod conversation; mod crypto; mod errors; -mod ffi; mod identity; mod inbox; mod proto; @@ -12,6 +11,8 @@ mod types; mod utils; pub use api::*; +pub use context::{Context, Introduction}; +pub use errors::ChatError; #[cfg(test)] mod tests { diff --git a/conversations/src/proto.rs b/core/conversations/src/proto.rs similarity index 100% rename from conversations/src/proto.rs rename to core/conversations/src/proto.rs diff --git a/conversations/src/storage/db.rs b/core/conversations/src/storage.rs similarity index 96% rename from conversations/src/storage/db.rs rename to core/conversations/src/storage.rs index 6530224..eca4c6c 100644 --- a/conversations/src/storage/db.rs +++ b/core/conversations/src/storage.rs @@ -1,12 +1,16 @@ //! Chat-specific storage implementation. +mod migrations; +mod types; + +use crypto::PrivateKey; use storage::{RusqliteError, SqliteDb, StorageConfig, StorageError, params}; use zeroize::Zeroize; -use super::migrations; -use super::types::{ConversationRecord, IdentityRecord}; -use crate::crypto::PrivateKey; -use crate::identity::Identity; +use crate::{ + identity::Identity, + storage::types::{ConversationRecord, IdentityRecord}, +}; /// Chat-specific storage operations. /// @@ -48,6 +52,46 @@ impl ChatStorage { Ok(()) } + /// Loads the identity if it exists. + /// + /// Note: Secret key bytes are zeroized after being copied into IdentityRecord, + /// which handles its own zeroization via ZeroizeOnDrop. + pub fn load_identity(&self) -> Result, StorageError> { + let mut stmt = self + .db + .connection() + .prepare("SELECT name, secret_key FROM identity WHERE id = 1")?; + + let result = stmt.query_row([], |row| { + let name: String = row.get(0)?; + let secret_key: Vec = row.get(1)?; + Ok((name, secret_key)) + }); + + match result { + Ok((name, mut secret_key_vec)) => { + let bytes: Result<[u8; 32], _> = secret_key_vec.as_slice().try_into(); + let bytes = match bytes { + Ok(b) => b, + Err(_) => { + secret_key_vec.zeroize(); + return Err(StorageError::InvalidData( + "Invalid secret key length".into(), + )); + } + }; + secret_key_vec.zeroize(); + let record = IdentityRecord { + name, + secret_key: bytes, + }; + Ok(Some(Identity::from(record))) + } + Err(RusqliteError::QueryReturnedNoRows) => Ok(None), + Err(e) => Err(e.into()), + } + } + // ==================== Ephemeral Key Operations ==================== /// Saves an ephemeral key pair to storage. @@ -162,9 +206,10 @@ impl ChatStorage { /// Loads all conversation records. pub fn load_conversations(&self) -> Result, StorageError> { - let mut stmt = self.db.connection().prepare( - "SELECT local_convo_id, remote_convo_id, convo_type FROM conversations", - )?; + let mut stmt = self + .db + .connection() + .prepare("SELECT local_convo_id, remote_convo_id, convo_type FROM conversations")?; let records = stmt .query_map([], |row| { @@ -187,48 +232,6 @@ impl ChatStorage { )?; Ok(()) } - - // ==================== Identity Operations (continued) ==================== - - /// Loads the identity if it exists. - /// - /// Note: Secret key bytes are zeroized after being copied into IdentityRecord, - /// which handles its own zeroization via ZeroizeOnDrop. - pub fn load_identity(&self) -> Result, StorageError> { - let mut stmt = self - .db - .connection() - .prepare("SELECT name, secret_key FROM identity WHERE id = 1")?; - - let result = stmt.query_row([], |row| { - let name: String = row.get(0)?; - let secret_key: Vec = row.get(1)?; - Ok((name, secret_key)) - }); - - match result { - Ok((name, mut secret_key_vec)) => { - let bytes: Result<[u8; 32], _> = secret_key_vec.as_slice().try_into(); - let bytes = match bytes { - Ok(b) => b, - Err(_) => { - secret_key_vec.zeroize(); - return Err(StorageError::InvalidData( - "Invalid secret key length".into(), - )); - } - }; - secret_key_vec.zeroize(); - let record = IdentityRecord { - name, - secret_key: bytes, - }; - Ok(Some(Identity::from(record))) - } - Err(RusqliteError::QueryReturnedNoRows) => Ok(None), - Err(e) => Err(e.into()), - } - } } #[cfg(test)] diff --git a/conversations/src/storage/migrations.rs b/core/conversations/src/storage/migrations.rs similarity index 79% rename from conversations/src/storage/migrations.rs rename to core/conversations/src/storage/migrations.rs index 848122d..014bb96 100644 --- a/conversations/src/storage/migrations.rs +++ b/core/conversations/src/storage/migrations.rs @@ -7,20 +7,10 @@ use storage::{Connection, StorageError}; /// Embeds and returns all migration SQL files in order. pub fn get_migrations() -> Vec<(&'static str, &'static str)> { - vec![ - ( - "001_initial_schema", - include_str!("migrations/001_initial_schema.sql"), - ), - ( - "002_ephemeral_keys", - include_str!("migrations/002_ephemeral_keys.sql"), - ), - ( - "003_conversations", - include_str!("migrations/003_conversations.sql"), - ), - ] + vec![( + "001_initial_schema", + include_str!("migrations/001_initial_schema.sql"), + )] } /// Applies all migrations to the database. diff --git a/core/conversations/src/storage/migrations/001_initial_schema.sql b/core/conversations/src/storage/migrations/001_initial_schema.sql new file mode 100644 index 0000000..69ec08b --- /dev/null +++ b/core/conversations/src/storage/migrations/001_initial_schema.sql @@ -0,0 +1,23 @@ +-- Initial schema for chat storage +-- Migration: 001_initial_schema + +-- Identity table (single row) +CREATE TABLE IF NOT EXISTS identity ( + id INTEGER PRIMARY KEY CHECK (id = 1), + name TEXT NOT NULL, + secret_key BLOB NOT NULL +); + +-- Ephemeral keys for inbox handshakes +CREATE TABLE IF NOT EXISTS ephemeral_keys ( + public_key_hex TEXT PRIMARY KEY, + secret_key BLOB NOT NULL +); + +-- Conversations metadata +CREATE TABLE IF NOT EXISTS conversations ( + local_convo_id TEXT PRIMARY KEY, + remote_convo_id TEXT NOT NULL, + convo_type TEXT NOT NULL, + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) +); diff --git a/conversations/src/storage/types.rs b/core/conversations/src/storage/types.rs similarity index 97% rename from conversations/src/storage/types.rs rename to core/conversations/src/storage/types.rs index 58c21ce..d51ac8f 100644 --- a/conversations/src/storage/types.rs +++ b/core/conversations/src/storage/types.rs @@ -22,7 +22,6 @@ impl From for Identity { } } -/// Record for storing conversation metadata. #[derive(Debug)] pub struct ConversationRecord { pub local_convo_id: String, diff --git a/conversations/src/types.rs b/core/conversations/src/types.rs similarity index 100% rename from conversations/src/types.rs rename to core/conversations/src/types.rs diff --git a/conversations/src/utils.rs b/core/conversations/src/utils.rs similarity index 100% rename from conversations/src/utils.rs rename to core/conversations/src/utils.rs diff --git a/crypto/Cargo.toml b/core/crypto/Cargo.toml similarity index 100% rename from crypto/Cargo.toml rename to core/crypto/Cargo.toml diff --git a/crypto/src/keys.rs b/core/crypto/src/keys.rs similarity index 100% rename from crypto/src/keys.rs rename to core/crypto/src/keys.rs diff --git a/crypto/src/lib.rs b/core/crypto/src/lib.rs similarity index 100% rename from crypto/src/lib.rs rename to core/crypto/src/lib.rs diff --git a/crypto/src/x3dh.rs b/core/crypto/src/x3dh.rs similarity index 100% rename from crypto/src/x3dh.rs rename to core/crypto/src/x3dh.rs diff --git a/crypto/src/xeddsa_sign.rs b/core/crypto/src/xeddsa_sign.rs similarity index 100% rename from crypto/src/xeddsa_sign.rs rename to core/crypto/src/xeddsa_sign.rs diff --git a/double-ratchets/Cargo.toml b/core/double-ratchets/Cargo.toml similarity index 100% rename from double-ratchets/Cargo.toml rename to core/double-ratchets/Cargo.toml diff --git a/double-ratchets/README.md b/core/double-ratchets/README.md similarity index 100% rename from double-ratchets/README.md rename to core/double-ratchets/README.md diff --git a/double-ratchets/examples/double_ratchet_basic.rs b/core/double-ratchets/examples/double_ratchet_basic.rs similarity index 100% rename from double-ratchets/examples/double_ratchet_basic.rs rename to core/double-ratchets/examples/double_ratchet_basic.rs diff --git a/double-ratchets/examples/out_of_order_demo.rs b/core/double-ratchets/examples/out_of_order_demo.rs similarity index 100% rename from double-ratchets/examples/out_of_order_demo.rs rename to core/double-ratchets/examples/out_of_order_demo.rs diff --git a/double-ratchets/examples/serialization_demo.rs b/core/double-ratchets/examples/serialization_demo.rs similarity index 100% rename from double-ratchets/examples/serialization_demo.rs rename to core/double-ratchets/examples/serialization_demo.rs diff --git a/double-ratchets/examples/storage_demo.rs b/core/double-ratchets/examples/storage_demo.rs similarity index 100% rename from double-ratchets/examples/storage_demo.rs rename to core/double-ratchets/examples/storage_demo.rs diff --git a/double-ratchets/ffi-nim-example/ffi_nim_example.nimble b/core/double-ratchets/ffi-nim-example/ffi_nim_example.nimble similarity index 100% rename from double-ratchets/ffi-nim-example/ffi_nim_example.nimble rename to core/double-ratchets/ffi-nim-example/ffi_nim_example.nimble diff --git a/double-ratchets/ffi-nim-example/src/ffi_nim_example.nim b/core/double-ratchets/ffi-nim-example/src/ffi_nim_example.nim similarity index 100% rename from double-ratchets/ffi-nim-example/src/ffi_nim_example.nim rename to core/double-ratchets/ffi-nim-example/src/ffi_nim_example.nim diff --git a/double-ratchets/src/aead.rs b/core/double-ratchets/src/aead.rs similarity index 100% rename from double-ratchets/src/aead.rs rename to core/double-ratchets/src/aead.rs diff --git a/double-ratchets/src/bin/generate-headers.rs b/core/double-ratchets/src/bin/generate-headers.rs similarity index 100% rename from double-ratchets/src/bin/generate-headers.rs rename to core/double-ratchets/src/bin/generate-headers.rs diff --git a/double-ratchets/src/errors.rs b/core/double-ratchets/src/errors.rs similarity index 100% rename from double-ratchets/src/errors.rs rename to core/double-ratchets/src/errors.rs diff --git a/double-ratchets/src/ffi/doubleratchet.rs b/core/double-ratchets/src/ffi/doubleratchet.rs similarity index 100% rename from double-ratchets/src/ffi/doubleratchet.rs rename to core/double-ratchets/src/ffi/doubleratchet.rs diff --git a/double-ratchets/src/ffi/key.rs b/core/double-ratchets/src/ffi/key.rs similarity index 100% rename from double-ratchets/src/ffi/key.rs rename to core/double-ratchets/src/ffi/key.rs diff --git a/double-ratchets/src/ffi/mod.rs b/core/double-ratchets/src/ffi/mod.rs similarity index 100% rename from double-ratchets/src/ffi/mod.rs rename to core/double-ratchets/src/ffi/mod.rs diff --git a/double-ratchets/src/ffi/utils.rs b/core/double-ratchets/src/ffi/utils.rs similarity index 100% rename from double-ratchets/src/ffi/utils.rs rename to core/double-ratchets/src/ffi/utils.rs diff --git a/double-ratchets/src/hkdf.rs b/core/double-ratchets/src/hkdf.rs similarity index 100% rename from double-ratchets/src/hkdf.rs rename to core/double-ratchets/src/hkdf.rs diff --git a/double-ratchets/src/keypair.rs b/core/double-ratchets/src/keypair.rs similarity index 100% rename from double-ratchets/src/keypair.rs rename to core/double-ratchets/src/keypair.rs diff --git a/double-ratchets/src/lib.rs b/core/double-ratchets/src/lib.rs similarity index 100% rename from double-ratchets/src/lib.rs rename to core/double-ratchets/src/lib.rs diff --git a/double-ratchets/src/reader.rs b/core/double-ratchets/src/reader.rs similarity index 100% rename from double-ratchets/src/reader.rs rename to core/double-ratchets/src/reader.rs diff --git a/double-ratchets/src/state.rs b/core/double-ratchets/src/state.rs similarity index 100% rename from double-ratchets/src/state.rs rename to core/double-ratchets/src/state.rs diff --git a/double-ratchets/src/storage/db.rs b/core/double-ratchets/src/storage/db.rs similarity index 100% rename from double-ratchets/src/storage/db.rs rename to core/double-ratchets/src/storage/db.rs diff --git a/double-ratchets/src/storage/errors.rs b/core/double-ratchets/src/storage/errors.rs similarity index 100% rename from double-ratchets/src/storage/errors.rs rename to core/double-ratchets/src/storage/errors.rs diff --git a/double-ratchets/src/storage/mod.rs b/core/double-ratchets/src/storage/mod.rs similarity index 100% rename from double-ratchets/src/storage/mod.rs rename to core/double-ratchets/src/storage/mod.rs diff --git a/double-ratchets/src/storage/session.rs b/core/double-ratchets/src/storage/session.rs similarity index 100% rename from double-ratchets/src/storage/session.rs rename to core/double-ratchets/src/storage/session.rs diff --git a/double-ratchets/src/storage/types.rs b/core/double-ratchets/src/storage/types.rs similarity index 100% rename from double-ratchets/src/storage/types.rs rename to core/double-ratchets/src/storage/types.rs diff --git a/double-ratchets/src/types.rs b/core/double-ratchets/src/types.rs similarity index 100% rename from double-ratchets/src/types.rs rename to core/double-ratchets/src/types.rs diff --git a/storage/Cargo.toml b/core/storage/Cargo.toml similarity index 100% rename from storage/Cargo.toml rename to core/storage/Cargo.toml diff --git a/storage/src/errors.rs b/core/storage/src/errors.rs similarity index 100% rename from storage/src/errors.rs rename to core/storage/src/errors.rs diff --git a/storage/src/lib.rs b/core/storage/src/lib.rs similarity index 100% rename from storage/src/lib.rs rename to core/storage/src/lib.rs diff --git a/storage/src/sqlite.rs b/core/storage/src/sqlite.rs similarity index 100% rename from storage/src/sqlite.rs rename to core/storage/src/sqlite.rs diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml new file mode 100644 index 0000000..d3cfb2a --- /dev/null +++ b/crates/client/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "client" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["rlib"] + +[dependencies] +libchat = { workspace = true } diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs new file mode 100644 index 0000000..a26908a --- /dev/null +++ b/crates/client/src/client.rs @@ -0,0 +1,18 @@ +use libchat::ChatError; +use libchat::Context; + +pub struct ChatClient { + ctx: Context, +} + +impl ChatClient { + pub fn new(name: impl Into) -> Self { + Self { + ctx: Context::new_with_name(name), + } + } + + pub fn create_bundle(&mut self) -> Result, ChatError> { + self.ctx.create_intro_bundle() + } +} diff --git a/crates/client/src/lib.rs b/crates/client/src/lib.rs new file mode 100644 index 0000000..008d68a --- /dev/null +++ b/crates/client/src/lib.rs @@ -0,0 +1,3 @@ +mod client; + +pub use client::ChatClient;