---
# System prepended metadata

title: Messages Zome

---

# Messages Zome

## User Stories
- As a user, I want to send a message to a contact so that I can deliver textual information to that user
- As a user, I want to reply to a specific message so that it would be clear to which message i'm replying to.
- As a user, I want to be able to search messages in a chat using a keyword so that I can easily find previously sent messages
- As a user, I want to be able to search messages in a chat using a keyword so that I can easily find previously sent messages
- As a user, I want to see the status of my messages so that I know if my message has been sent or if my messages has been seen (sending, sent, seen, error)
- As a user, I want to search messages by date so that I can pinpoint specific messages
- As a user, I want to delete my own message so that I can remove wrong sent messages.
- As a user, I want to edit a message I sent so that I can update information.

## Entry Structure
```rust
Entry "message_anchor"
    struct MessageAnchor {
        author: Address,
        timestamp: u64,
        receiver: Address,
        message_type: String,
    }
    Links: {
        agent_id->message_anchor|message|
        message_anchor->message
    }

Entry "message"
    struct Message {
        anchor: Address,
        payload: String,
        lastmodified: u64
    }
    Links: {
        agent_id->message
        message_anchor->message|messages|
        message->message
    }


```
**Anchor explanation**
The message zome follows an anchor-first pattern.

An entry -- the message_anchor -- serves as the anchor for another entry -- the message itself.
The message_anchor contains static information about the more dynamic message entry. Edited messages form a chain with a link from the latest version of the message to the previous version until the very first version. When a message is edited, a new message entry is created and commited and the link from the message_anchor is transferred form the previous version to the more recent version.

**agent_id -> message_anchor**
Links the message_anchor from the the author's agent address. This allows fetching of all message_anchors using the agent's address.

**agent_id -> message**
Links the message from the author's agent address. This allows quick fetching og all messages created by a user.

**message_anchor -> message**
Links a message (containing the actual payload message) from the message_anchor. The link always points to the latest version of a message (in cases of edited messages). A message can be fetched using the anchor's address.

**message -> message**
Links a message to its immediated previous version forming a chain. The edit history of a message can be fetched by traversing the links one message after another.


## Entry Relationship Diagram
```mermaid
graph LR
    subgraph messageszome
      subgraph agents
        alice_id
      end
      subgraph entries
          subgraph message_anchors
              alice_id-->message_1_anchor
              alice_id-->message_2_anchor
          end
          subgraph messages
            message_1_v1 -.-> message_1_anchor
            alice_id --> message_1_v1
            message_1_anchor --> message_1_v1

            message_2_v3 -.-> message_2_anchor
            message_2_v2 -.-> message_2_anchor
            message_2_v1 -.-> message_2_anchor

            message_2_v2 --> message_2_v1
            message_2_v3 --> message_2_v2
            message_2_anchor --> message_2_v3

            alice_id --> message_2_v3
          end
      end

    end

```

## Validation
### Entries
### Links

