tuwunel_service/appservice/
thirdparty.rs1use std::collections::BTreeMap;
2
3use futures::StreamExt;
4use ruma::{
5 api::appservice::{
6 Registration,
7 thirdparty::{get_location_for_protocol, get_protocol, get_user_for_protocol},
8 },
9 serde::Raw,
10 thirdparty::{Location, Protocol, User},
11};
12use serde_json::{Value, value::to_raw_value};
13use tuwunel_core::{
14 implement,
15 utils::stream::{IterStream, ReadyExt, WidebandExt},
16};
17
18type Protocols = BTreeMap<String, Raw<Protocol>>;
19
20#[implement(super::Service)]
31#[tracing::instrument(level = "debug", skip(self))]
32pub async fn thirdparty_protocols(&self, only: Option<&str>) -> Protocols {
33 let jobs: Vec<(Registration, String)> = self
34 .read()
35 .await
36 .values()
37 .filter_map(|info| {
38 info.registration
39 .protocols
40 .as_ref()
41 .map(|protocols| (&info.registration, protocols))
42 })
43 .flat_map(|(registration, protocols)| {
44 protocols
45 .iter()
46 .filter(|protocol| only.is_none_or(|only| only == protocol.as_str()))
47 .map(move |protocol| (registration.clone(), protocol.clone()))
48 })
49 .collect();
50
51 jobs.into_iter()
52 .stream()
53 .wide_filter_map(async |(registration, protocol)| {
54 let request = get_protocol::v1::Request { protocol: protocol.clone() };
55
56 self.send_request(registration, request)
57 .await
58 .ok()
59 .flatten()
60 .map(|response| (protocol, Raw::from_json(response.protocol.into_json())))
61 })
62 .ready_fold(Protocols::new(), |mut protocols, (protocol, metadata)| {
63 protocols
64 .entry(protocol)
65 .and_modify(|existing| merge_instances(existing, &metadata))
66 .or_insert(metadata);
67
68 protocols
69 })
70 .await
71}
72
73fn merge_instances(base: &mut Raw<Protocol>, addition: &Raw<Protocol>) {
77 let (Ok(mut merged), Ok(extra)) =
78 (base.deserialize_as::<Value>(), addition.deserialize_as::<Value>())
79 else {
80 return;
81 };
82
83 let (Some(instances), Some(added)) = (
84 merged
85 .get_mut("instances")
86 .and_then(Value::as_array_mut),
87 extra.get("instances").and_then(Value::as_array),
88 ) else {
89 return;
90 };
91
92 instances.extend(added.iter().cloned());
93
94 if let Ok(raw) = to_raw_value(&merged) {
95 *base = Raw::from_json(raw);
96 }
97}
98
99#[implement(super::Service)]
102#[tracing::instrument(level = "debug", skip(self, fields))]
103pub async fn thirdparty_users(
104 &self,
105 protocol: &str,
106 fields: &BTreeMap<String, String>,
107) -> Vec<User> {
108 self.declaring(protocol)
109 .await
110 .into_iter()
111 .stream()
112 .wide_filter_map(async |registration| {
113 let request = get_user_for_protocol::v1::Request {
114 protocol: protocol.to_owned(),
115 fields: forwarded_fields(fields).collect(),
116 };
117
118 self.send_request(registration, request)
119 .await
120 .ok()
121 .flatten()
122 })
123 .map(|response| response.users)
124 .concat()
125 .await
126}
127
128#[implement(super::Service)]
131#[tracing::instrument(level = "debug", skip(self, fields))]
132pub async fn thirdparty_locations(
133 &self,
134 protocol: &str,
135 fields: &BTreeMap<String, String>,
136) -> Vec<Location> {
137 self.declaring(protocol)
138 .await
139 .into_iter()
140 .stream()
141 .wide_filter_map(async |registration| {
142 let request = get_location_for_protocol::v1::Request {
143 protocol: protocol.to_owned(),
144 fields: forwarded_fields(fields).collect(),
145 };
146
147 self.send_request(registration, request)
148 .await
149 .ok()
150 .flatten()
151 })
152 .map(|response| response.locations)
153 .concat()
154 .await
155}
156
157#[implement(super::Service)]
160async fn declaring(&self, protocol: &str) -> Vec<Registration> {
161 self.read()
162 .await
163 .values()
164 .filter(|info| declares(&info.registration, protocol))
165 .map(|info| info.registration.clone())
166 .collect()
167}
168
169fn forwarded_fields(
172 fields: &BTreeMap<String, String>,
173) -> impl Iterator<Item = (String, String)> + '_ {
174 fields
175 .iter()
176 .filter(|(name, _)| name.as_str() != "access_token")
177 .map(|(name, value)| (name.clone(), value.clone()))
178}
179
180fn declares(registration: &Registration, protocol: &str) -> bool {
181 registration
182 .protocols
183 .as_ref()
184 .is_some_and(|protocols| {
185 protocols
186 .iter()
187 .any(|declared| declared == protocol)
188 })
189}