xmpp/
builder.rs

1// Copyright (c) 2023 xmpp-rs contributors.
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#[cfg(feature = "starttls")]
8use crate::tokio_xmpp::connect::{DnsConfig, StartTlsServerConnector};
9
10use crate::{
11    Agent, ClientFeature, ClientType, Config, RoomNick,
12    jid::{BareJid, Jid, ResourceRef},
13    parsers::{
14        disco::{DiscoInfoResult, Feature, Identity},
15        ns,
16    },
17    tokio_xmpp::{Client as TokioXmppClient, connect::ServerConnector, xmlstream::Timeouts},
18};
19
20pub struct ClientBuilder<'a, C: ServerConnector> {
21    jid: BareJid,
22    password: &'a str,
23    server_connector: C,
24    config: Config,
25    features: Vec<ClientFeature>,
26    resource: Option<String>,
27    timeouts: Timeouts,
28}
29
30#[cfg(feature = "starttls")]
31impl ClientBuilder<'_, StartTlsServerConnector> {
32    pub fn new<'a>(jid: BareJid, password: &'a str) -> ClientBuilder<'a, StartTlsServerConnector> {
33        Self::new_with_connector(
34            jid.clone(),
35            password,
36            StartTlsServerConnector(DnsConfig::srv_default_client(jid.domain())),
37        )
38    }
39}
40
41impl<C: ServerConnector> ClientBuilder<'_, C> {
42    pub fn new_with_connector<'a>(
43        jid: BareJid,
44        password: &'a str,
45        server_connector: C,
46    ) -> ClientBuilder<'a, C> {
47        ClientBuilder {
48            jid,
49            password,
50            server_connector,
51            config: Config::default(),
52            features: vec![],
53            resource: None,
54            timeouts: Timeouts::default(),
55        }
56    }
57
58    /// Optionally set a resource associated to this device on the client
59    pub fn set_resource(mut self, resource: &str) -> Self {
60        self.resource = Some(resource.to_string());
61        self
62    }
63
64    pub fn set_config(mut self, config: Config) -> Self {
65        self.config = config;
66        self
67    }
68
69    pub fn set_client(mut self, type_: ClientType, name: &str) -> Self {
70        self.config.disco = (type_, String::from(name));
71        self
72    }
73
74    pub fn set_website(mut self, url: &str) -> Self {
75        self.config.website = String::from(url);
76        self
77    }
78
79    pub fn set_default_nick(mut self, nick: impl AsRef<ResourceRef>) -> Self {
80        self.config.default_nick = RoomNick::from_resource_ref(nick.as_ref());
81        self
82    }
83
84    pub fn set_lang(mut self, lang: Vec<String>) -> Self {
85        self.config.lang = lang;
86        self
87    }
88
89    /// Configure the timeouts used.
90    ///
91    /// See [`Timeouts`] for more information on the semantics and the
92    /// defaults (which are used unless you call this method).
93    pub fn set_timeouts(mut self, timeouts: Timeouts) -> Self {
94        self.timeouts = timeouts;
95        self
96    }
97
98    pub fn enable_feature(mut self, feature: ClientFeature) -> Self {
99        self.features.push(feature);
100        self
101    }
102
103    fn make_disco(&self) -> DiscoInfoResult {
104        let identities = vec![Identity::new(
105            "client",
106            self.config.disco.0.to_string(),
107            "en",
108            self.config.disco.1.to_string(),
109        )];
110        let mut features = vec![Feature::new(ns::DISCO_INFO)];
111        #[cfg(feature = "avatars")]
112        {
113            if self.features.contains(&ClientFeature::Avatars) {
114                features.push(Feature::new(format!("{}+notify", ns::AVATAR_METADATA)));
115            }
116        }
117        if self.features.contains(&ClientFeature::JoinRooms) {
118            features.push(Feature::new(format!("{}+notify", ns::BOOKMARKS2)));
119        }
120        DiscoInfoResult {
121            node: None,
122            identities,
123            features,
124            extensions: vec![],
125        }
126    }
127
128    pub fn build(self) -> Agent {
129        let jid: Jid = if let Some(resource) = &self.resource {
130            self.jid.with_resource_str(resource).unwrap().into()
131        } else {
132            self.jid.clone().into()
133        };
134
135        let client = TokioXmppClient::new_with_connector(
136            jid,
137            self.password,
138            self.server_connector.clone(),
139            self.timeouts,
140        );
141        self.build_impl(client)
142    }
143
144    // This function is meant to be used for testing build
145    pub(crate) fn build_impl(self, client: TokioXmppClient) -> Agent {
146        let disco = self.make_disco();
147
148        Agent::new(client, self.config, disco)
149    }
150}