tilepad_plugin_sdk/
lib.rs

1use clap::Parser;
2use futures_util::StreamExt;
3use inspector::Inspector;
4use plugin::Plugin;
5use protocol::ServerPluginMessage;
6use session::{PluginSessionHandle, PluginSessionRx};
7use subscription::Subscriptions;
8use tokio::join;
9use tokio_tungstenite::{connect_async, tungstenite::client::IntoClientRequest};
10
11use tracing_subscriber::EnvFilter;
12use ws::WebSocketFuture;
13
14// Provide tracing modules to the implementor
15pub use tracing;
16pub use tracing_subscriber;
17
18pub mod inspector;
19pub mod plugin;
20pub mod protocol;
21pub mod session;
22mod subscription;
23mod ws;
24
25#[derive(Parser, Debug)]
26#[command(version, about, long_about = None)]
27struct Args {
28    /// ID of the plugin to connect as
29    #[arg(long)]
30    plugin_id: String,
31
32    /// Plugin server connection host URL
33    #[arg(long)]
34    connect_url: String,
35}
36
37pub async fn start_plugin<P>(plugin: P)
38where
39    P: Plugin,
40{
41    // Accept the command line arguments
42    let args = Args::parse();
43
44    // Connect to the server socket
45    let client_request = args
46        .connect_url
47        .into_client_request()
48        .expect("failed to create client request");
49    let (socket, _response) = connect_async(client_request)
50        .await
51        .expect("failed to connect to plugin server");
52
53    // Create and spawn a future for the websocket
54    let (ws_future, ws_rx, ws_tx) = WebSocketFuture::new(socket);
55
56    // Create message subscriptions store
57    let subscriptions = Subscriptions::default();
58
59    // Wrap the websocket handle with the custom protocol
60    let handle = PluginSessionHandle::new(ws_tx, subscriptions.clone());
61
62    // Send registration message
63    handle
64        .register(args.plugin_id)
65        .expect("failed to register plugin");
66
67    let msg_rx = PluginSessionRx::new(ws_rx);
68
69    let socket_future = run_websocket(ws_future);
70    let handle_future = run_handler(plugin, handle, subscriptions, msg_rx);
71
72    join!(socket_future, handle_future);
73}
74
75/// Helper to run the websocket and emit a log in the case of error
76async fn run_websocket(ws_future: WebSocketFuture) {
77    if let Err(cause) = ws_future.await {
78        tracing::error!(?cause, "error running device websocket future");
79    }
80}
81
82/// Handle all incoming messages from the websocket
83async fn run_handler<P>(
84    mut plugin: P,
85    handle: PluginSessionHandle,
86    subscriptions: Subscriptions,
87    mut msg_rx: PluginSessionRx,
88) where
89    P: Plugin,
90{
91    while let Some(msg) = msg_rx.next().await {
92        let msg = match msg {
93            Ok(value) => value,
94            Err(cause) => {
95                tracing::error!(?cause, "error processing server message");
96                return;
97            }
98        };
99
100        // Handle subscriptions
101        subscriptions.apply(&msg);
102
103        match msg {
104            ServerPluginMessage::Registered { .. } => {
105                handle
106                    .request_properties()
107                    .expect("failed to request initial properties");
108
109                plugin.on_registered(&handle);
110            }
111            ServerPluginMessage::Properties { properties } => {
112                plugin.on_properties(&handle, properties);
113            }
114            ServerPluginMessage::TileClicked { ctx, properties } => {
115                plugin.on_tile_clicked(&handle, ctx, properties);
116            }
117            ServerPluginMessage::RecvFromInspector { ctx, message } => {
118                plugin.on_inspector_message(
119                    &handle,
120                    Inspector {
121                        ctx,
122                        session: handle.clone(),
123                    },
124                    message,
125                );
126            }
127            ServerPluginMessage::InspectorOpen { ctx } => {
128                plugin.on_inspector_open(
129                    &handle,
130                    Inspector {
131                        ctx,
132                        session: handle.clone(),
133                    },
134                );
135            }
136            ServerPluginMessage::InspectorClose { ctx } => {
137                plugin.on_inspector_close(
138                    &handle,
139                    Inspector {
140                        ctx,
141                        session: handle.clone(),
142                    },
143                );
144            }
145            ServerPluginMessage::DeepLink { ctx } => {
146                plugin.on_deep_link(&handle, ctx);
147            }
148            ServerPluginMessage::TileProperties {
149                tile_id,
150                properties,
151            } => {
152                plugin.on_tile_properties(&handle, tile_id, properties);
153            }
154        }
155    }
156
157    subscriptions.clear();
158}
159
160pub fn setup_tracing() {
161    let filter = EnvFilter::from_default_env();
162    let subscriber = tracing_subscriber::fmt()
163        .compact()
164        .with_file(true)
165        .with_env_filter(filter)
166        .with_line_number(true)
167        .with_thread_ids(false)
168        .with_target(false)
169        .with_ansi(false)
170        .without_time()
171        .finish();
172
173    // use that subscriber to process traces emitted after this point
174    tracing::subscriber::set_global_default(subscriber).expect("failed to setup tracing");
175}