1use anyhow::Result;
2use async_trait::async_trait;
3use futures::stream::BoxStream;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7#[serde(rename_all = "camelCase")]
8pub struct DeviceCapabilities {
9 pub can_host: bool,
10 pub can_sync: bool,
11 pub read_only: bool,
12}
13
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct Device {
17 pub app_tag: Option<String>,
18 pub device_id: String,
19 pub user_id: Option<String>,
20 pub device_name: String,
21 pub platform_type: Option<String>,
22 pub capabilities: Option<DeviceCapabilities>,
23 pub session_id: Option<String>,
24 pub node_id: Option<String>,
25 pub tag: Option<String>,
26 pub kind: Option<String>,
27 pub metadata: Option<String>,
28 pub online: bool,
29 pub ticket: Option<String>,
30 pub last_seen_at: Option<serde_json::Value>,
31 pub expires_at: Option<serde_json::Value>,
32 pub created_at: Option<serde_json::Value>,
33 pub updated_at: Option<serde_json::Value>,
34 #[serde(default)]
38 pub excluded_peers: Vec<String>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42#[serde(rename_all = "camelCase")]
43pub struct E2eeInfo {
44 pub session_key_id: String,
45 pub cipher_text: String,
46 pub iv: String,
47 pub salt: String,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51#[serde(rename_all = "camelCase")]
52pub struct SignalingSession {
53 pub connection_id: String,
54 pub initiator: String,
55 pub target: String,
56 pub initiator_device_id: String,
57 pub target_device_id: String,
58 pub connection_type: Option<String>,
59 pub offer: Option<serde_json::Value>,
60 pub offer_e2ee: Option<E2eeInfo>,
61 pub answer: Option<serde_json::Value>,
62 pub answer_e2ee: Option<E2eeInfo>,
63 #[serde(default)]
64 pub ice_candidates: Vec<serde_json::Value>,
65 pub initiator_node_id: Option<String>,
66 pub target_node_id: Option<String>,
67 pub initiator_endpoint_addr: Option<String>,
68 pub target_endpoint_addr: Option<String>,
69 pub intent: Option<String>,
70 pub app_tag: Option<String>,
71 pub created_at: Option<i64>,
72 pub expires_at: Option<i64>,
73 pub state: String,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77#[serde(tag = "type", rename_all = "camelCase")]
78pub enum SessionEvent {
79 Added {
80 session: SignalingSession,
81 },
82 Modified {
83 session: SignalingSession,
84 },
85 Removed {
86 #[serde(rename = "sessionId")]
87 session_id: String,
88 },
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
92#[serde(tag = "type", rename_all = "camelCase")]
93pub enum DeviceEvent {
94 Added {
95 device: Device,
96 },
97 Modified {
98 device: Device,
99 },
100 Removed {
101 #[serde(rename = "deviceId")]
102 device_id: String,
103 },
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
107#[serde(rename_all = "camelCase")]
108pub struct SignalingEnvelope {
109 #[serde(skip_serializing_if = "Option::is_none")]
110 pub app_tag: Option<String>,
111 pub sender_id: String,
112 pub target_id: String,
113 pub payload: String,
114 pub state: Option<String>,
115 pub reply_payload: Option<String>,
116 pub timestamp: i64,
117 #[serde(skip_serializing_if = "Option::is_none")]
118 pub sender_user_id: Option<String>,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub target_user_id: Option<String>,
121 #[serde(skip_serializing_if = "Option::is_none")]
122 pub expires_at: Option<i64>,
123}
124
125#[cfg(target_arch = "wasm32")]
126pub trait SendSyncBound {}
127#[cfg(target_arch = "wasm32")]
128impl<T> SendSyncBound for T {}
129
130#[cfg(not(target_arch = "wasm32"))]
131pub trait SendSyncBound: Send + Sync {}
132#[cfg(not(target_arch = "wasm32"))]
133impl<T: Send + Sync> SendSyncBound for T {}
134
135#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
136#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
137pub trait SignalingBackend: SendSyncBound {
138 async fn update_presence(
139 &self,
140 user_id: &str,
141 local_node_id: &str,
142 ticket_str: &str,
143 is_online: bool,
144 name: &str,
145 ttl_ms: u64,
146 metadata: Option<&str>,
147 ) -> Result<()>;
148
149 async fn set_offline(&self, user_id: &str, local_node_id: &str) -> Result<()>;
150
151 async fn update_live_presence(
154 &self,
155 _user_id: &str,
156 _local_node_id: &str,
157 _ticket_str: &str,
158 _name: &str,
159 _metadata: Option<&str>,
160 ) -> Result<()> {
161 #[cfg(target_arch = "wasm32")]
162 return Ok(());
163
164 #[cfg(not(target_arch = "wasm32"))]
165 anyhow::bail!("native signaling backend does not implement managed live presence")
166 }
167
168 async fn set_live_presence_offline(&self, _user_id: &str, _local_node_id: &str) -> Result<()> {
170 #[cfg(target_arch = "wasm32")]
171 return Ok(());
172
173 #[cfg(not(target_arch = "wasm32"))]
174 anyhow::bail!("native signaling backend does not implement managed offline presence")
175 }
176
177 async fn update_device(
178 &self,
179 user_id: &str,
180 device_id: &str,
181 device_name: Option<&str>,
182 capabilities: Option<DeviceCapabilities>,
183 metadata: Option<&str>,
184 ) -> Result<()>;
185
186 async fn delete_device(&self, user_id: &str, device_id: &str) -> Result<()>;
187
188 async fn set_excluded_peers(
191 &self,
192 user_id: &str,
193 local_node_id: &str,
194 excluded_peers: &[String],
195 ) -> Result<()>;
196
197 async fn search_devices(
198 &self,
199 user_id: &str,
200 exclude_node_id: Option<&str>,
201 ) -> Result<Vec<Device>>;
202
203 async fn list_devices(
204 &self,
205 user_id: &str,
206 exclude_node_id: Option<&str>,
207 ) -> Result<Vec<Device>>;
208
209 async fn send_message(
210 &self,
211 sender_id: &str,
212 target_id: &str,
213 payload: &str,
214 state: Option<&str>,
215 reply_payload: Option<&str>,
216 ) -> Result<String>;
217
218 async fn subscribe_devices(
219 &self,
220 user_id: &str,
221 ) -> Result<BoxStream<'static, Result<Vec<DeviceEvent>>>>;
222
223 async fn create_session(&self, session: SignalingSession) -> Result<()>;
224
225 async fn update_session(&self, session_id: &str, update_data: serde_json::Value) -> Result<()>;
226
227 async fn subscribe_sessions(
228 &self,
229 local_device_id: &str,
230 ) -> Result<BoxStream<'static, Result<Vec<SessionEvent>>>>;
231}
232
233#[derive(Debug, Default)]
241pub struct GatewayRequiredSignalingBackend;
242
243impl GatewayRequiredSignalingBackend {
244 fn unavailable<T>() -> Result<T> {
245 anyhow::bail!(
246 "managed coordination gateway is required; direct Firebase coordination has been removed"
247 )
248 }
249}
250
251#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
252#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
253impl SignalingBackend for GatewayRequiredSignalingBackend {
254 async fn update_presence(
255 &self,
256 _user_id: &str,
257 _local_node_id: &str,
258 _ticket_str: &str,
259 _is_online: bool,
260 _name: &str,
261 _ttl_ms: u64,
262 _metadata: Option<&str>,
263 ) -> Result<()> {
264 Self::unavailable()
265 }
266
267 async fn set_offline(&self, _user_id: &str, _local_node_id: &str) -> Result<()> {
268 Self::unavailable()
269 }
270
271 async fn update_live_presence(
272 &self,
273 _user_id: &str,
274 _local_node_id: &str,
275 _ticket_str: &str,
276 _name: &str,
277 _metadata: Option<&str>,
278 ) -> Result<()> {
279 Self::unavailable()
280 }
281
282 async fn set_live_presence_offline(&self, _user_id: &str, _local_node_id: &str) -> Result<()> {
283 Self::unavailable()
284 }
285
286 async fn update_device(
287 &self,
288 _user_id: &str,
289 _device_id: &str,
290 _device_name: Option<&str>,
291 _capabilities: Option<DeviceCapabilities>,
292 _metadata: Option<&str>,
293 ) -> Result<()> {
294 Self::unavailable()
295 }
296
297 async fn delete_device(&self, _user_id: &str, _device_id: &str) -> Result<()> {
298 Self::unavailable()
299 }
300
301 async fn set_excluded_peers(
302 &self,
303 _user_id: &str,
304 _local_node_id: &str,
305 _excluded_peers: &[String],
306 ) -> Result<()> {
307 Self::unavailable()
308 }
309
310 async fn search_devices(
311 &self,
312 _user_id: &str,
313 _exclude_node_id: Option<&str>,
314 ) -> Result<Vec<Device>> {
315 Self::unavailable()
316 }
317
318 async fn list_devices(
319 &self,
320 _user_id: &str,
321 _exclude_node_id: Option<&str>,
322 ) -> Result<Vec<Device>> {
323 Self::unavailable()
324 }
325
326 async fn send_message(
327 &self,
328 _sender_id: &str,
329 _target_id: &str,
330 _payload: &str,
331 _state: Option<&str>,
332 _reply_payload: Option<&str>,
333 ) -> Result<String> {
334 Self::unavailable()
335 }
336
337 async fn subscribe_devices(
338 &self,
339 _user_id: &str,
340 ) -> Result<BoxStream<'static, Result<Vec<DeviceEvent>>>> {
341 Self::unavailable()
342 }
343
344 async fn create_session(&self, _session: SignalingSession) -> Result<()> {
345 Self::unavailable()
346 }
347
348 async fn update_session(
349 &self,
350 _session_id: &str,
351 _update_data: serde_json::Value,
352 ) -> Result<()> {
353 Self::unavailable()
354 }
355
356 async fn subscribe_sessions(
357 &self,
358 _local_device_id: &str,
359 ) -> Result<BoxStream<'static, Result<Vec<SessionEvent>>>> {
360 Self::unavailable()
361 }
362}