tilepad_plugin_sdk/
lib.rs1use 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
14pub 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 #[arg(long)]
30 plugin_id: String,
31
32 #[arg(long)]
34 connect_url: String,
35}
36
37pub async fn start_plugin<P>(plugin: P)
38where
39 P: Plugin,
40{
41 let args = Args::parse();
43
44 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 let (ws_future, ws_rx, ws_tx) = WebSocketFuture::new(socket);
55
56 let subscriptions = Subscriptions::default();
58
59 let handle = PluginSessionHandle::new(ws_tx, subscriptions.clone());
61
62 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
75async 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
82async 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 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 tracing::subscriber::set_global_default(subscriber).expect("failed to setup tracing");
175}