171 lines
5.1 KiB
Markdown
171 lines
5.1 KiB
Markdown
# jmap-client
|
|
|
|
[](https://crates.io/crates/jmap-client)
|
|
[](https://github.com/stalwartlabs/jmap-client/actions/workflows/rust.yml)
|
|
[](https://docs.rs/jmap-client)
|
|
[](http://www.apache.org/licenses/LICENSE-2.0)
|
|
|
|
_jmap-client_ is a **JSON Meta Application Protocol (JMAP) library** written in Rust. The library is a full implementation of the JMAP RFCs including:
|
|
|
|
- JMAP Core ([RFC 8620](https://datatracker.ietf.org/doc/html/rfc8620))
|
|
- JMAP for Mail ([RFC 8621](https://datatracker.ietf.org/doc/html/rfc8621))
|
|
- JMAP over WebSocket ([RFC 8887](https://datatracker.ietf.org/doc/html/rfc8887)).
|
|
- JMAP for Sieve Scripts ([DRAFT-SIEVE-12](https://www.ietf.org/archive/id/draft-ietf-jmap-sieve-12.html)).
|
|
|
|
Features:
|
|
|
|
- Async and blocking support (use the cargo feature ``blocking`` to enable blocking).
|
|
- WebSocket async streams (use the cargo feature ``websockets`` to enable JMAP over WebSocket).
|
|
- EventSource async streams.
|
|
- Helper functions to reduce boilerplate code and quickly build JMAP requests.
|
|
- Fast parsing and encoding of JMAP requests.
|
|
|
|
## Usage Example
|
|
|
|
```rust
|
|
// Connect to the JMAP server using Basic authentication.
|
|
// (just for demonstration purposes, Bearer tokens should be used instead)
|
|
let client = Client::new()
|
|
.credentials(("john@example.org", "secret"))
|
|
.connect("https://jmap.example.org")
|
|
.await
|
|
.unwrap();
|
|
|
|
// Create a mailbox.
|
|
let mailbox_id = client
|
|
.mailbox_create("My Mailbox", None::<String>, Role::None)
|
|
.await
|
|
.unwrap()
|
|
.take_id();
|
|
|
|
// Import a message into the mailbox.
|
|
client
|
|
.email_import(
|
|
b"From: john@example.org\nSubject: test\n\n test".to_vec(),
|
|
[&mailbox_id],
|
|
["$draft"].into(),
|
|
None,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Obtain all e-mail ids matching a filter.
|
|
let email_id = client
|
|
.email_query(
|
|
Filter::and([
|
|
email::query::Filter::subject("test"),
|
|
email::query::Filter::in_mailbox(&mailbox_id),
|
|
email::query::Filter::has_keyword("$draft"),
|
|
])
|
|
.into(),
|
|
[email::query::Comparator::from()].into(),
|
|
)
|
|
.await
|
|
.unwrap()
|
|
.take_ids()
|
|
.pop()
|
|
.unwrap();
|
|
|
|
// Fetch an e-mail message.
|
|
let email = client
|
|
.email_get(
|
|
&email_id,
|
|
[Property::Subject, Property::Preview, Property::Keywords].into(),
|
|
)
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
assert_eq!(email.preview().unwrap(), "test");
|
|
assert_eq!(email.subject().unwrap(), "test");
|
|
assert_eq!(email.keywords(), ["$draft"]);
|
|
|
|
// Fetch only the updated properties of all mailboxes that changed
|
|
// since a state.
|
|
let mut request = client.build();
|
|
let changes_request = request.changes_mailbox("n").max_changes(0);
|
|
let properties_ref = changes_request.updated_properties_reference();
|
|
let updated_ref = changes_request.updated_reference();
|
|
request
|
|
.get_mailbox()
|
|
.ids_ref(updated_ref)
|
|
.properties_ref(properties_ref);
|
|
for mailbox in request
|
|
.send()
|
|
.await
|
|
.unwrap()
|
|
.unwrap_method_responses()
|
|
.pop()
|
|
.unwrap()
|
|
.unwrap_get_mailbox()
|
|
.unwrap()
|
|
.take_list()
|
|
{
|
|
println!("Changed mailbox: {:#?}", mailbox);
|
|
}
|
|
|
|
// Delete the mailbox including any messages
|
|
client.mailbox_destroy(&mailbox_id, true).await.unwrap();
|
|
|
|
// Open an EventSource connection with the JMAP server.
|
|
let mut stream = client
|
|
.event_source(
|
|
[
|
|
TypeState::Email,
|
|
TypeState::EmailDelivery,
|
|
TypeState::Mailbox,
|
|
TypeState::EmailSubmission,
|
|
TypeState::Identity,
|
|
]
|
|
.into(),
|
|
false,
|
|
60.into(),
|
|
None,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Consume events received over EventSource.
|
|
while let Some(event) = stream.next().await {
|
|
let changes = event.unwrap();
|
|
println!("-> Change id: {:?}", changes.id());
|
|
for account_id in changes.changed_accounts() {
|
|
println!(" Account {} has changes:", account_id);
|
|
if let Some(account_changes) = changes.changes(account_id) {
|
|
for (type_state, state_id) in account_changes {
|
|
println!(" Type {:?} has a new state {}.", type_state, state_id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
More examples available under the [examples](examples) directory.
|
|
|
|
## Testing
|
|
|
|
To run the testsuite:
|
|
|
|
```bash
|
|
$ cargo test --all-features
|
|
```
|
|
|
|
## Conformed RFCs
|
|
|
|
- [RFC 8620 - The JSON Meta Application Protocol (JMAP)](https://datatracker.ietf.org/doc/html/rfc8620)
|
|
- [RFC 8621 - The JSON Meta Application Protocol (JMAP) for Mail](https://datatracker.ietf.org/doc/html/rfc8621)
|
|
- [RFC 8887 - A JSON Meta Application Protocol (JMAP) Subprotocol for WebSocket](https://datatracker.ietf.org/doc/html/rfc8887)
|
|
|
|
## License
|
|
|
|
Licensed under either of
|
|
|
|
* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
|
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
|
|
|
at your option.
|
|
|
|
## Copyright
|
|
|
|
Copyright (C) 2022, Stalwart Labs Ltd.
|
|
|