jazz-rs 0.4.0

A framework for CRDT based, end-to-end enrypted distributed apps
Documentation
use std::{cell::RefCell, pin::Pin, rc::Weak};

use audi::Listener;
use credo::{Credential, CredentialSource, ScopeID};
use futures::{Future, FutureExt, StreamExt};
use litl::Litl;

use crate::{conventions::CREDENTIALS_DOC_NAME, Jazz, JazzInner};

pub struct AccountCredentialSource {
    account_id: ScopeID,
    account_credential: Credential,
    jazz: Weak<RefCell<JazzInner>>,
}

impl AccountCredentialSource {
    pub(crate) fn new(
        account_id: ScopeID,
        account_credential: Credential,
        jazz_weak_ref: Weak<RefCell<JazzInner>>,
    ) -> Self {
        Self {
            account_id,
            account_credential,
            jazz: jazz_weak_ref,
        }
    }
}

impl CredentialSource for AccountCredentialSource {
    fn add_credential<'a>(
        &'a mut self,
        scope_id: ScopeID,
        credential: Credential,
    ) -> std::pin::Pin<Box<dyn futures::Future<Output = ()> + 'a>> {
        if scope_id == self.account_id {
            panic!("Can't add credential for account document");
        }

        let jazz = Jazz(self.jazz.upgrade().unwrap());

        async move {
            let account_doc_id = jazz
                .resolve_named_document(self.account_id, CREDENTIALS_DOC_NAME)
                .await;

            let account_doc = jazz.load_document(account_doc_id).await;

            let account_doc_view = account_doc.start_writing();

            account_doc_view
                .get_root()
                .if_map_mut()
                .expect("Expected account doc to be a map")
                .insert(
                    Litl::from_se(&scope_id).tagged_data_to_raw_str().as_str(),
                    Litl::from_se(&credential),
                );

            account_doc.finish_writing(account_doc_view);
        }
        .boxed_local()
    }

    fn credentials_for<'a>(
        &'a self,
        scope_id: &'a ScopeID,
    ) -> Pin<Box<dyn Future<Output = Vec<Credential>> + 'a>> {
        let jazz = Jazz(self.jazz.upgrade().unwrap());
        async move {
            if scope_id == &self.account_id {
                vec![self.account_credential.clone()]
            } else {
                let account_doc_id = jazz
                    .resolve_named_document(self.account_id, CREDENTIALS_DOC_NAME)
                    .await;

                if scope_id == &account_doc_id.0 {
                    // prevent recursion, caller should use credentials from parent team
                    vec![]
                } else {
                    let account_doc = jazz.load_document(account_doc_id).await;

                    let (doc_updates_tx, mut doc_updates_rx) = futures::channel::mpsc::channel(10);
                    account_doc
                        .add_listener(Listener::new(
                            &format!(
                                "credentials_for_{}_{:?}",
                                Litl::from_se(scope_id).tagged_data_to_raw_str(),
                                ti64::now()
                            ),
                            doc_updates_tx,
                        ))
                        .await;

                    while let Some((root, _)) = doc_updates_rx.next().await {
                        if let Some(credential) = root.if_map().ok().and_then(|root_map| {
                            root_map
                                .get(Litl::from_se(&scope_id).tagged_data_to_raw_str().as_str())
                                .if_plain()
                                .ok()
                                .and_then(|credential_litl| {
                                    credential_litl.clone().try_into_de().ok()
                                })
                        }) {
                            return vec![credential];
                        }
                    }

                    unreachable!("Account doc listener closed before credential was found");
                }
            }
        }
        .boxed_local()
    }
}