Skip to main content

rmux_sdk/web_share/
handle.rs

1use rmux_proto::{PaneTargetRef, WebShareScope};
2
3use crate::handles::Rmux;
4use crate::transport::TransportClient;
5use crate::Result;
6
7use super::{
8    list_web_shares, lookup_summary, stop_all_web_shares, stop_web_share, token_from_url,
9    web_config, WebConfigInfo, WebShareSummary,
10};
11
12/// A share handle returned by a create operation.
13///
14/// Dropping this handle does not stop the daemon-side share. The share remains
15/// active until its TTL expires, the shared pane or session goes away, or
16/// [`Self::stop`] is called explicitly.
17///
18/// Cloned handles point at the same daemon share. Stopping one clone invalidates
19/// the share for every other clone.
20#[derive(Clone)]
21pub struct WebShareHandle {
22    transport: TransportClient,
23    id: String,
24    scope: WebShareScope,
25    spectator_url: Option<String>,
26    operator_url: Option<String>,
27    expires_at_unix: Option<u64>,
28    operator_pairing_code: Option<String>,
29    spectator_pairing_code: Option<String>,
30    max_operators: Option<u16>,
31    max_spectators: Option<u16>,
32    operator: bool,
33    spectator: bool,
34    kill_session_on_expire: bool,
35}
36
37impl WebShareHandle {
38    pub(crate) fn new(
39        transport: TransportClient,
40        created: rmux_proto::WebShareCreatedResponse,
41    ) -> Self {
42        Self {
43            transport,
44            id: created.share_id,
45            scope: created.scope,
46            spectator_url: created.spectator_url,
47            operator_url: created.operator_url,
48            expires_at_unix: created.expires_at_unix,
49            operator_pairing_code: created.operator_pairing_code,
50            spectator_pairing_code: created.spectator_pairing_code,
51            max_operators: created.max_operators,
52            max_spectators: created.max_spectators,
53            operator: created.operator,
54            spectator: created.spectator,
55            kill_session_on_expire: created.kill_session_on_expire,
56        }
57    }
58
59    /// Returns the opaque share id.
60    #[must_use]
61    pub fn id(&self) -> &str {
62        &self.id
63    }
64
65    /// Returns the pane or session scope resolved by the daemon at create time.
66    #[must_use]
67    pub const fn scope(&self) -> &WebShareScope {
68        &self.scope
69    }
70
71    /// Returns the pane target when this is a single-pane share.
72    #[must_use]
73    pub fn pane_target(&self) -> Option<&PaneTargetRef> {
74        match &self.scope {
75            WebShareScope::Pane(target) => Some(target),
76            WebShareScope::Session(_) => None,
77        }
78    }
79
80    /// Returns whether this share minted an operator URL.
81    #[must_use]
82    pub const fn operator(&self) -> bool {
83        self.operator
84    }
85
86    /// Returns whether this share minted a spectator URL.
87    #[must_use]
88    pub const fn spectator(&self) -> bool {
89        self.spectator
90    }
91
92    /// Returns whether this share kills its target session on expiry.
93    #[must_use]
94    pub const fn kill_session_on_expire(&self) -> bool {
95        self.kill_session_on_expire
96    }
97
98    /// Returns the spectator browser URL.
99    #[must_use]
100    pub fn spectator_url(&self) -> Option<&str> {
101        self.spectator_url.as_deref()
102    }
103
104    /// Returns the spectator capability token carried in the browser URL, when present.
105    #[must_use]
106    pub fn spectator_token(&self) -> Option<&str> {
107        self.spectator_url.as_deref().and_then(token_from_url)
108    }
109
110    /// Returns the privileged operator URL, when this share has an operator.
111    #[must_use]
112    pub fn operator_url(&self) -> Option<&str> {
113        self.operator_url.as_deref()
114    }
115
116    /// Returns the operator capability token carried in the operator URL, when present.
117    #[must_use]
118    pub fn operator_token(&self) -> Option<&str> {
119        self.operator_url.as_deref().and_then(token_from_url)
120    }
121
122    /// Returns the out-of-band operator pairing code required by this share.
123    #[must_use]
124    pub fn operator_pairing_code(&self) -> Option<&str> {
125        self.operator_pairing_code.as_deref()
126    }
127
128    /// Returns the out-of-band spectator pairing code required by this share.
129    #[must_use]
130    pub fn spectator_pairing_code(&self) -> Option<&str> {
131        self.spectator_pairing_code.as_deref()
132    }
133
134    /// Returns the effective cap for concurrent spectator clients, when capped.
135    #[must_use]
136    pub const fn max_spectators(&self) -> Option<u16> {
137        self.max_spectators
138    }
139
140    /// Returns the effective cap for concurrent operator clients, when capped.
141    #[must_use]
142    pub const fn max_operators(&self) -> Option<u16> {
143        self.max_operators
144    }
145
146    /// Returns the expiration timestamp in UNIX seconds.
147    #[must_use]
148    pub const fn expires_at_unix(&self) -> Option<u64> {
149        self.expires_at_unix
150    }
151
152    /// Fetches redacted live metadata for this share.
153    pub async fn summary(&self) -> Result<WebShareSummary> {
154        lookup_summary(&self.transport, &self.id).await
155    }
156
157    /// Returns the current number of spectator clients.
158    pub async fn spectators_active(&self) -> Result<u16> {
159        Ok(self.summary().await?.active_spectators)
160    }
161
162    /// Returns the current number of operator clients.
163    pub async fn operators_active(&self) -> Result<u16> {
164        Ok(self.summary().await?.active_operators)
165    }
166
167    /// Stops this share on the daemon.
168    pub async fn stop(self) -> Result<()> {
169        stop_web_share(&self.transport, &self.id).await.map(|_| ())
170    }
171}
172
173/// Lookup handle for a share that may not have been created by this client.
174#[derive(Clone)]
175pub struct WebShareLookup {
176    transport: TransportClient,
177    summary: WebShareSummary,
178}
179
180impl WebShareLookup {
181    pub(crate) fn new(transport: TransportClient, summary: WebShareSummary) -> Self {
182        Self { transport, summary }
183    }
184
185    /// Returns the opaque share id.
186    #[must_use]
187    pub fn id(&self) -> &str {
188        &self.summary.id
189    }
190
191    /// Returns the pane or session scope resolved by the daemon at create time.
192    #[must_use]
193    pub const fn scope(&self) -> &WebShareScope {
194        &self.summary.scope
195    }
196
197    /// Returns the pane target when this is a single-pane share.
198    #[must_use]
199    pub fn pane_target(&self) -> Option<&PaneTargetRef> {
200        self.summary.pane_target()
201    }
202
203    /// Returns whether this share has an operator URL.
204    #[must_use]
205    pub const fn operator(&self) -> bool {
206        self.summary.operator
207    }
208
209    /// Returns whether this share has a spectator URL.
210    #[must_use]
211    pub const fn spectator(&self) -> bool {
212        self.summary.spectator
213    }
214
215    /// Returns the redacted spectator URL, when available.
216    #[must_use]
217    pub fn spectator_url_redacted(&self) -> Option<&str> {
218        self.summary.spectator_url_redacted.as_deref()
219    }
220
221    /// Returns the cached summary from the lookup response.
222    #[must_use]
223    pub const fn cached_summary(&self) -> &WebShareSummary {
224        &self.summary
225    }
226
227    /// Fetches fresh redacted metadata for this share.
228    pub async fn summary(&self) -> Result<WebShareSummary> {
229        lookup_summary(&self.transport, &self.summary.id).await
230    }
231
232    /// Stops this share on the daemon.
233    pub async fn stop(self) -> Result<()> {
234        stop_web_share(&self.transport, &self.summary.id)
235            .await
236            .map(|_| ())
237    }
238}
239
240impl Rmux {
241    /// Lists active web shares.
242    pub async fn list_web_shares(&self) -> Result<Vec<WebShareSummary>> {
243        let transport = self
244            .connect_transport_for_operation(self.resolved_timeout(None))
245            .await?;
246        list_web_shares(&transport).await
247    }
248
249    /// Stops one web share by id and returns whether it existed.
250    pub async fn stop_web_share(&self, id: &str) -> Result<bool> {
251        let transport = self
252            .connect_transport_for_operation(self.resolved_timeout(None))
253            .await?;
254        stop_web_share(&transport, id).await
255    }
256
257    /// Stops every active web share and returns the number stopped.
258    pub async fn stop_all_web_shares(&self) -> Result<usize> {
259        let transport = self
260            .connect_transport_for_operation(self.resolved_timeout(None))
261            .await?;
262        stop_all_web_shares(&transport).await
263    }
264
265    /// Looks up one web share without exposing access keys.
266    pub async fn web_share_by_id(&self, id: &str) -> Result<WebShareLookup> {
267        let transport = self
268            .connect_transport_for_operation(self.resolved_timeout(None))
269            .await?;
270        let summary = lookup_summary(&transport, id).await?;
271        Ok(WebShareLookup::new(transport, summary))
272    }
273
274    /// Returns the active daemon web-share listener configuration.
275    pub async fn web_config(&self) -> Result<WebConfigInfo> {
276        let transport = self
277            .connect_transport_for_operation(self.resolved_timeout(None))
278            .await?;
279        web_config(&transport).await
280    }
281}