Skip to content

Commit 7b6a0a3

Browse files
committed
browser-signer-proxy: support for automatic shutdown
Pull-Request: #1005 Signed-off-by: Yuki Kishimoto <[email protected]>
1 parent 1cc5f62 commit 7b6a0a3

File tree

4 files changed

+62
-18
lines changed

4 files changed

+62
-18
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

signer/nostr-browser-signer-proxy/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ rust-version.workspace = true
1212
keywords = ["nostr", "nip07", "browser", "signer", "proxy"]
1313

1414
[dependencies]
15+
atomic-destructor.workspace = true
1516
bytes = "1.10"
1617
http-body-util = "0.1"
1718
hyper = { version = "1.6", features = ["server", "http1"] }

signer/nostr-browser-signer-proxy/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ pub enum Error {
2727
Generic(String),
2828
/// Timeout
2929
Timeout,
30+
/// The server is shutdown
31+
Shutdown,
3032
}
3133

3234
impl std::error::Error for Error {}
@@ -41,6 +43,7 @@ impl fmt::Display for Error {
4143
Self::OneShotRecv(e) => write!(f, "{e}"),
4244
Self::Generic(e) => write!(f, "{e}"),
4345
Self::Timeout => write!(f, "timeout"),
46+
Self::Shutdown => write!(f, "server is shutdown"),
4447
}
4548
}
4649
}

signer/nostr-browser-signer-proxy/src/lib.rs

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313

1414
use std::collections::HashMap;
1515
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
16+
use std::sync::atomic::{AtomicBool, Ordering};
1617
use std::sync::Arc;
1718
use std::time::Duration;
1819

20+
use atomic_destructor::{AtomicDestroyer, AtomicDestructor};
1921
use bytes::Bytes;
2022
use http_body_util::combinators::BoxBody;
2123
use http_body_util::{BodyExt, Full};
@@ -165,11 +167,8 @@ pub struct BrowserSignerProxyOptions {
165167
pub addr: SocketAddr,
166168
}
167169

168-
/// Nostr Browser Signer Proxy
169-
///
170-
/// Proxy to use Nostr Browser signer (NIP-07) in native applications.
171170
#[derive(Debug, Clone)]
172-
pub struct BrowserSignerProxy {
171+
struct InnerBrowserSignerProxy {
173172
/// Configuration options for the proxy
174173
options: BrowserSignerProxyOptions,
175174
/// Internal state of the proxy including request queues
@@ -178,6 +177,38 @@ pub struct BrowserSignerProxy {
178177
handle: OnceCell<Arc<JoinHandle<()>>>,
179178
/// Notification trigger for graceful shutdown
180179
shutdown: Arc<Notify>,
180+
/// Flag to indicate if the server is shutdown
181+
is_shutdown: Arc<AtomicBool>,
182+
}
183+
184+
impl AtomicDestroyer for InnerBrowserSignerProxy {
185+
fn on_destroy(&self) {
186+
self.shutdown();
187+
}
188+
}
189+
190+
impl InnerBrowserSignerProxy {
191+
#[inline]
192+
fn is_shutdown(&self) -> bool {
193+
self.is_shutdown.load(Ordering::SeqCst)
194+
}
195+
196+
fn shutdown(&self) {
197+
// Mark the server as shutdown
198+
self.is_shutdown.store(true, Ordering::SeqCst);
199+
200+
// Notify all waiters that the proxy is shutting down
201+
self.shutdown.notify_one();
202+
self.shutdown.notify_waiters();
203+
}
204+
}
205+
206+
/// Nostr Browser Signer Proxy
207+
///
208+
/// Proxy to use Nostr Browser signer (NIP-07) in native applications.
209+
#[derive(Debug, Clone)]
210+
pub struct BrowserSignerProxy {
211+
inner: AtomicDestructor<InnerBrowserSignerProxy>,
181212
}
182213

183214
impl Default for BrowserSignerProxyOptions {
@@ -210,8 +241,6 @@ impl BrowserSignerProxyOptions {
210241
}
211242
}
212243

213-
// TODO: use atomic-destructor to automatically shutdown this when all instances are dropped
214-
215244
impl BrowserSignerProxy {
216245
/// Construct a new browser signer proxy
217246
pub fn new(options: BrowserSignerProxyOptions) -> Self {
@@ -221,32 +250,36 @@ impl BrowserSignerProxy {
221250
};
222251

223252
Self {
224-
options,
225-
state: Arc::new(state),
226-
handle: OnceCell::new(),
227-
shutdown: Arc::new(Notify::new()),
253+
inner: AtomicDestructor::new(InnerBrowserSignerProxy {
254+
options,
255+
state: Arc::new(state),
256+
handle: OnceCell::new(),
257+
shutdown: Arc::new(Notify::new()),
258+
is_shutdown: Arc::new(AtomicBool::new(false)),
259+
}),
228260
}
229261
}
230262

231263
/// Get the signer proxy webpage URL
232264
#[inline]
233265
pub fn url(&self) -> String {
234-
format!("http://{}", self.options.addr)
266+
format!("http://{}", self.inner.options.addr)
235267
}
236268

237269
/// Start the proxy
238270
///
239271
/// If this is not called, will be automatically started on the first interaction with the signer.
240272
pub async fn start(&self) -> Result<(), Error> {
241273
let _handle: &Arc<JoinHandle<()>> = self
274+
.inner
242275
.handle
243276
.get_or_try_init(|| async {
244-
let listener = TcpListener::bind(self.options.addr).await?;
277+
let listener = TcpListener::bind(self.inner.options.addr).await?;
245278

246-
tracing::info!("Starting proxy server on {}", self.options.addr);
279+
tracing::info!("Starting proxy server on {}", self.inner.options.addr);
247280

248-
let state = self.state.clone();
249-
let shutdown = self.shutdown.clone();
281+
let state = self.inner.state.clone();
282+
let shutdown = self.inner.shutdown.clone();
250283

251284
let handle: JoinHandle<()> = tokio::spawn(async move {
252285
loop {
@@ -293,20 +326,26 @@ impl BrowserSignerProxy {
293326

294327
#[inline]
295328
async fn store_pending_response(&self, id: Uuid, tx: Sender<Result<Value, String>>) {
296-
let mut pending_responses = self.state.pending_responses.lock().await;
329+
let mut pending_responses = self.inner.state.pending_responses.lock().await;
297330
pending_responses.insert(id, tx);
298331
}
299332

300333
#[inline]
301334
async fn store_outgoing_request(&self, request: RequestData) {
302-
let mut outgoing_requests = self.state.outgoing_requests.lock().await;
335+
let mut outgoing_requests = self.inner.state.outgoing_requests.lock().await;
303336
outgoing_requests.push(request);
304337
}
305338

306339
async fn request<T>(&self, method: RequestMethod, params: Value) -> Result<T, Error>
307340
where
308341
T: DeserializeOwned,
309342
{
343+
// Ensure is not shutdown
344+
if self.inner.is_shutdown() {
345+
return Err(Error::Shutdown);
346+
}
347+
348+
// Start the proxy if not already started
310349
self.start().await?;
311350

312351
// Construct the request
@@ -322,7 +361,7 @@ impl BrowserSignerProxy {
322361
self.store_outgoing_request(request).await;
323362

324363
// Wait for response
325-
match time::timeout(self.options.timeout, rx)
364+
match time::timeout(self.inner.options.timeout, rx)
326365
.await
327366
.map_err(|_| Error::Timeout)??
328367
{

0 commit comments

Comments
 (0)