# Direct Messaging 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.
## Asynchronous node-to-node E2E messaging (with Holo)
- This pattern is inspired from [mailbox](https://forum.holochain.org/t/mailbox/1084) and [async node2node messaging](https://forum.holochain.org/t/asynchronous-private-messaging/1085) pattern
- We still need to implement E2E scheme for this. Currently, the de facto standard encryption protocol for synchronous/asynchronous messaging is the signal protocol. Simplified diagram can be found [here](https://hackmd.io/@tatssato/rk6ZZ5dNP)
- This will be fully functional with a Public Key Infrastructre like [DeepKey](https://developer.holochain.org/docs/guide/dpki/)
- TODO: Work on update/delete
- can private entries be linked in RSM? (test this)
- How to effectively segregate private messages entries in a single source chain?
- Is there any risk in leaving encrypted message stay on DHT forever?
```mermaid
sequenceDiagram
participant AUI as Alice_UI
participant ALD as Alice_Lobby_DNA
participant BLD as Bobby_Lobby_DNA
participant BUI as Bobby_UI
participant LDHT as Lobby_DHT
AUI-->>AUI: Signs the message (chaperone)
AUI-->>ALD: call send_message
ALD-->>ALD: commit the message in source chain (flag: not_received)
alt DPKI available
ALD-->>LDHT: get PubKey of Bobby through DPKI
LDHT-->>ALD: send PubKey
else DPKI not available
alt online
ALD-->>BLD: ask for PubKey
BLD-->>ALD: send PubKey
else offline
ALD-->>AUI: Recipient offline
AUI-->>AUI: prompt resend
end
end
ALD-->>ALD: encrypt payload with bobby PubKey
ALD-->>BLD: call_remote to call receive_message (check CAP token?)
alt online
BLD-->>BLD: check the provenance of payload against the author field
alt provenance match
BLD-->>BLD: commit the payload in the source chain.
BLD-->>BUI: Send the encrypted entry
BLD-->>ALD: send received flag: true
ALD-->>ALD: link received flag to message payload (tentative)
BUI-->>BUI: decrypt the payload (chaperone)
BUI-->>BUI: render message
else provenance mismatch
BLD-->>ALD: provenance mismatch
ALD-->>AUI: send failed (provenance mismatch)
AUI-->>AUI: prompt resend
end
else offline
BLD-->>ALD: recepient offline
ALD-->>LDHT: commit encrypted message payload and link to Bobby Agent ID
BUI-->>BLD: call fetch_messages when back online
BLD-->>LDHT: get messages
LDHT-->>BLD: messages
BLD-->>BLD: commit messages to source chain
BLD-->>LDHT: remove links from agent_id to message (Is this enough?)
BLD-->>BUI: messages
BUI-->>BUI: render message
end
```
## Entry Structure
```rust
// This could be considered as the genesis of a conversation between 2 agents.
// This will only be committed synchronously to the receving agent
// with call_remote to avoid leaving traces of social connection in DHT.
// If the agent's source chain is hosted on Holo, then there is at least a 98.6%
// for synchronous remote call to work.
// Counter signing this entry might be helpful to ensure authenticity.
// QUESTION: Should we have this? Or just have have genesis field
// in incoming and outgoing message?
#[hdk_entry(id="conversation", visbility="private")]
struct Conversation {
creator: AgentPubKey,
conversant: AgentPubKey,
id: String,
first_message_address: Option<EntryHash>
timestamp: Timestamp,
}
let conversation = Conversation::new();
let outgoing_message = OugoingMessage::new();
create_link!(conversation, outgoing_message);
1. if conv exist {
let link = get_link(hash_entry!(&conversation));
let first_msg = get!(link.target);
let first_msg_hash = hash!(first_msg)
let new_message = IncomingMessage {
previous_message_address: Some(first_msg_hash)
}
create_link!(first_msg_hash, new_message_hash)
create_link!(conversation, new_message, 1);
}
2.
// This will be an entry that will be committed to the DHT in case the
// receiver is offline.
// This entry is going to be encrypted and signed with
// frequently rotating receiver's signing key and encryption key respectively.
#[hdk_entry(id="outgoing_message", visibility="public")]
struct OutgoingMessage {
conversation_address: EntryHash,
receiver: AgentPubKey,
// if None, then this is the first message.
previous_message_address: Option<EntryHash>,
conversation_address: Option<EntryHash>
// will be true if call_remote works,
// if committed to DHT then it will be false
// synchronous: bool,
payload: String,
timestamp: Timestamp,
}
Links: {
// gets deleted once fetch is done
receiving_agent_pubkey->outgoing_message|mailbox|
}
/* IGNORE
// This will be the entry that will be replicated from
// OutgoingMessage and committed privately
// in the receiver's source chain.
// For every IncomingMessage there should be an OutgoingMessage.
*/
#[hdk_entry(id="incoming_message", visibility="private")]
struct IncomingMessage {
conversation_address: EntryHash,
sender: AgentPubKey,
// Will be None if received synchronously
// if None then synchronous should be true
// outgoing_message_address: Option<EntryHash>,
previous_message_address: Option<EntryHash>,
conversation_address: Option<EntryHash>,
// Should be false if outgoing_message_address is true
// synchronous: bool,
timestamp: Timestamp,
payload: String,
}
```
## Entry Relationship Diagram
```mermaid
graph TD
subgraph dm zome
subgraph agents
alice_pubkey
bobby_pubkey
end
subgraph conversation
alice_bobby_conversation -.->|creator| alice_pubkey
alice_bobby_conversation -.->|conversant| bobby_pubkey
end
subgraph outgoing_message
outgoing_message_to_bobby_#1 -.-> alice_bobby_conversation
outgoing_message_to_bobby_#1 -.-> alice_pubkey
outgoing_message_to_bobby_#1 -.-> bobby_pubkey
bobby_pubkey --> |mailbox - deleted once fetch|outgoing_message_to_bobby_#1
end
subgraph incoming_message
incoming_message_from_alice_#1 -.-> alice_pubkey
incoming_message_from_alice_#1 -.-> bobby_pubkey
incoming_message_from_alice_#1 -.-> alice_bobby_conversation
incoming_message_from_alice_#1 -.-> |counterpart|outgoing_message_to_bobby_#1
incoming_message_from_bobby_#2 -.-> alice_pubkey
incoming_message_from_bobby_#2 -.-> bobby_pubkey
incoming_message_from_bobby_#2 -.-> |conversation|alice_bobby_conversation
incoming_message_from_bobby_#2 -.-> |previous_message|outgoing_message_to_bobby_#1
incoming_message_from_bobby_#2 -.-> incoming_message_from_alice_#1
end
end
```
## Validation
### Entries
### Links