deltachat/webxdc/
integration.rs1use std::path::Path;
2
3use crate::chat::{send_msg, ChatId};
4use crate::config::Config;
5use crate::contact::ContactId;
6use crate::context::Context;
7use crate::message::{Message, MsgId, Viewtype};
8use crate::param::Param;
9use crate::webxdc::{maps_integration, StatusUpdateItem, StatusUpdateSerial};
10use anyhow::Result;
11
12impl Context {
13 pub async fn set_webxdc_integration(&self, file: &str) -> Result<()> {
16 let chat_id = ChatId::create_for_contact(self, ContactId::SELF).await?;
17 let mut msg = Message::new(Viewtype::Webxdc);
18 msg.set_file_and_deduplicate(self, Path::new(&file), None, None)?;
19 msg.hidden = true;
20 msg.param.set_int(Param::WebxdcIntegration, 1);
21 msg.param.set_int(Param::GuaranteeE2ee, 1); send_msg(self, chat_id, &mut msg).await?;
23 Ok(())
24 }
25
26 pub async fn init_webxdc_integration(
31 &self,
32 integrate_for: Option<ChatId>,
33 ) -> Result<Option<MsgId>> {
34 let Some(instance_id) = self
35 .get_config_parsed::<u32>(Config::WebxdcIntegration)
36 .await?
37 else {
38 return Ok(None);
39 };
40
41 let Some(mut instance) =
42 Message::load_from_db_optional(self, MsgId::new(instance_id)).await?
43 else {
44 return Ok(None);
45 };
46
47 if instance.viewtype != Viewtype::Webxdc {
48 return Ok(None);
49 }
50
51 let integrate_for = integrate_for.unwrap_or_default().to_u32() as i32;
52 if instance.param.get_int(Param::WebxdcIntegrateFor) != Some(integrate_for) {
53 instance
54 .param
55 .set_int(Param::WebxdcIntegrateFor, integrate_for);
56 instance.update_param(self).await?;
57 }
58 Ok(Some(instance.id))
59 }
60
61 pub(crate) async fn update_webxdc_integration_database(
63 &self,
64 msg: &mut Message,
65 context: &Context,
66 ) -> Result<()> {
67 if msg.viewtype == Viewtype::Webxdc {
68 let is_integration = if msg.param.get_int(Param::WebxdcIntegration).is_some() {
69 true
70 } else if msg.chat_id.is_self_talk(context).await? {
71 let info = msg.get_webxdc_info(context).await?;
72 if info.request_integration == "map" {
73 msg.param.set_int(Param::WebxdcIntegration, 1);
74 msg.update_param(context).await?;
75 true
76 } else {
77 false
78 }
79 } else {
80 false
81 };
82
83 if is_integration {
84 self.set_config_internal(
85 Config::WebxdcIntegration,
86 Some(&msg.id.to_u32().to_string()),
87 )
88 .await?;
89 }
90 }
91 Ok(())
92 }
93
94 pub(crate) async fn intercept_send_webxdc_status_update(
96 &self,
97 instance: Message,
98 status_update: StatusUpdateItem,
99 ) -> Result<()> {
100 let chat_id = instance.webxdc_integrated_for();
101 maps_integration::intercept_send_update(self, chat_id, status_update).await
102 }
103
104 pub(crate) async fn intercept_get_webxdc_status_updates(
106 &self,
107 instance: Message,
108 last_known_serial: StatusUpdateSerial,
109 ) -> Result<String> {
110 let chat_id = instance.webxdc_integrated_for();
111 maps_integration::intercept_get_updates(self, chat_id, last_known_serial).await
112 }
113}
114
115impl Message {
116 fn webxdc_integrated_for(&self) -> Option<ChatId> {
119 let raw_id = self.param.get_int(Param::WebxdcIntegrateFor).unwrap_or(0) as u32;
120 if raw_id > 0 {
121 Some(ChatId::new(raw_id))
122 } else {
123 None
124 }
125 }
126
127 pub(crate) async fn is_set_as_webxdc_integration(&self, context: &Context) -> Result<bool> {
129 if let Some(integration_id) = context
130 .get_config_parsed::<u32>(Config::WebxdcIntegration)
131 .await?
132 {
133 Ok(integration_id == self.id.to_u32())
134 } else {
135 Ok(false)
136 }
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use crate::config::Config;
143 use crate::context::Context;
144 use crate::message;
145 use crate::message::{Message, Viewtype};
146 use crate::test_utils::TestContext;
147 use anyhow::Result;
148 use std::time::Duration;
149
150 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
151 async fn test_default_integrations_are_single_device() -> Result<()> {
152 let t = TestContext::new_alice().await;
153 t.set_config_bool(Config::BccSelf, false).await?;
154
155 let bytes = include_bytes!("../../test-data/webxdc/minimal.xdc");
156 let file = t.get_blobdir().join("maps.xdc");
157 tokio::fs::write(&file, bytes).await.unwrap();
158 t.set_webxdc_integration(file.to_str().unwrap()).await?;
159
160 let sent = t.pop_sent_msg_opt(Duration::from_secs(1)).await;
162 assert!(sent.is_none());
163
164 Ok(())
165 }
166
167 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
168 async fn test_overwrite_default_integration() -> Result<()> {
169 let t = TestContext::new_alice().await;
170 let self_chat = &t.get_self_chat().await;
171 assert!(t.init_webxdc_integration(None).await?.is_none());
172
173 async fn assert_integration(t: &Context, name: &str) -> Result<()> {
174 let integration_id = t.init_webxdc_integration(None).await?.unwrap();
175 let integration = Message::load_from_db(t, integration_id).await?;
176 let integration_info = integration.get_webxdc_info(t).await?;
177 assert_eq!(integration_info.name, name);
178 Ok(())
179 }
180
181 let bytes = include_bytes!("../../test-data/webxdc/with-manifest-and-png-icon.xdc");
183 let file = t.get_blobdir().join("maps.xdc");
184 tokio::fs::write(&file, bytes).await.unwrap();
185 t.set_webxdc_integration(file.to_str().unwrap()).await?;
186 assert_integration(&t, "with some icon").await?;
187
188 let mut msg = Message::new(Viewtype::Webxdc);
190 msg.set_file_from_bytes(
191 &t,
192 "mapstest.xdc",
193 include_bytes!("../../test-data/webxdc/mapstest-integration-unset.xdc"),
194 None,
195 )?;
196 t.send_msg(self_chat.id, &mut msg).await;
197 assert_integration(&t, "with some icon").await?; let mut msg = Message::new(Viewtype::Webxdc);
201 msg.set_file_from_bytes(
202 &t,
203 "mapstest.xdc",
204 include_bytes!("../../test-data/webxdc/mapstest-integration-set.xdc"),
205 None,
206 )?;
207 let sent = t.send_msg(self_chat.id, &mut msg).await;
208 let info = msg.get_webxdc_info(&t).await?;
209 assert!(info.summary.contains("Used as map"));
210 assert_integration(&t, "Maps Test 2").await?;
211
212 let t2 = TestContext::new_alice().await;
214 let msg2 = t2.recv_msg(&sent).await;
215 let info = msg2.get_webxdc_info(&t2).await?;
216 assert!(info.summary.contains("To use as map,"));
217 assert!(t2.init_webxdc_integration(None).await?.is_none());
218
219 message::delete_msgs(&t, &[msg.id]).await?;
221 assert!(t.init_webxdc_integration(None).await?.is_none());
222
223 Ok(())
224 }
225}