## Zome Functions
### send
```rust
pub fn send_message(recipient: Address, message: String, timestamp: u64) -> ZomeApiResult<MessageReturn> {
    let message_anchor = MessageAnchor::new(AGENT_ADDRESS.to_string().into(), recipient.clone(), timestamp.clone(), "text".to_string());
    let message_anchor_entry = message_anchor.clone().entry();

    let message_anchor_address = hdk::commit_entry(&message_anchor_entry)?;

    hdk::link_entries(
        &AGENT_ADDRESS, 
        &message_anchor_address, 
        AGENT_MESSAGE_ANCHOR_LINK_TYPE, 
        ""
    )?;

    let message_struct = Message::new(message_anchor_address.clone(), message.clone(), timestamp.clone());
    let message_entry = message_struct.entry();

    let message_entry_address = hdk::commit_entry(&message_entry)?;
    
    hdk::link_entries(
        &message_anchor_address, 
        &message_entry_address,
        MESSAGE_ANCHOR_LINK_TYPE,
        ""
    )?;

    hdk::link_entries(
        &AGENT_ADDRESS,
        &message_entry_address,
        AUTHOR_MESSAGE_ANCHOR_LINK_TYPE,
        ""
    )?;

    let message_struct = Message::new(message_anchor_address.clone(), message.clone(), timestamp.clone());
    let message_return = MessageReturn::new(message_anchor.clone(), message_anchor_address.clone(), message_struct);

    match hdk::send(recipient.to_owned(), json!(message_return).to_string(), 10000.into()) {
        Ok(_response) => {
            let _emitted = hdk::emit_signal(
                "message_sent".to_owned(),
                JsonString::from_json(&format!("{{\"code\": \"{}\", \"message\": \"{}\", \"recipient\": \"{}\"}}", "message_sent".to_owned(), message, recipient.to_string()))
            )?;
            Ok(message_return)
        },
        _=> {
            let _emitted = hdk::emit_signal(
                "send_message_recipient_offline",
                JsonString::from_json(&format!("{{\"code\": \"{}\"}}", "recipient_offline".to_owned()))
            )?;
            Ok(message_return)
        }
    }
}
```
Takes in 3 input values -- the recipient agent's address \<HashtSring\>, the message payload \<Strinh\>, and the timestamp \<u64\> of the message creation. If successful, the function commits a MessageAnchor entry and a MessageEntry to the DHT, emits a signal to the sender that the call was successful, and returns a structure MessageReturn if successful. On the recipient's side, the receive zome function is triggered.

    MessageReturn {
        address: address,
        author: anchor.author,
        recipient: anchor.recipient,
        timestamp: anchor.timestamp,
        message: message.payload,
        lastmodified: message.lastmodified
    }

### receive
A callback triggered when a message is received through the send function. Emits a signal notifying the recipient that a message has been received and contains the actual payload of the message.
```rust
pub fn receive(_from: Address, payload: String) -> String {
    let maybe_dna_instance_id_json =  hdk::property("instance_id");
    let dna_instance_id: Option<String> = match maybe_dna_instance_id_json {
        Ok(dna_instance_id_json ) => {
            match from_str(&dna_instance_id_json.to_string()) {
                Ok(instance_id) => Some(instance_id),
                _ => Some("".to_owned()),
            }
        },
        _ => None,
    };
    let _emitted = match dna_instance_id {
        Some(instance_id) => {
            hdk::emit_signal(
                "message_received",
                JsonString::from_json(&format!("{{\"payload\": {}, \"instance_id\": \"{}\"}}", payload, instance_id))
            )
        },
        None => {
            hdk::emit_signal(
                "message_received",
                JsonString::from_json(&format!("{{\"payload\": {}}}", payload))
            )
        }
    };
    format!("{{\"code\": \"{}\"}}", "message_sent".to_string())
}
```

### update_message
Updates an existing message with a new message string. Takes in as input the address of the MessageAnchor \<HashString\>, the new message \<String\>, and the timestamp of when the edit mas made. Returns true if the operation was successful.

```rust
pub fn update_message(id: Address, message: String, lastmodified: u64) -> ZomeApiResult<bool> {
    let message_address = get_message_head_address(id.clone())?;

    hdk::remove_link(
        &id,
        &message_address,
        MESSAGE_ANCHOR_LINK_TYPE,
        ""
    )?;
    hdk::remove_link(
        &AGENT_ADDRESS,
        &message_address,
        AUTHOR_MESSAGE_ANCHOR_LINK_TYPE,
        ""
    )?;
    
    let message_struct = Message::new(id.clone(), message.clone(), lastmodified.clone());
    let message_entry = message_struct.entry();
    let message_entry_address = hdk::update_entry(message_entry, &message_address)?;
    
    hdk::link_entries(
        &id, 
        &message_entry_address,
        MESSAGE_ANCHOR_LINK_TYPE,
        ""
    )?;

    hdk::link_entries(
        &AGENT_ADDRESS, 
        &message_entry_address,
        AUTHOR_MESSAGE_ANCHOR_LINK_TYPE,
        ""
    )?;

    hdk::link_entries(
        &message_entry_address,
        &message_address,
        MESSAGE_MESSAGE_ANCHOR_LINK_TYPE,
        ""
    )?;
 
    Ok(true)
}
```
### delete_messages *(may be updated to just remove the MessageAnchor and all links instead of removing all Message entries)*
Deletes an existing message. Takes in as input the address of the MessageAnchor \<HashString\>. Marks as removed the Message entries (all message versions) and the MessageAnchor. Returns true if the operation was successful.
```rust
pub fn delete_messages(anchor_addresses: Vec<Address>) -> ZomeApiResult<bool> {
    let deduped_addresses = address_deduper(anchor_addresses);

    for i in 0..deduped_addresses.len() {
        let anchor_address = deduped_addresses[i].clone();
        let message_head_address = get_message_head_address(anchor_address.clone())?;
        let mut current_address: Address = message_head_address.clone();

        'inner: loop {
            let message_link = hdk::get_links(
                &current_address,
                LinkMatch::Exactly(MESSAGE_MESSAGE_ANCHOR_LINK_TYPE),
                LinkMatch::Exactly("")
            )?.addresses();
            
            if message_link.is_empty() { 
                
                hdk::remove_link(
                    &AGENT_ADDRESS,
                    &message_head_address,
                    AUTHOR_MESSAGE_ANCHOR_LINK_TYPE,
                    ""
                )?;

                hdk::remove_entry(&current_address)?;
                break 'inner;
            };
            
            hdk::remove_link(
                &current_address,
                &message_link[0],
                MESSAGE_MESSAGE_ANCHOR_LINK_TYPE,
                ""
            )?;

            current_address = message_link[0].clone();
        };

        hdk::remove_link(
            &anchor_address,
            &message_head_address,
            MESSAGE_ANCHOR_LINK_TYPE,
            ""
        )?;
        hdk::remove_entry(&anchor_address)?;

    }

    Ok(true)
}

```

