From 0cfe15271fdfb6f6e0f0c05883172dcbd5f00bb4 Mon Sep 17 00:00:00 2001 From: massimoalbarello Date: Wed, 10 Jan 2024 18:51:50 +0100 Subject: [PATCH 1/9] pub method to instantiate CanisterWsOpenArguments --- src/ic-websocket-cdk/src/types.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ic-websocket-cdk/src/types.rs b/src/ic-websocket-cdk/src/types.rs index 9d563be..09fd673 100644 --- a/src/ic-websocket-cdk/src/types.rs +++ b/src/ic-websocket-cdk/src/types.rs @@ -55,6 +55,14 @@ pub struct CanisterWsOpenArguments { pub(crate) gateway_principal: GatewayPrincipal, } +impl CanisterWsOpenArguments { + pub fn new(client_nonce: u64, gateway_principal: GatewayPrincipal) -> Self { + Self { + client_nonce, + gateway_principal, + } + } +} /// The arguments for [ws_close](crate::ws_close). #[derive(CandidType, Clone, Deserialize, Serialize, Eq, PartialEq, Debug)] pub struct CanisterWsCloseArguments { From eaa027060906264cae018461abb7e711192d01cc Mon Sep 17 00:00:00 2001 From: Luca8991 Date: Thu, 11 Jan 2024 12:45:27 +0100 Subject: [PATCH 2/9] feat: make ClientKey public --- .../src/tests/integration_tests/a_ws_open.rs | 8 ++++---- src/ic-websocket-cdk/src/types.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ic-websocket-cdk/src/tests/integration_tests/a_ws_open.rs b/src/ic-websocket-cdk/src/tests/integration_tests/a_ws_open.rs index 6ef926d..d03e611 100644 --- a/src/ic-websocket-cdk/src/tests/integration_tests/a_ws_open.rs +++ b/src/ic-websocket-cdk/src/tests/integration_tests/a_ws_open.rs @@ -82,10 +82,10 @@ proptest! { #[test] fn test_4_should_open_a_connection_for_the_same_client_with_a_different_nonce(test_client_nonce in any::().prop_map(|_| generate_random_client_nonce())) { - let client_key = ClientKey { - client_principal: CLIENT_1_KEY.deref().client_principal, - client_nonce: test_client_nonce, - }; + let client_key = ClientKey::new( + CLIENT_1_KEY.deref().client_principal, + test_client_nonce + ); let args = CanisterWsOpenArguments { client_nonce: client_key.client_nonce, gateway_principal: GATEWAY_1.deref().to_owned(), diff --git a/src/ic-websocket-cdk/src/types.rs b/src/ic-websocket-cdk/src/types.rs index 09fd673..86b8bd9 100644 --- a/src/ic-websocket-cdk/src/types.rs +++ b/src/ic-websocket-cdk/src/types.rs @@ -12,14 +12,14 @@ use crate::{ pub type ClientPrincipal = Principal; #[derive(CandidType, Clone, Deserialize, Serialize, Eq, PartialEq, Debug, Hash)] -pub(crate) struct ClientKey { +pub struct ClientKey { pub(crate) client_principal: ClientPrincipal, pub(crate) client_nonce: u64, } impl ClientKey { /// Creates a new instance of ClientKey. - pub(crate) fn new(client_principal: ClientPrincipal, client_nonce: u64) -> Self { + pub fn new(client_principal: ClientPrincipal, client_nonce: u64) -> Self { Self { client_principal, client_nonce, From e163571c4963814e0526077b2f382421658b85b3 Mon Sep 17 00:00:00 2001 From: Luca8991 Date: Thu, 11 Jan 2024 12:47:17 +0100 Subject: [PATCH 3/9] fix: export ClientKey --- src/ic-websocket-cdk/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ic-websocket-cdk/src/lib.rs b/src/ic-websocket-cdk/src/lib.rs index 2857c09..b2ae09d 100644 --- a/src/ic-websocket-cdk/src/lib.rs +++ b/src/ic-websocket-cdk/src/lib.rs @@ -19,8 +19,9 @@ use types::*; pub use types::{ CanisterCloseResult, CanisterSendResult, CanisterWsCloseArguments, CanisterWsCloseResult, CanisterWsGetMessagesArguments, CanisterWsGetMessagesResult, CanisterWsMessageArguments, - CanisterWsMessageResult, CanisterWsOpenArguments, CanisterWsOpenResult, ClientPrincipal, - OnCloseCallbackArgs, OnMessageCallbackArgs, OnOpenCallbackArgs, WsHandlers, WsInitParams, + CanisterWsMessageResult, CanisterWsOpenArguments, CanisterWsOpenResult, ClientKey, + ClientPrincipal, OnCloseCallbackArgs, OnMessageCallbackArgs, OnOpenCallbackArgs, WsHandlers, + WsInitParams, }; /// The label used when constructing the certification tree. From a231afdc1a2ff74bb7716a377d0663407aeb767b Mon Sep 17 00:00:00 2001 From: Luca8991 Date: Thu, 11 Jan 2024 12:51:36 +0100 Subject: [PATCH 4/9] feat: make ClientKey fields public --- src/ic-websocket-cdk/src/types.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ic-websocket-cdk/src/types.rs b/src/ic-websocket-cdk/src/types.rs index 86b8bd9..2dcb72e 100644 --- a/src/ic-websocket-cdk/src/types.rs +++ b/src/ic-websocket-cdk/src/types.rs @@ -13,8 +13,8 @@ use crate::{ pub type ClientPrincipal = Principal; #[derive(CandidType, Clone, Deserialize, Serialize, Eq, PartialEq, Debug, Hash)] pub struct ClientKey { - pub(crate) client_principal: ClientPrincipal, - pub(crate) client_nonce: u64, + pub client_principal: ClientPrincipal, + pub client_nonce: u64, } impl ClientKey { From 562027d5907025944dc06a813489e193a2cb58c5 Mon Sep 17 00:00:00 2001 From: Luca8991 Date: Thu, 11 Jan 2024 13:19:29 +0100 Subject: [PATCH 5/9] feat: more public structs and new() methods --- src/ic-websocket-cdk/src/lib.rs | 7 +- .../src/tests/integration_tests/a_ws_open.rs | 8 +- .../tests/integration_tests/b_ws_message.rs | 91 +++++++++---------- .../integration_tests/c_ws_get_messages.rs | 50 +++------- .../src/tests/integration_tests/d_ws_close.rs | 20 +--- .../g_messages_acknowledgement.rs | 68 +++++--------- .../integration_tests/h_multiple_gateways.rs | 12 +-- .../tests/integration_tests/utils/messages.rs | 12 +-- .../src/tests/unit_tests/mod.rs | 14 +-- src/ic-websocket-cdk/src/types.rs | 80 ++++++++++++---- 10 files changed, 173 insertions(+), 189 deletions(-) diff --git a/src/ic-websocket-cdk/src/lib.rs b/src/ic-websocket-cdk/src/lib.rs index b2ae09d..7540b2e 100644 --- a/src/ic-websocket-cdk/src/lib.rs +++ b/src/ic-websocket-cdk/src/lib.rs @@ -17,11 +17,12 @@ use timers::*; pub use types::CanisterWsSendResult; use types::*; pub use types::{ - CanisterCloseResult, CanisterSendResult, CanisterWsCloseArguments, CanisterWsCloseResult, + CanisterCloseResult, CanisterOutputCertifiedMessages, CanisterOutputMessage, + CanisterSendResult, CanisterWsCloseArguments, CanisterWsCloseResult, CanisterWsGetMessagesArguments, CanisterWsGetMessagesResult, CanisterWsMessageArguments, CanisterWsMessageResult, CanisterWsOpenArguments, CanisterWsOpenResult, ClientKey, - ClientPrincipal, OnCloseCallbackArgs, OnMessageCallbackArgs, OnOpenCallbackArgs, WsHandlers, - WsInitParams, + ClientPrincipal, OnCloseCallbackArgs, OnMessageCallbackArgs, OnOpenCallbackArgs, + WebsocketMessage, WsHandlers, WsInitParams, }; /// The label used when constructing the certification tree. diff --git a/src/ic-websocket-cdk/src/tests/integration_tests/a_ws_open.rs b/src/ic-websocket-cdk/src/tests/integration_tests/a_ws_open.rs index d03e611..556697f 100644 --- a/src/ic-websocket-cdk/src/tests/integration_tests/a_ws_open.rs +++ b/src/ic-websocket-cdk/src/tests/integration_tests/a_ws_open.rs @@ -38,10 +38,8 @@ fn test_2_should_open_a_connection() { let res = call_ws_open(&client_1_key.client_principal, args); assert_eq!(res, CanisterWsOpenResult::Ok(())); - let CanisterOutputCertifiedMessages { messages, .. } = call_ws_get_messages_with_panic( - GATEWAY_1.deref(), - CanisterWsGetMessagesArguments { nonce: 0 }, - ); + let CanisterOutputCertifiedMessages { messages, .. } = + call_ws_get_messages_with_panic(GATEWAY_1.deref(), CanisterWsGetMessagesArguments::new(0)); let first_message = &messages[0]; assert_eq!(first_message.client_key, *client_1_key); @@ -95,7 +93,7 @@ proptest! { let CanisterOutputCertifiedMessages { messages, .. } = call_ws_get_messages_with_panic( GATEWAY_1.deref(), - CanisterWsGetMessagesArguments { nonce: 0 }, + CanisterWsGetMessagesArguments::new(0), ); let service_message_for_client = messages diff --git a/src/ic-websocket-cdk/src/tests/integration_tests/b_ws_message.rs b/src/ic-websocket-cdk/src/tests/integration_tests/b_ws_message.rs index 40d73c6..430d520 100644 --- a/src/ic-websocket-cdk/src/tests/integration_tests/b_ws_message.rs +++ b/src/ic-websocket-cdk/src/tests/integration_tests/b_ws_message.rs @@ -37,9 +37,7 @@ fn test_1_fails_if_client_is_not_registered() { let client_2_key = CLIENT_2_KEY.deref(); let res = call_ws_message( &client_2_key.client_principal, - CanisterWsMessageArguments { - msg: create_websocket_message(client_2_key, 0, None, false), - }, + CanisterWsMessageArguments::new(create_websocket_message(client_2_key, 0, None, false)), ); assert_eq!( @@ -69,9 +67,12 @@ fn test_2_fails_if_client_sends_a_message_with_a_different_client_key() { // first, send a message with a different principal let res = call_ws_message( &client_1_key.client_principal, - CanisterWsMessageArguments { - msg: create_websocket_message(&wrong_client_key, 0, None, false), - }, + CanisterWsMessageArguments::new(create_websocket_message( + &wrong_client_key, + 0, + None, + false, + )), ); assert_eq!( res, @@ -91,9 +92,12 @@ fn test_2_fails_if_client_sends_a_message_with_a_different_client_key() { // then, send a message with a different nonce let res = call_ws_message( &client_1_key.client_principal, - CanisterWsMessageArguments { - msg: create_websocket_message(&wrong_client_key, 0, None, false), - }, + CanisterWsMessageArguments::new(create_websocket_message( + &wrong_client_key, + 0, + None, + false, + )), ); assert_eq!( res, @@ -116,9 +120,7 @@ fn test_3_should_send_a_message_from_a_registered_client() { let res = call_ws_message( &client_1_key.client_principal, - CanisterWsMessageArguments { - msg: create_websocket_message(client_1_key, 1, None, false), - }, + CanisterWsMessageArguments::new(create_websocket_message(client_1_key, 1, None, false)), ); assert_eq!(res, CanisterWsMessageResult::Ok(())); } @@ -135,9 +137,12 @@ fn test_4_fails_if_client_sends_a_message_with_a_wrong_sequence_number() { let expected_sequence_number = 1; // the next valid sequence number let res = call_ws_message( &client_1_key.client_principal, - CanisterWsMessageArguments { - msg: create_websocket_message(client_1_key, wrong_sequence_number, None, false), - }, + CanisterWsMessageArguments::new(create_websocket_message( + client_1_key, + wrong_sequence_number, + None, + false, + )), ); assert_eq!( res, @@ -153,7 +158,7 @@ fn test_4_fails_if_client_sends_a_message_with_a_wrong_sequence_number() { // check if the gateway put the close message in the queue let msgs = call_ws_get_messages_with_panic( GATEWAY_1.deref(), - CanisterWsGetMessagesArguments { nonce: 1 }, // skip the first open message + CanisterWsGetMessagesArguments::new(1), // skip the first open message ); check_canister_message_has_close_reason( &msgs.messages[0], @@ -164,9 +169,7 @@ fn test_4_fails_if_client_sends_a_message_with_a_wrong_sequence_number() { // so calling the ws_close endpoint should return the ClientKeyNotConnected error let res = call_ws_close( GATEWAY_1.deref(), - CanisterWsCloseArguments { - client_key: client_1_key.clone(), - }, + CanisterWsCloseArguments::new(client_1_key.clone()), ); assert_eq!( res, @@ -190,14 +193,12 @@ fn test_5_fails_if_client_sends_a_wrong_service_message() { // fail with wrong content encoding let res = call_ws_message( &client_1_key.client_principal, - CanisterWsMessageArguments { - msg: create_websocket_message( - client_1_key, - 1, - Some(encode_one(vec![1, 2, 3]).unwrap()), - true, - ), - }, + CanisterWsMessageArguments::new(create_websocket_message( + client_1_key, + 1, + Some(encode_one(vec![1, 2, 3]).unwrap()), + true, + )), ); assert!(res .err() @@ -211,16 +212,14 @@ fn test_5_fails_if_client_sends_a_wrong_service_message() { }); let res = call_ws_message( &client_1_key.client_principal, - CanisterWsMessageArguments { - msg: create_websocket_message( - client_1_key, - 2, - Some(encode_websocket_service_message_content( - &wrong_service_message, - )), - true, - ), - }, + CanisterWsMessageArguments::new(create_websocket_message( + client_1_key, + 2, + Some(encode_websocket_service_message_content( + &wrong_service_message, + )), + true, + )), ); assert_eq!( res, @@ -242,16 +241,14 @@ fn test_6_should_send_a_service_message_from_a_registered_client() { }); let res = call_ws_message( &client_1_key.client_principal, - CanisterWsMessageArguments { - msg: create_websocket_message( - client_1_key, - 1, - Some(encode_websocket_service_message_content( - &client_service_message, - )), - true, - ), - }, + CanisterWsMessageArguments::new(create_websocket_message( + client_1_key, + 1, + Some(encode_websocket_service_message_content( + &client_service_message, + )), + true, + )), ); assert_eq!(res, CanisterWsMessageResult::Ok(())); } diff --git a/src/ic-websocket-cdk/src/tests/integration_tests/c_ws_get_messages.rs b/src/ic-websocket-cdk/src/tests/integration_tests/c_ws_get_messages.rs index baa2693..52eef8b 100644 --- a/src/ic-websocket-cdk/src/tests/integration_tests/c_ws_get_messages.rs +++ b/src/ic-websocket-cdk/src/tests/integration_tests/c_ws_get_messages.rs @@ -30,16 +30,11 @@ proptest! { let res = call_ws_get_messages_with_panic( test_gateway_principal, - CanisterWsGetMessagesArguments { nonce: 0 }, + CanisterWsGetMessagesArguments::new(0), ); assert_eq!( res, - CanisterOutputCertifiedMessages { - messages: vec![], - cert: vec![], - tree: vec![], - is_end_of_queue: true, - }, + CanisterOutputCertifiedMessages::empty() ); } @@ -53,7 +48,7 @@ proptest! { let CanisterOutputCertifiedMessages { messages, .. } = call_ws_get_messages_with_panic( GATEWAY_1.deref(), - CanisterWsGetMessagesArguments { nonce: 0 }, + CanisterWsGetMessagesArguments::new(0), ); prop_assert_eq!(messages.len(), 1); // we expect only the service message } @@ -85,7 +80,7 @@ proptest! { .. } = call_ws_get_messages_with_panic( GATEWAY_1.deref(), - CanisterWsGetMessagesArguments { nonce: i }, + CanisterWsGetMessagesArguments::new(i), ); prop_assert_eq!( messages.len() as u64, @@ -104,18 +99,11 @@ proptest! { // try to get more messages than available let res = call_ws_get_messages_with_panic( GATEWAY_1.deref(), - CanisterWsGetMessagesArguments { - nonce: messages_count, - }, + CanisterWsGetMessagesArguments::new(messages_count), ); prop_assert_eq!( res, - CanisterOutputCertifiedMessages { - messages: vec![], - cert: vec![], - tree: vec![], - is_end_of_queue: true, - } + CanisterOutputCertifiedMessages::empty() ); } @@ -146,9 +134,7 @@ proptest! { .. } = call_ws_get_messages_with_panic( GATEWAY_1.deref(), - CanisterWsGetMessagesArguments { - nonce: next_polling_nonce, - }, + CanisterWsGetMessagesArguments::new(next_polling_nonce), ); verify_messages( &messages, @@ -196,7 +182,7 @@ proptest! { .. } = call_ws_get_messages_with_panic( GATEWAY_1.deref(), - CanisterWsGetMessagesArguments { nonce: 0 }, + CanisterWsGetMessagesArguments::new(0), ); assert_eq!( @@ -250,9 +236,7 @@ fn test_6_empty_gateway_can_get_messages_until_next_keep_alive_check() { // disconnect the client and check that gateway can still receive the messages call_ws_close_with_panic( &GATEWAY_1, - CanisterWsCloseArguments { - client_key: client_1_key.clone(), - }, + CanisterWsCloseArguments::new(client_1_key.clone()), ); // check that gateway can still receive the messages @@ -296,9 +280,7 @@ fn test_7_empty_gateway_can_get_messages_until_next_keep_alive_check_if_removed_ // disconnect the client and check that gateway can still receive the messages call_ws_close_with_panic( &GATEWAY_1, - CanisterWsCloseArguments { - client_key: client_1_key.clone(), - }, + CanisterWsCloseArguments::new(client_1_key.clone()), ); let expected_messages_len = send_messages_count + 1; // +1 for the ack message @@ -330,10 +312,8 @@ mod helpers { use super::*; pub(super) fn assert_gateway_has_messages(send_messages_count: usize) { - let CanisterOutputCertifiedMessages { messages, .. } = call_ws_get_messages_with_panic( - &GATEWAY_1, - CanisterWsGetMessagesArguments { nonce: 0 }, - ); + let CanisterOutputCertifiedMessages { messages, .. } = + call_ws_get_messages_with_panic(&GATEWAY_1, CanisterWsGetMessagesArguments::new(0)); assert_eq!( messages.len(), // + 1 for the open service message @@ -342,10 +322,8 @@ mod helpers { } pub(super) fn assert_gateway_has_no_messages() { - let CanisterOutputCertifiedMessages { messages, .. } = call_ws_get_messages_with_panic( - &GATEWAY_1, - CanisterWsGetMessagesArguments { nonce: 0 }, - ); + let CanisterOutputCertifiedMessages { messages, .. } = + call_ws_get_messages_with_panic(&GATEWAY_1, CanisterWsGetMessagesArguments::new(0)); assert_eq!(messages.len(), 0); } } diff --git a/src/ic-websocket-cdk/src/tests/integration_tests/d_ws_close.rs b/src/ic-websocket-cdk/src/tests/integration_tests/d_ws_close.rs index 7abc148..ff4de06 100644 --- a/src/ic-websocket-cdk/src/tests/integration_tests/d_ws_close.rs +++ b/src/ic-websocket-cdk/src/tests/integration_tests/d_ws_close.rs @@ -26,9 +26,7 @@ fn test_1_fails_if_gateway_is_not_registered() { // finally, we can start testing let res = call_ws_close( gateway_2_principal, - CanisterWsCloseArguments { - client_key: CLIENT_1_KEY.clone(), - }, + CanisterWsCloseArguments::new(CLIENT_1_KEY.clone()), ); assert_eq!( res, @@ -51,9 +49,7 @@ fn test_2_fails_if_client_is_not_registered() { let client_2_key = CLIENT_2_KEY.deref(); let res = call_ws_close( GATEWAY_1.deref(), - CanisterWsCloseArguments { - client_key: client_2_key.clone(), - }, + CanisterWsCloseArguments::new(client_2_key.clone()), ); assert_eq!( res, @@ -79,9 +75,7 @@ fn test_3_fails_if_client_is_not_registered_to_gateway() { // finally, we can start testing let res = call_ws_close( gateway_2_principal, - CanisterWsCloseArguments { - client_key: CLIENT_1_KEY.clone(), - }, + CanisterWsCloseArguments::new(CLIENT_1_KEY.clone()), ); assert_eq!( res, @@ -99,18 +93,14 @@ fn test_3_fails_if_client_is_not_registered_to_gateway() { fn test_4_should_close_the_websocket_for_a_registered_client() { let res = call_ws_close( GATEWAY_1.deref(), - CanisterWsCloseArguments { - client_key: CLIENT_1_KEY.clone(), - }, + CanisterWsCloseArguments::new(CLIENT_1_KEY.clone()), ); assert_eq!(res, CanisterWsCloseResult::Ok(())); // we expect the ws_close to fail if we execute it again let res = call_ws_close( GATEWAY_1.deref(), - CanisterWsCloseArguments { - client_key: CLIENT_1_KEY.clone(), - }, + CanisterWsCloseArguments::new(CLIENT_1_KEY.clone()), ); assert_eq!( res, diff --git a/src/ic-websocket-cdk/src/tests/integration_tests/g_messages_acknowledgement.rs b/src/ic-websocket-cdk/src/tests/integration_tests/g_messages_acknowledgement.rs index 76d1a11..cb8ce9e 100644 --- a/src/ic-websocket-cdk/src/tests/integration_tests/g_messages_acknowledgement.rs +++ b/src/ic-websocket-cdk/src/tests/integration_tests/g_messages_acknowledgement.rs @@ -34,23 +34,19 @@ fn test_1_client_should_receive_ack_messages() { // make sure there are no messages in the queue, except from first open message let CanisterOutputCertifiedMessages { messages, .. } = call_ws_get_messages_with_panic( GATEWAY_1.deref(), - CanisterWsGetMessagesArguments { nonce: 1 }, // skip the service open message + CanisterWsGetMessagesArguments::new(1), // skip the service open message ); assert_eq!(messages.len(), 0); // send a message from the client in order to receive the ack with the updated sequence number call_ws_message_with_panic( &client_1_key.client_principal, - CanisterWsMessageArguments { - msg: create_websocket_message(client_1_key, 1, None, false), - }, + CanisterWsMessageArguments::new(create_websocket_message(client_1_key, 1, None, false)), ); // advance the canister time to make sure the ack timer expires and an ack is sent get_test_env().advance_canister_time_ms(DEFAULT_TEST_SEND_ACK_INTERVAL_MS); - let res = call_ws_get_messages_with_panic( - GATEWAY_1.deref(), - CanisterWsGetMessagesArguments { nonce: 1 }, - ); + let res = + call_ws_get_messages_with_panic(GATEWAY_1.deref(), CanisterWsGetMessagesArguments::new(1)); helpers::check_ack_message_result(&res, client_1_key, 1, 2); } @@ -63,10 +59,8 @@ fn test_2_client_is_removed_if_keep_alive_timeout_is_reached() { // advance the canister time to make sure the ack timer expires and an ack is sent get_test_env().advance_canister_time_ms(DEFAULT_TEST_SEND_ACK_INTERVAL_MS); // get messages to check if the ack message has been set - let msgs = call_ws_get_messages_with_panic( - GATEWAY_1.deref(), - CanisterWsGetMessagesArguments { nonce: 1 }, - ); + let msgs = + call_ws_get_messages_with_panic(GATEWAY_1.deref(), CanisterWsGetMessagesArguments::new(1)); helpers::check_ack_message_result(&msgs, client_1_key, 0, 2); // advance the canister time to make sure the keep alive timeout expires @@ -75,7 +69,7 @@ fn test_2_client_is_removed_if_keep_alive_timeout_is_reached() { // check if the gateway put the close message in the queue let msgs = call_ws_get_messages_with_panic( GATEWAY_1.deref(), - CanisterWsGetMessagesArguments { nonce: 1 }, // skip the first open message + CanisterWsGetMessagesArguments::new(1), // skip the first open message ); check_canister_message_has_close_reason( &msgs.messages[1], @@ -86,9 +80,7 @@ fn test_2_client_is_removed_if_keep_alive_timeout_is_reached() { // so calling the ws_close endpoint should return the ClientKeyNotConnected error let res = call_ws_close( GATEWAY_1.deref(), - CanisterWsCloseArguments { - client_key: client_1_key.clone(), - }, + CanisterWsCloseArguments::new(client_1_key.clone()), ); assert_eq!( res, @@ -110,29 +102,23 @@ fn test_3_client_is_not_removed_if_it_sends_a_keep_alive_before_timeout() { // advance the canister time to make sure the ack timer expires and an ack is sent get_test_env().advance_canister_time_ms(DEFAULT_TEST_SEND_ACK_INTERVAL_MS); // get messages to check if the ack message has been set - let res = call_ws_get_messages_with_panic( - GATEWAY_1.deref(), - CanisterWsGetMessagesArguments { nonce: 1 }, - ); + let res = + call_ws_get_messages_with_panic(GATEWAY_1.deref(), CanisterWsGetMessagesArguments::new(1)); helpers::check_ack_message_result(&res, client_1_key, 0, 2); // send keep alive message call_ws_message_with_panic( &client_1_key.client_principal, - CanisterWsMessageArguments { - msg: create_websocket_message( - client_1_key, - 1, - Some(encode_websocket_service_message_content( - &WebsocketServiceMessageContent::KeepAliveMessage( - ClientKeepAliveMessageContent { - last_incoming_sequence_num: 1, // ignored in the CDK - }, - ), - )), - true, - ), - }, + CanisterWsMessageArguments::new(create_websocket_message( + client_1_key, + 1, + Some(encode_websocket_service_message_content( + &WebsocketServiceMessageContent::KeepAliveMessage(ClientKeepAliveMessageContent { + last_incoming_sequence_num: 1, // ignored in the CDK + }), + )), + true, + )), ); // advance the canister time to make sure the keep alive timeout expires and the canister checks the keep alive get_test_env().advance_canister_time_ms(CLIENT_KEEP_ALIVE_TIMEOUT_MS); @@ -140,16 +126,14 @@ fn test_3_client_is_not_removed_if_it_sends_a_keep_alive_before_timeout() { // and be sure that the client has not been removed call_ws_message_with_panic( &client_1_key.client_principal, - CanisterWsMessageArguments { - msg: create_websocket_message(client_1_key, 2, None, false), - }, + CanisterWsMessageArguments::new(create_websocket_message(client_1_key, 2, None, false)), ); // wait for the canister to send the next ack get_test_env() .advance_canister_time_ms(DEFAULT_TEST_SEND_ACK_INTERVAL_MS - CLIENT_KEEP_ALIVE_TIMEOUT_MS); let res = call_ws_get_messages_with_panic( GATEWAY_1.deref(), - CanisterWsGetMessagesArguments { nonce: 2 }, // skip the service open message and the fist ack message + CanisterWsGetMessagesArguments::new(2), // skip the service open message and the fist ack message ); helpers::check_ack_message_result(&res, client_1_key, 2, 3); } @@ -166,7 +150,7 @@ fn test_4_client_is_not_removed_if_it_connects_while_canister_is_waiting_for_kee // get messages for client: at this point the client doesn't expect any message let CanisterOutputCertifiedMessages { messages, .. } = call_ws_get_messages_with_panic( GATEWAY_1.deref(), - CanisterWsGetMessagesArguments { nonce: 1 }, // skip the service open message + CanisterWsGetMessagesArguments::new(1), // skip the service open message ); assert_eq!(messages.len(), 0); @@ -174,9 +158,7 @@ fn test_4_client_is_not_removed_if_it_connects_while_canister_is_waiting_for_kee // and be sure that the client has not been removed call_ws_message_with_panic( &client_1_key.client_principal, - CanisterWsMessageArguments { - msg: create_websocket_message(client_1_key, 1, None, false), - }, + CanisterWsMessageArguments::new(create_websocket_message(client_1_key, 1, None, false)), ); // wait for the keep alive timeout to expire @@ -187,7 +169,7 @@ fn test_4_client_is_not_removed_if_it_connects_while_canister_is_waiting_for_kee let res = call_ws_get_messages_with_panic( GATEWAY_1.deref(), - CanisterWsGetMessagesArguments { nonce: 1 }, // skip the service open message + CanisterWsGetMessagesArguments::new(1), // skip the service open message ); helpers::check_ack_message_result(&res, client_1_key, 1, 2); } diff --git a/src/ic-websocket-cdk/src/tests/integration_tests/h_multiple_gateways.rs b/src/ic-websocket-cdk/src/tests/integration_tests/h_multiple_gateways.rs index 32317b4..2cc9878 100644 --- a/src/ic-websocket-cdk/src/tests/integration_tests/h_multiple_gateways.rs +++ b/src/ic-websocket-cdk/src/tests/integration_tests/h_multiple_gateways.rs @@ -42,7 +42,7 @@ proptest! { // gateway 1 can poll the messages let CanisterOutputCertifiedMessages { messages, cert, tree, is_end_of_queue } = call_ws_get_messages_with_panic( first_gateway, - CanisterWsGetMessagesArguments { nonce: 0 }, + CanisterWsGetMessagesArguments::new(0), ); prop_assert_eq!(messages.len(), messages_to_send.len() + 1); // +1 for the open service message prop_assert_eq!(is_end_of_queue, true); @@ -60,23 +60,21 @@ proptest! { // gateway 2 has no messages because it's not registered let CanisterOutputCertifiedMessages { messages, .. } = call_ws_get_messages_with_panic( second_gateway, - CanisterWsGetMessagesArguments { nonce: 0 }, + CanisterWsGetMessagesArguments::new(0), ); prop_assert_eq!(messages.len() as u64, 0); // client disconnects, so gateway 1 closes the connection call_ws_close_with_panic( first_gateway, - CanisterWsCloseArguments { - client_key: client_key.clone(), - }, + CanisterWsCloseArguments::new(client_key.clone()), ); // client reopens connection with gateway 2 call_ws_open_for_client_key_and_gateway_with_panic(&client_key, *second_gateway); // gateway 2 now has the open message let CanisterOutputCertifiedMessages { messages, cert, tree, is_end_of_queue } = call_ws_get_messages_with_panic( second_gateway, - CanisterWsGetMessagesArguments { nonce: 0 }, + CanisterWsGetMessagesArguments::new(0), ); prop_assert_eq!(messages.len() as u64, 1); @@ -114,7 +112,7 @@ proptest! { let CanisterOutputCertifiedMessages { messages, cert, tree, is_end_of_queue } = call_ws_get_messages_with_panic( second_gateway, - CanisterWsGetMessagesArguments { nonce: 0 }, + CanisterWsGetMessagesArguments::new(0), ); prop_assert_eq!(messages.len(), messages_to_send.len() + 1); // +1 for the open service message prop_assert_eq!(is_end_of_queue, true); diff --git a/src/ic-websocket-cdk/src/tests/integration_tests/utils/messages.rs b/src/ic-websocket-cdk/src/tests/integration_tests/utils/messages.rs index de9891e..526ce61 100644 --- a/src/ic-websocket-cdk/src/tests/integration_tests/utils/messages.rs +++ b/src/ic-websocket-cdk/src/tests/integration_tests/utils/messages.rs @@ -49,13 +49,13 @@ pub(in crate::tests::integration_tests) fn create_websocket_message( ) -> WebsocketMessage { let content = content.unwrap_or(vec![]); - WebsocketMessage { - client_key: client_key.clone(), - sequence_num: sequence_number, - timestamp: get_current_timestamp_ns(), - content, + WebsocketMessage::new( + client_key.clone(), + sequence_number, + get_current_timestamp_ns(), is_service_message, - } + content, + ) } pub(in crate::tests::integration_tests) fn decode_websocket_message( diff --git a/src/ic-websocket-cdk/src/tests/unit_tests/mod.rs b/src/ic-websocket-cdk/src/tests/unit_tests/mod.rs index 326fe77..de4a9b7 100644 --- a/src/ic-websocket-cdk/src/tests/unit_tests/mod.rs +++ b/src/ic-websocket-cdk/src/tests/unit_tests/mod.rs @@ -835,13 +835,13 @@ proptest! { #[test] fn test_serialize_websocket_message(test_msg_bytes in any::>(), test_sequence_num in any::(), test_timestamp in any::()) { // TODO: add more tests, in which we check the serialized message - let websocket_message = WebsocketMessage { - client_key: common::get_random_client_key(), - sequence_num: test_sequence_num, - timestamp: test_timestamp, - is_service_message: false, - content: test_msg_bytes, - }; + let websocket_message = WebsocketMessage::new( + common::get_random_client_key(), + test_sequence_num, + test_timestamp, + false, + test_msg_bytes, + ); let serialized_message = websocket_message.cbor_serialize(); prop_assert!(serialized_message.is_ok()); // not so useful as a test diff --git a/src/ic-websocket-cdk/src/types.rs b/src/ic-websocket-cdk/src/types.rs index 2dcb72e..10196d6 100644 --- a/src/ic-websocket-cdk/src/types.rs +++ b/src/ic-websocket-cdk/src/types.rs @@ -51,8 +51,8 @@ pub type CanisterCloseResult = Result<(), String>; /// The arguments for [ws_open](crate::ws_open). #[derive(CandidType, Clone, Deserialize, Serialize, Eq, PartialEq, Debug)] pub struct CanisterWsOpenArguments { - pub(crate) client_nonce: u64, - pub(crate) gateway_principal: GatewayPrincipal, + pub client_nonce: u64, + pub gateway_principal: GatewayPrincipal, } impl CanisterWsOpenArguments { @@ -66,35 +66,71 @@ impl CanisterWsOpenArguments { /// The arguments for [ws_close](crate::ws_close). #[derive(CandidType, Clone, Deserialize, Serialize, Eq, PartialEq, Debug)] pub struct CanisterWsCloseArguments { - pub(crate) client_key: ClientKey, + pub client_key: ClientKey, +} + +impl CanisterWsCloseArguments { + pub fn new(client_key: ClientKey) -> Self { + Self { client_key } + } } /// The arguments for [ws_message](crate::ws_message). #[derive(CandidType, Clone, Deserialize, Serialize, Eq, PartialEq, Debug)] pub struct CanisterWsMessageArguments { - pub(crate) msg: WebsocketMessage, + pub msg: WebsocketMessage, +} + +impl CanisterWsMessageArguments { + pub fn new(msg: WebsocketMessage) -> Self { + Self { msg } + } } /// The arguments for [ws_get_messages](crate::ws_get_messages). #[derive(CandidType, Clone, Deserialize, Serialize, Eq, PartialEq, Debug)] pub struct CanisterWsGetMessagesArguments { - pub(crate) nonce: u64, + pub nonce: u64, +} + +impl CanisterWsGetMessagesArguments { + pub fn new(nonce: u64) -> Self { + Self { nonce } + } } /// Messages exchanged through the WebSocket. +/// +/// Note: you won't need this struct in a normal IC WebSocket implementation. #[derive(CandidType, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] -pub(crate) struct WebsocketMessage { - pub(crate) client_key: ClientKey, // The client that the gateway will forward the message to or that sent the message. - pub(crate) sequence_num: u64, // Both ways, messages should arrive with sequence numbers 0, 1, 2... - pub(crate) timestamp: TimestampNs, // Timestamp of when the message was made for the recipient to inspect. - pub(crate) is_service_message: bool, // Whether the message is a service message sent by the CDK to the client or vice versa. +pub struct WebsocketMessage { + pub client_key: ClientKey, // The client that the gateway will forward the message to or that sent the message. + pub sequence_num: u64, // Both ways, messages should arrive with sequence numbers 0, 1, 2... + pub timestamp: TimestampNs, // Timestamp of when the message was made for the recipient to inspect. + pub is_service_message: bool, // Whether the message is a service message sent by the CDK to the client or vice versa. #[serde(with = "serde_bytes")] - pub(crate) content: Vec, // Application message encoded in binary. + pub content: Vec, // Application message encoded in binary. } impl WebsocketMessage { + pub fn new( + client_key: ClientKey, + sequence_num: u64, + timestamp: TimestampNs, + is_service_message: bool, + content: Vec, + ) -> Self { + Self { + client_key, + sequence_num, + timestamp, + is_service_message, + content, + } + } + /// Serializes the message into a Vec, using CBOR. - pub(crate) fn cbor_serialize(&self) -> Result, String> { + pub fn cbor_serialize(&self) -> Result, String> { let mut data = vec![]; let mut serializer = Serializer::new(&mut data); serializer.self_describe().map_err(|e| e.to_string())?; @@ -104,23 +140,27 @@ impl WebsocketMessage { } /// Element of the list of messages returned to the WS Gateway after polling. +/// +/// Note: you won't need this struct in a normal IC WebSocket implementation. #[derive(CandidType, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] -pub(crate) struct CanisterOutputMessage { - pub(crate) client_key: ClientKey, // The client that the gateway will forward the message to or that sent the message. - pub(crate) key: String, // Key for certificate verification. +pub struct CanisterOutputMessage { + pub client_key: ClientKey, // The client that the gateway will forward the message to or that sent the message. + pub key: String, // Key for certificate verification. #[serde(with = "serde_bytes")] - pub(crate) content: Vec, // The message to be relayed, that contains the application message. + pub content: Vec, // The message to be relayed, that contains the application message. } /// List of messages returned to the WS Gateway after polling. +/// +/// Note: you won't need this struct in a normal IC WebSocket implementation. #[derive(CandidType, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] pub struct CanisterOutputCertifiedMessages { - pub(crate) messages: Vec, // List of messages. + pub messages: Vec, // List of messages. #[serde(with = "serde_bytes")] - pub(crate) cert: Vec, // cert+tree constitute the certificate for all returned messages. + pub cert: Vec, // cert+tree constitute the certificate for all returned messages. #[serde(with = "serde_bytes")] - pub(crate) tree: Vec, // cert+tree constitute the certificate for all returned messages. - pub(crate) is_end_of_queue: bool, // Whether the end of the messages queue has been reached. + pub tree: Vec, // cert+tree constitute the certificate for all returned messages. + pub is_end_of_queue: bool, // Whether the end of the messages queue has been reached. } impl CanisterOutputCertifiedMessages { From a643c207fb1ca050ec061f206882e4c75950cd39 Mon Sep 17 00:00:00 2001 From: Luca8991 Date: Sun, 14 Jan 2024 12:14:31 +0100 Subject: [PATCH 6/9] feat: service message types public --- src/ic-websocket-cdk/src/types.rs | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/ic-websocket-cdk/src/types.rs b/src/ic-websocket-cdk/src/types.rs index 10196d6..626a3e4 100644 --- a/src/ic-websocket-cdk/src/types.rs +++ b/src/ic-websocket-cdk/src/types.rs @@ -303,22 +303,26 @@ impl RegisteredClient { } #[derive(CandidType, Debug, Deserialize, PartialEq, Eq)] -pub(crate) struct CanisterOpenMessageContent { - pub(crate) client_key: ClientKey, +/// Note: you won't need this struct in a normal IC WebSocket implementation. +pub struct CanisterOpenMessageContent { + pub client_key: ClientKey, } #[derive(CandidType, Debug, Deserialize, PartialEq, Eq)] -pub(crate) struct CanisterAckMessageContent { - pub(crate) last_incoming_sequence_num: u64, +/// Note: you won't need this struct in a normal IC WebSocket implementation. +pub struct CanisterAckMessageContent { + pub last_incoming_sequence_num: u64, } #[derive(CandidType, Debug, Deserialize, PartialEq, Eq)] -pub(crate) struct ClientKeepAliveMessageContent { - pub(crate) last_incoming_sequence_num: u64, +/// Note: you won't need this struct in a normal IC WebSocket implementation. +pub struct ClientKeepAliveMessageContent { + pub last_incoming_sequence_num: u64, } #[derive(CandidType, Clone, Debug, Deserialize, PartialEq, Eq)] -pub(crate) enum CloseMessageReason { +/// Note: you won't need this enum in a normal IC WebSocket implementation. +pub enum CloseMessageReason { /// When the canister receives a wrong sequence number from the client. WrongSequenceNumber, /// When the canister receives an invalid service message from the client. @@ -330,13 +334,16 @@ pub(crate) enum CloseMessageReason { } #[derive(CandidType, Debug, Deserialize, PartialEq, Eq)] -pub(crate) struct CanisterCloseMessageContent { - pub(crate) reason: CloseMessageReason, +/// Note: you won't need this struct in a normal IC WebSocket implementation. +pub struct CanisterCloseMessageContent { + pub reason: CloseMessageReason, } -/// A service message sent by the CDK to the client or vice versa. #[derive(CandidType, Debug, Deserialize, PartialEq, Eq)] -pub(crate) enum WebsocketServiceMessageContent { +/// A service message sent by the CDK to the client or vice versa. +/// +/// Note: you won't need this struct in a normal IC WebSocket implementation. +pub enum WebsocketServiceMessageContent { /// Message sent by the **canister** when a client opens a connection. OpenMessage(CanisterOpenMessageContent), /// Message sent _periodically_ by the **canister** to the client to acknowledge the messages received. @@ -348,7 +355,7 @@ pub(crate) enum WebsocketServiceMessageContent { } impl WebsocketServiceMessageContent { - pub(crate) fn from_candid_bytes(bytes: &[u8]) -> Result { + pub fn from_candid_bytes(bytes: &[u8]) -> Result { decode_one(&bytes).map_err(|err| WsError::DecodeServiceMessageContent { err }.to_string()) } } From 315963afbac91bf04c80b340182116e8ada7995f Mon Sep 17 00:00:00 2001 From: Luca8991 Date: Sun, 14 Jan 2024 12:22:16 +0100 Subject: [PATCH 7/9] fix: expose types not needed by devs under types module --- src/ic-websocket-cdk/src/lib.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/ic-websocket-cdk/src/lib.rs b/src/ic-websocket-cdk/src/lib.rs index 7540b2e..73f3d67 100644 --- a/src/ic-websocket-cdk/src/lib.rs +++ b/src/ic-websocket-cdk/src/lib.rs @@ -8,7 +8,7 @@ mod errors; mod state; mod tests; mod timers; -mod types; +pub mod types; mod utils; use state::*; @@ -17,12 +17,10 @@ use timers::*; pub use types::CanisterWsSendResult; use types::*; pub use types::{ - CanisterCloseResult, CanisterOutputCertifiedMessages, CanisterOutputMessage, - CanisterSendResult, CanisterWsCloseArguments, CanisterWsCloseResult, + CanisterCloseResult, CanisterSendResult, CanisterWsCloseArguments, CanisterWsCloseResult, CanisterWsGetMessagesArguments, CanisterWsGetMessagesResult, CanisterWsMessageArguments, - CanisterWsMessageResult, CanisterWsOpenArguments, CanisterWsOpenResult, ClientKey, - ClientPrincipal, OnCloseCallbackArgs, OnMessageCallbackArgs, OnOpenCallbackArgs, - WebsocketMessage, WsHandlers, WsInitParams, + CanisterWsMessageResult, CanisterWsOpenArguments, CanisterWsOpenResult, ClientPrincipal, + OnCloseCallbackArgs, OnMessageCallbackArgs, OnOpenCallbackArgs, WsHandlers, WsInitParams, }; /// The label used when constructing the certification tree. From 6b6bbea1c08834cb73e28b6b175a1aa44c82a695 Mon Sep 17 00:00:00 2001 From: Luca8991 Date: Thu, 18 Jan 2024 21:13:13 +0100 Subject: [PATCH 8/9] chore: update struct comments --- src/ic-websocket-cdk/src/types.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ic-websocket-cdk/src/types.rs b/src/ic-websocket-cdk/src/types.rs index 626a3e4..9b5634e 100644 --- a/src/ic-websocket-cdk/src/types.rs +++ b/src/ic-websocket-cdk/src/types.rs @@ -101,7 +101,7 @@ impl CanisterWsGetMessagesArguments { /// Messages exchanged through the WebSocket. /// -/// Note: you won't need this struct in a normal IC WebSocket implementation. +/// **Note:** You should only use this struct in tests. #[derive(CandidType, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] pub struct WebsocketMessage { pub client_key: ClientKey, // The client that the gateway will forward the message to or that sent the message. @@ -141,7 +141,7 @@ impl WebsocketMessage { /// Element of the list of messages returned to the WS Gateway after polling. /// -/// Note: you won't need this struct in a normal IC WebSocket implementation. +/// **Note:** You should only use this struct in tests. #[derive(CandidType, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] pub struct CanisterOutputMessage { pub client_key: ClientKey, // The client that the gateway will forward the message to or that sent the message. @@ -152,7 +152,7 @@ pub struct CanisterOutputMessage { /// List of messages returned to the WS Gateway after polling. /// -/// Note: you won't need this struct in a normal IC WebSocket implementation. +/// **Note:** You should only use this struct in tests. #[derive(CandidType, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] pub struct CanisterOutputCertifiedMessages { pub messages: Vec, // List of messages. @@ -189,8 +189,8 @@ pub(crate) struct MessageToDelete { pub(crate) type GatewayPrincipal = Principal; -#[derive(Clone, Debug, Default, Eq, PartialEq)] /// Contains data about the registered WS Gateway. +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub(crate) struct RegisteredGateway { /// The queue of the messages that the gateway can poll. pub(crate) messages_queue: VecDeque, @@ -302,26 +302,26 @@ impl RegisteredClient { } } +/// **Note:** You should only use this struct in tests. #[derive(CandidType, Debug, Deserialize, PartialEq, Eq)] -/// Note: you won't need this struct in a normal IC WebSocket implementation. pub struct CanisterOpenMessageContent { pub client_key: ClientKey, } +/// **Note:** You should only use this struct in tests. #[derive(CandidType, Debug, Deserialize, PartialEq, Eq)] -/// Note: you won't need this struct in a normal IC WebSocket implementation. pub struct CanisterAckMessageContent { pub last_incoming_sequence_num: u64, } +/// **Note:** You should only use this struct in tests. #[derive(CandidType, Debug, Deserialize, PartialEq, Eq)] -/// Note: you won't need this struct in a normal IC WebSocket implementation. pub struct ClientKeepAliveMessageContent { pub last_incoming_sequence_num: u64, } +/// **Note:** You should only use this struct in tests. #[derive(CandidType, Clone, Debug, Deserialize, PartialEq, Eq)] -/// Note: you won't need this enum in a normal IC WebSocket implementation. pub enum CloseMessageReason { /// When the canister receives a wrong sequence number from the client. WrongSequenceNumber, @@ -333,16 +333,16 @@ pub enum CloseMessageReason { ClosedByApplication, } +/// **Note:** You should only use this struct in tests. #[derive(CandidType, Debug, Deserialize, PartialEq, Eq)] -/// Note: you won't need this struct in a normal IC WebSocket implementation. pub struct CanisterCloseMessageContent { pub reason: CloseMessageReason, } -#[derive(CandidType, Debug, Deserialize, PartialEq, Eq)] /// A service message sent by the CDK to the client or vice versa. /// -/// Note: you won't need this struct in a normal IC WebSocket implementation. +/// **Note:** You should only use this struct in tests. +#[derive(CandidType, Debug, Deserialize, PartialEq, Eq)] pub enum WebsocketServiceMessageContent { /// Message sent by the **canister** when a client opens a connection. OpenMessage(CanisterOpenMessageContent), From ba568ae70787ecaadfeb2fd91a6ab747168cc028 Mon Sep 17 00:00:00 2001 From: Luca8991 Date: Thu, 18 Jan 2024 21:21:49 +0100 Subject: [PATCH 9/9] chore: bump to version v0.3.3 --- Cargo.lock | 2 +- src/ic-websocket-cdk/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1745e84..3577ad0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1001,7 +1001,7 @@ dependencies = [ [[package]] name = "ic-websocket-cdk" -version = "0.3.2" +version = "0.3.3" dependencies = [ "base64 0.21.5", "candid", diff --git a/src/ic-websocket-cdk/Cargo.toml b/src/ic-websocket-cdk/Cargo.toml index 99173b5..2c2ec1b 100644 --- a/src/ic-websocket-cdk/Cargo.toml +++ b/src/ic-websocket-cdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ic-websocket-cdk" -version = "0.3.2" +version = "0.3.3" edition.workspace = true rust-version.workspace = true repository.workspace = true