/// <summary> /// Opens the websocket connection and sends the CONNECT message. /// </summary> /// <returns><System.Boolean> true if successful.</returns> public async Task <bool> ConnectAsync(Action <string> errorsEventHandler) { const string WEBSOCKETPATH = "/w/messages/websocket"; // Based on https://docs.nem.io/en/nem-dev-basics-docker/blockchain-monitoring await ClientWs.ConnectAsync(new Uri(string.Concat("ws://", Domain, ":", Port, WEBSOCKETPATH)), CancellationToken.None); if (ClientWs.State != WebSocketState.Open) { return(false); } // Send CONNECT StompMessage connect = new StompMessage(StompMessage.ClientCommands.CONNECT); // Set message headers, might not be needed //connect["accept-version"] = "1.1,1.0"; //connect["heart-beat"] = "10000, 10000"; // out, in await SendAsync(connect); // Read the answer from the server StompMessage connectedMsg = StompMessage.Deserialize(await ReadSocketAsync()); if (connectedMsg.Command != StompMessage.ServerResponses.CONNECTED) { return(false); } // Subscribe to errors channel await SubscribeToErrorsAsync(errorsEventHandler); // Start the Loop to read answers Task loop = LoopReadStompMsgsAsync(); // Explicitely not using await, to allow the Loop to run asynchronously without waiting for it to complete! return(true); }
/// <summary> /// Subscribes to the channel on the destination path. /// </summary> /// <param name="destinationPath">The channel.</param> async Task SendSubscribeStompMsgAsync(string destinationPath, Address destinationAddress = null) { StompMessage subscribe = new StompMessage(StompMessage.ClientCommands.SUBSCRIBE); subscribe.SetSubscriptionId(SubscriptionCounter++); subscribe.SetDestination(destinationPath, destinationAddress); await SendAsync(subscribe); // The LoopReadStompMsgsAsync will process the answer from the server Debug.WriteLine("Subscription requested on path " + subscribe.GetDestination()); }
/// <summary> /// Subscribes to the channel for Definitions of Mosaics owned by the given Account. The Eventhandler will be invoked when a message is received on the channel. /// </summary> /// <param name="account"></param> /// <param name="mosaicDefinitionEventHandler"></param> /// <returns></returns> public async Task SubscribeToOwnedMosaicDefinitionsAsync(string account, Action <Address, MosaicInfo> mosaicDefinitionEventHandler) { Address destinationAddress = new Address(account); this.OnMosaicDefinitionEventHandler = mosaicDefinitionEventHandler; await SendSubscribeStompMsgAsync(ChannelPaths.OWNEDMOSAICDEFINITION, destinationAddress); // Send an explicit message to request an immediate answer on the channel StompMessage send = new StompMessage(StompMessage.ClientCommands.SEND, "{'account':'" + destinationAddress.Plain + "'}"); send.SetDestination(ApiPaths.OWNEDMOSAICDEFINITIONS); await SendAsync(send); // The LoopReadStompMsgsAsync will process the answer from the server }
/// <summary> /// Subscribes to the channel for Mosaics owned by the given Account. The Eventhandler will be invoked when a message is received on the channel. /// </summary> /// <param name="account"></param> /// <param name="mosaicEventHandler"></param> public async Task SubscribeToOwnedMosaicsAsync(string account, Action <Address, MosaicAmount> mosaicEventHandler) { Address destinationAddress = new Address(account); this.OnMosaicEventHandler = mosaicEventHandler; // The callback will be invoked for each msg received per Mosaic owned await SendSubscribeStompMsgAsync(ChannelPaths.OWNEDMOSAICS, destinationAddress); // Send an explicit message to request an immediate answer on the channel StompMessage send = new StompMessage(StompMessage.ClientCommands.SEND, "{'account':'" + destinationAddress.Plain + "'}"); send.SetDestination(ApiPaths.OWNEDMOSAICS); await SendAsync(send); // The LoopReadStompMsgsAsync will process the answer from the server }
/// <summary> /// Subscribes to the channel for an Account. The Eventhandler will be invoked when a message is received on the channel. /// </summary> /// <param name="account"></param> /// <param name="accountEventHandler"></param> public async Task SubscribeToAccountAsync(string account, Action <AccountInfo> accountEventHandler) { Address destinationAddress = new Address(account); this.OnAccountEventHandler = accountEventHandler; await SendSubscribeStompMsgAsync(ChannelPaths.ACCOUNT, destinationAddress); // Send an explicit message to request an immediate answer on the channel StompMessage send = new StompMessage(StompMessage.ClientCommands.SEND, "{'account':'" + destinationAddress.Plain + "'}"); send.SetDestination(ApiPaths.ACCOUNT); await SendAsync(send); // The LoopReadStompMsgsAsync will process the answer from the server }
/// <summary> /// Loop to read Stomp messages from the websocket and call appropriate actions. /// </summary> private async Task LoopReadStompMsgsAsync() { while (ClientWs.State == WebSocketState.Open) // Infinite loop for as long as we are running with an open socket { var msg = await ReadSocketAsync(); StompMessage stompMsg = StompMessage.Deserialize(msg); switch (stompMsg.Command) { case StompMessage.ServerResponses.ERROR: this.OnErrorEventHandler?.Invoke(stompMsg.Body); break; case StompMessage.ServerResponses.MESSAGE: ProcessReceivedMessage(stompMsg.GetDestination(), stompMsg.Body); break; default: // throw exception? Debug.WriteLine("Received STOMP Message with an unsupported Command: " + stompMsg); break; } } }
/// <summary> /// Sends the StompMessage Asynchronously. Call LoopReadStompMsgsAsync to process answers from the server. /// </summary> /// <param name="stompMsg"></param> async Task SendAsync(StompMessage stompMsg) { byte[] encoded = Encoding.UTF8.GetBytes(stompMsg.Serialize()); ArraySegment <byte> segment = new ArraySegment <byte>(encoded, 0, encoded.Length); await ClientWs.SendAsync(segment, WebSocketMessageType.Text, true, CancellationToken.None); }