### get_all_messages_from_addresses
Fetches all messages authored by the given agent addresses. Takes in an array of addresses \<HashString\> and returns an array of MessageReturn structures.

```rust
pub fn get_all_messages_from_addresses(ids: Vec<Address>)->ZomeApiResult<Vec<MessageReturn>> {
    let deduped_ids = address_deduper(ids);
    let messages_entry_address = deduped_ids.into_iter().map(|id: HashString| {
        match hdk::get_links(
            &id,
            LinkMatch::Exactly(AUTHOR_MESSAGE_ANCHOR_LINK_TYPE),
            LinkMatch::Exactly("")
        ) {
            Ok(res) => res.addresses(),
            Err(_e) => Vec::default(),
        }
    }).flatten().collect::<Vec<HashString>>();

    match messages_entry_address.is_empty() {
        false => {
            let mut res: Vec<MessageReturn> = Vec::new();

            for i in 0..messages_entry_address.len() {

                // get the entries associated with the address
                let entry_result = hdk::api::get_entry_result(
                    &messages_entry_address[i], GetEntryOptions::new(
                        StatusRequestKind::default(),
                        true,
                        true,
                        Timeout::default()
                    )
                )?;

                match entry_result.found() {
                    true => {
                        let result_type = entry_result.result;

                        match result_type {
                            
                            GetEntryResultType::Single(item) => {
                                if let Some(entry) = item.entry {
                                    if let Some(message) = Message::from_entry(&entry){
                                        let anchor_entry = hdk::get_entry(&message.anchor)?;
                                        let message_anchor = match anchor_entry {
                                            Some(t) => {
                                                if let Some(anchor) = MessageAnchor::from_entry(&t) { anchor } 
                                                else { MessageAnchor::blank() }
                                            },
                                            None => MessageAnchor::blank()
                                        };
                                        res.push(MessageReturn::new(message_anchor,message.anchor.clone(),message))
                                    } 
                                }
                            },
                            GetEntryResultType::All(history) => {
                                if let Some(entry_item) = history.items.last() { 
                                    if let Some(entry) = &(*entry_item).entry {
                                        if let Some(message) = Message::from_entry(&entry){
                                            let anchor_entry = hdk::get_entry(&message.anchor)?;
                                            let message_anchor = match anchor_entry {
                                                Some(t) => {
                                                    if let Some(anchor) = MessageAnchor::from_entry(&t) { anchor } 
                                                    else { MessageAnchor::blank() }
                                                },
                                                None => MessageAnchor::blank()
                                            };
                                            res.push(MessageReturn::new(message_anchor,message.anchor.clone(),message))
                                        }
                                    }
                                } 
                            }
                        }
                    },
                    false => continue
                }
            }
            Ok(res)

        }
        true =>  Ok(Vec::new())
    }
}
```