1use 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
17pub type WsUnsubscribeRequest = nut17::ws::WsUnsubscribeRequest<SubId>;
19
20pub type WsNotification = nut17::ws::WsNotification<SubId>;
22
23pub type WsResponseResult = nut17::ws::WsResponseResult<SubId>;
25
26pub type WsRequest = nut17::ws::WsRequest<SubId>;
28
29pub type WsResponse = nut17::ws::WsResponse<SubId>;
31
32pub type WsMethodRequest = nut17::ws::WsMethodRequest<SubId>;
34
35pub type WsErrorBody = nut17::ws::WsErrorBody;
37
38pub type WsMessageOrResponse = nut17::ws::WsMessageOrResponse<SubId>;
40
41pub type RawNotificationInner = nut17::ws::RawNotificationInner<SubId>;
43
44pub type RawWsMessageOrResponse = nut17::ws::RawWsMessageOrResponse<SubId>;
46
47pub type NotificationInner<T> = nut17::ws::NotificationInner<T, SubId>;
49
50#[cfg(feature = "mint")]
51pub 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")]
88pub 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}