Skip to main content

cdk_common/
ws.rs

1//! Websocket types and functions for the CDK.
2//!
3//! This module extends the `cashu` crate with types and functions for the CDK, using the correct
4//! expected ID types.
5use std::sync::Arc;
6
7#[cfg(feature = "mint")]
8use cashu::nut17::ws::JSON_RPC_VERSION;
9use cashu::nut17::{self};
10#[cfg(feature = "mint")]
11use cashu::quote_id::QuoteId;
12#[cfg(feature = "mint")]
13use cashu::NotificationPayload;
14
15type SubId = Arc<crate::subscription::SubId>;
16
17/// Request to unsubscribe from a websocket subscription
18pub type WsUnsubscribeRequest = nut17::ws::WsUnsubscribeRequest<SubId>;
19
20/// Notification message sent over websocket
21pub type WsNotification = nut17::ws::WsNotification<SubId>;
22
23/// Result part of a websocket response
24pub type WsResponseResult = nut17::ws::WsResponseResult<SubId>;
25
26/// Generic websocket request
27pub type WsRequest = nut17::ws::WsRequest<SubId>;
28
29/// Generic websocket response
30pub type WsResponse = nut17::ws::WsResponse<SubId>;
31
32/// Method-specific websocket request
33pub type WsMethodRequest = nut17::ws::WsMethodRequest<SubId>;
34
35/// Error body for websocket responses
36pub type WsErrorBody = nut17::ws::WsErrorBody;
37
38/// Either a websocket message or a response
39pub type WsMessageOrResponse = nut17::ws::WsMessageOrResponse<SubId>;
40
41/// Raw notification content with an undecoded JSON payload
42pub type RawNotificationInner = nut17::ws::RawNotificationInner<SubId>;
43
44/// Either a websocket message or a response with raw notification payloads
45pub type RawWsMessageOrResponse = nut17::ws::RawWsMessageOrResponse<SubId>;
46
47/// Inner content of a notification with generic payload type
48pub type NotificationInner<T> = nut17::ws::NotificationInner<T, SubId>;
49
50#[cfg(feature = "mint")]
51/// Converts a notification with UUID identifiers to a notification with string identifiers
52pub fn notification_uuid_to_notification_string(
53    notification: NotificationInner<QuoteId>,
54) -> NotificationInner<String> {
55    nut17::ws::NotificationInner {
56        sub_id: notification.sub_id,
57        payload: match notification.payload {
58            NotificationPayload::ProofState(pk) => NotificationPayload::ProofState(pk),
59            NotificationPayload::MeltQuoteBolt11Response(quote) => {
60                NotificationPayload::MeltQuoteBolt11Response(quote.to_string_id())
61            }
62            NotificationPayload::MintQuoteBolt11Response(quote) => {
63                NotificationPayload::MintQuoteBolt11Response(quote.to_string_id())
64            }
65            NotificationPayload::MintQuoteBolt12Response(quote) => {
66                NotificationPayload::MintQuoteBolt12Response(quote.to_string_id())
67            }
68            NotificationPayload::MeltQuoteBolt12Response(quote) => {
69                NotificationPayload::MeltQuoteBolt12Response(quote.to_string_id())
70            }
71            NotificationPayload::CustomMintQuoteResponse(method, quote) => {
72                NotificationPayload::CustomMintQuoteResponse(method, quote.to_string_id())
73            }
74            NotificationPayload::CustomMeltQuoteResponse(method, quote) => {
75                NotificationPayload::CustomMeltQuoteResponse(method, quote.to_string_id())
76            }
77            NotificationPayload::MeltQuoteOnchainResponse(quote) => {
78                NotificationPayload::MeltQuoteOnchainResponse(quote.to_string_id())
79            }
80            NotificationPayload::MintQuoteOnchainResponse(quote) => {
81                NotificationPayload::MintQuoteOnchainResponse(quote.to_string_id())
82            }
83        },
84    }
85}
86
87#[cfg(feature = "mint")]
88/// Converts a notification to a websocket message that can be sent to clients
89pub fn notification_to_ws_message(notification: NotificationInner<QuoteId>) -> WsMessageOrResponse {
90    nut17::ws::WsMessageOrResponse::Notification(Box::new(nut17::ws::WsNotification {
91        jsonrpc: JSON_RPC_VERSION.to_owned(),
92        method: "subscribe".to_string(),
93        params: notification_uuid_to_notification_string(notification),
94    }))
95}
96
97#[cfg(test)]
98mod tests {
99    use cashu::nut17::MAX_SUBSCRIPTION_ID_LEN;
100    use serde_json::json;
101
102    use super::*;
103
104    #[test]
105    fn websocket_requests_reject_oversized_subscription_ids() {
106        let oversized_id = "a".repeat(MAX_SUBSCRIPTION_ID_LEN + 1);
107        let subscribe = json!({
108            "jsonrpc": "2.0",
109            "method": "subscribe",
110            "params": {
111                "kind": "bolt11_mint_quote",
112                "filters": [],
113                "subId": oversized_id,
114            },
115            "id": 1,
116        });
117        assert!(serde_json::from_value::<WsRequest>(subscribe).is_err());
118
119        let unsubscribe = json!({
120            "jsonrpc": "2.0",
121            "method": "unsubscribe",
122            "params": {
123                "subId": "a".repeat(MAX_SUBSCRIPTION_ID_LEN + 1),
124            },
125            "id": 2,
126        });
127        assert!(serde_json::from_value::<WsRequest>(unsubscribe).is_err());
128    }
129}