Major overhaul #2
@@ -34,7 +34,14 @@ namespace ntfysh_client
 | 
			
		||||
            if (result != DialogResult.OK) return;
 | 
			
		||||
                
 | 
			
		||||
            //Subscribe
 | 
			
		||||
            _notificationListener.SubscribeToTopicUsingLongHttpJson(dialog.Unique, dialog.TopicId, dialog.ServerUrl, dialog.Username, dialog.Password);
 | 
			
		||||
            if (dialog.UseWebsockets)
 | 
			
		||||
            {
 | 
			
		||||
                _notificationListener.SubscribeToTopicUsingWebsocket(dialog.Unique, dialog.TopicId, dialog.ServerUrl, dialog.Username, dialog.Password);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                _notificationListener.SubscribeToTopicUsingLongHttpJson(dialog.Unique, dialog.TopicId, dialog.ServerUrl, dialog.Username, dialog.Password);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //Add to the user visible list
 | 
			
		||||
            notificationTopics.Items.Add(dialog.Unique);
 | 
			
		||||
@@ -167,7 +174,24 @@ namespace ntfysh_client
 | 
			
		||||
            //Load them in
 | 
			
		||||
            foreach (SubscribedTopic topic in topics)
 | 
			
		||||
            {
 | 
			
		||||
                _notificationListener.SubscribeToTopicUsingLongHttpJson($"{topic.TopicId}@{topic.ServerUrl}", topic.TopicId, topic.ServerUrl, topic.Username, topic.Password);
 | 
			
		||||
                string[] parts = topic.ServerUrl.Split("://", 2);
 | 
			
		||||
                
 | 
			
		||||
                switch (parts[0].ToLower())
 | 
			
		||||
                {
 | 
			
		||||
                    case "ws":
 | 
			
		||||
                    case "wss":
 | 
			
		||||
                        _notificationListener.SubscribeToTopicUsingWebsocket($"{topic.TopicId}@{topic.ServerUrl}", topic.TopicId, topic.ServerUrl, topic.Username, topic.Password);
 | 
			
		||||
                        break;
 | 
			
		||||
                    
 | 
			
		||||
                    case "http":
 | 
			
		||||
                    case "https":
 | 
			
		||||
                        _notificationListener.SubscribeToTopicUsingLongHttpJson($"{topic.TopicId}@{topic.ServerUrl}", topic.TopicId, topic.ServerUrl, topic.Username, topic.Password);
 | 
			
		||||
                        break;
 | 
			
		||||
                    
 | 
			
		||||
                    default:
 | 
			
		||||
                        continue;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                notificationTopics.Items.Add($"{topic.TopicId}@{topic.ServerUrl}");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ namespace ntfysh_client
 | 
			
		||||
            ServicePointManager.DefaultConnectionLimit = 100;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task ListenToTopicAsync(HttpRequestMessage message, CancellationToken cancellationToken)
 | 
			
		||||
        private async Task ListenToTopicWithHttpLongJsonAsync(HttpRequestMessage message, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            if (_isDisposed) throw new ObjectDisposedException(nameof(NotificationListener));
 | 
			
		||||
            
 | 
			
		||||
@@ -84,6 +84,59 @@ namespace ntfysh_client
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        private async Task ListenToTopicWithWebsocketAsync(Uri uri, NetworkCredential credentials, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            if (_isDisposed) throw new ObjectDisposedException(nameof(NotificationListener));
 | 
			
		||||
            
 | 
			
		||||
            while (!cancellationToken.IsCancellationRequested)
 | 
			
		||||
            {
 | 
			
		||||
                using ClientWebSocket socket = new();
 | 
			
		||||
                socket.Options.Credentials = credentials;
 | 
			
		||||
                
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    StringBuilder mainBuffer = new();
 | 
			
		||||
                    
 | 
			
		||||
                    await socket.ConnectAsync(uri, cancellationToken);
 | 
			
		||||
                    
 | 
			
		||||
                    while (!cancellationToken.IsCancellationRequested)
 | 
			
		||||
                    {
 | 
			
		||||
                        //Read as much as possible
 | 
			
		||||
                        byte[] buffer = new byte[8192];
 | 
			
		||||
                        WebSocketReceiveResult? result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
 | 
			
		||||
                        
 | 
			
		||||
                        //Append it to our main buffer
 | 
			
		||||
                        mainBuffer.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
 | 
			
		||||
                        
 | 
			
		||||
                        List<string> lines = mainBuffer.ToString().Split('\n').ToList();
 | 
			
		||||
                        //If we have not yet received a full line, meaning theres only 1 part, go back to reading
 | 
			
		||||
                        if (lines.Count <= 1) continue;
 | 
			
		||||
                        
 | 
			
		||||
                        //We now have at least 1 line! Count how many full lines. There will always be a partial line at the end, even if that partial line is empty
 | 
			
		||||
                        //Separate the partial line from the full lines
 | 
			
		||||
                        int partialLineIndex = lines.Count - 1;
 | 
			
		||||
                        string partialLine = lines[partialLineIndex];
 | 
			
		||||
                        lines.RemoveAt(partialLineIndex);
 | 
			
		||||
                        
 | 
			
		||||
                        //Process the full lines
 | 
			
		||||
                        foreach (string line in lines) ProcessMessage(line);
 | 
			
		||||
                        
 | 
			
		||||
                        //Write back the partial line
 | 
			
		||||
                        mainBuffer.Clear();
 | 
			
		||||
                        mainBuffer.Append(partialLine);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    #if DEBUG
 | 
			
		||||
                        Debug.WriteLine(ex);
 | 
			
		||||
                    #endif
 | 
			
		||||
                    
 | 
			
		||||
                    //Fall back to the outer loop to restart the listen, or cancel if requested
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ProcessMessage(string message)
 | 
			
		||||
        {
 | 
			
		||||
            #if DEBUG
 | 
			
		||||
@@ -120,11 +173,26 @@ namespace ntfysh_client
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            CancellationTokenSource listenCanceller = new();
 | 
			
		||||
            Task listenTask = ListenToTopicAsync(message, listenCanceller.Token);
 | 
			
		||||
            Task listenTask = ListenToTopicWithHttpLongJsonAsync(message, listenCanceller.Token);
 | 
			
		||||
 | 
			
		||||
            SubscribedTopicsByUnique.Add(unique, new SubscribedTopic(topicId, serverUrl, username, password, listenTask, listenCanceller));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public void SubscribeToTopicUsingWebsocket(string unique, string topicId, string serverUrl, string? username, string? password)
 | 
			
		||||
        {
 | 
			
		||||
            if (_isDisposed) throw new ObjectDisposedException(nameof(NotificationListener));
 | 
			
		||||
            
 | 
			
		||||
            if (SubscribedTopicsByUnique.ContainsKey(unique)) throw new InvalidOperationException("A topic with this unique already exists");
 | 
			
		||||
            
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(username)) username = null;
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(password)) password = null;
 | 
			
		||||
            
 | 
			
		||||
            Uri targetUri = new($"{serverUrl}/{HttpUtility.UrlEncode(topicId)}/ws");
 | 
			
		||||
            CancellationTokenSource listenCanceller = new();
 | 
			
		||||
            Task listenTask = ListenToTopicWithWebsocketAsync(targetUri, new NetworkCredential(username, password), listenCanceller.Token);
 | 
			
		||||
            SubscribedTopicsByUnique.Add(unique, new SubscribedTopic(topicId, serverUrl, username, password, listenTask, listenCanceller));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task UnsubscribeFromTopicAsync(string topicUniqueString)
 | 
			
		||||
        {
 | 
			
		||||
            if (_isDisposed) throw new ObjectDisposedException(nameof(NotificationListener));
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										94
									
								
								ntfysh_client/SubscribeDialog.Designer.cs
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										94
									
								
								ntfysh_client/SubscribeDialog.Designer.cs
									
									
									
										generated
									
									
									
								
							@@ -40,6 +40,8 @@ namespace ntfysh_client
 | 
			
		||||
            this.label3 = new System.Windows.Forms.Label();
 | 
			
		||||
            this.password = new System.Windows.Forms.TextBox();
 | 
			
		||||
            this.label4 = new System.Windows.Forms.Label();
 | 
			
		||||
            this.label5 = new System.Windows.Forms.Label();
 | 
			
		||||
            this.connectionType = new System.Windows.Forms.ComboBox();
 | 
			
		||||
            this.panel1.SuspendLayout();
 | 
			
		||||
            this.SuspendLayout();
 | 
			
		||||
            // 
 | 
			
		||||
@@ -49,18 +51,19 @@ namespace ntfysh_client
 | 
			
		||||
            this.panel1.Controls.Add(this.button2);
 | 
			
		||||
            this.panel1.Controls.Add(this.button1);
 | 
			
		||||
            this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom;
 | 
			
		||||
            this.panel1.Location = new System.Drawing.Point(0, 175);
 | 
			
		||||
            this.panel1.Location = new System.Drawing.Point(0, 244);
 | 
			
		||||
            this.panel1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
 | 
			
		||||
            this.panel1.Name = "panel1";
 | 
			
		||||
            this.panel1.Size = new System.Drawing.Size(297, 44);
 | 
			
		||||
            this.panel1.Size = new System.Drawing.Size(346, 51);
 | 
			
		||||
            this.panel1.TabIndex = 0;
 | 
			
		||||
            // 
 | 
			
		||||
            // button2
 | 
			
		||||
            // 
 | 
			
		||||
            this.button2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
 | 
			
		||||
            this.button2.Location = new System.Drawing.Point(131, 11);
 | 
			
		||||
            this.button2.Margin = new System.Windows.Forms.Padding(10, 10, 3, 10);
 | 
			
		||||
            this.button2.Location = new System.Drawing.Point(153, 13);
 | 
			
		||||
            this.button2.Margin = new System.Windows.Forms.Padding(12, 12, 4, 12);
 | 
			
		||||
            this.button2.Name = "button2";
 | 
			
		||||
            this.button2.Size = new System.Drawing.Size(75, 23);
 | 
			
		||||
            this.button2.Size = new System.Drawing.Size(88, 27);
 | 
			
		||||
            this.button2.TabIndex = 1;
 | 
			
		||||
            this.button2.Text = "Cancel";
 | 
			
		||||
            this.button2.UseVisualStyleBackColor = true;
 | 
			
		||||
@@ -69,10 +72,10 @@ namespace ntfysh_client
 | 
			
		||||
            // button1
 | 
			
		||||
            // 
 | 
			
		||||
            this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
 | 
			
		||||
            this.button1.Location = new System.Drawing.Point(212, 11);
 | 
			
		||||
            this.button1.Margin = new System.Windows.Forms.Padding(3, 10, 10, 10);
 | 
			
		||||
            this.button1.Location = new System.Drawing.Point(247, 13);
 | 
			
		||||
            this.button1.Margin = new System.Windows.Forms.Padding(4, 12, 12, 12);
 | 
			
		||||
            this.button1.Name = "button1";
 | 
			
		||||
            this.button1.Size = new System.Drawing.Size(75, 23);
 | 
			
		||||
            this.button1.Size = new System.Drawing.Size(88, 27);
 | 
			
		||||
            this.button1.TabIndex = 2;
 | 
			
		||||
            this.button1.Text = "Subscribe";
 | 
			
		||||
            this.button1.UseVisualStyleBackColor = true;
 | 
			
		||||
@@ -81,9 +84,10 @@ namespace ntfysh_client
 | 
			
		||||
            // label1
 | 
			
		||||
            // 
 | 
			
		||||
            this.label1.AutoSize = true;
 | 
			
		||||
            this.label1.Location = new System.Drawing.Point(12, 9);
 | 
			
		||||
            this.label1.Location = new System.Drawing.Point(14, 10);
 | 
			
		||||
            this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
 | 
			
		||||
            this.label1.Name = "label1";
 | 
			
		||||
            this.label1.Size = new System.Drawing.Size(51, 13);
 | 
			
		||||
            this.label1.Size = new System.Drawing.Size(52, 15);
 | 
			
		||||
            this.label1.TabIndex = 1;
 | 
			
		||||
            this.label1.Text = "Topic ID:";
 | 
			
		||||
            // 
 | 
			
		||||
@@ -91,9 +95,10 @@ namespace ntfysh_client
 | 
			
		||||
            // 
 | 
			
		||||
            this.topicId.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
 | 
			
		||||
            | System.Windows.Forms.AnchorStyles.Right)));
 | 
			
		||||
            this.topicId.Location = new System.Drawing.Point(12, 25);
 | 
			
		||||
            this.topicId.Location = new System.Drawing.Point(14, 29);
 | 
			
		||||
            this.topicId.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
 | 
			
		||||
            this.topicId.Name = "topicId";
 | 
			
		||||
            this.topicId.Size = new System.Drawing.Size(273, 20);
 | 
			
		||||
            this.topicId.Size = new System.Drawing.Size(318, 23);
 | 
			
		||||
            this.topicId.TabIndex = 0;
 | 
			
		||||
            this.topicId.KeyDown += new System.Windows.Forms.KeyEventHandler(this.topicId_KeyDown);
 | 
			
		||||
            // 
 | 
			
		||||
@@ -101,19 +106,21 @@ namespace ntfysh_client
 | 
			
		||||
            // 
 | 
			
		||||
            this.serverUrl.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
 | 
			
		||||
            | System.Windows.Forms.AnchorStyles.Right)));
 | 
			
		||||
            this.serverUrl.Location = new System.Drawing.Point(12, 64);
 | 
			
		||||
            this.serverUrl.Location = new System.Drawing.Point(14, 74);
 | 
			
		||||
            this.serverUrl.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
 | 
			
		||||
            this.serverUrl.Name = "serverUrl";
 | 
			
		||||
            this.serverUrl.Size = new System.Drawing.Size(273, 20);
 | 
			
		||||
            this.serverUrl.Size = new System.Drawing.Size(318, 23);
 | 
			
		||||
            this.serverUrl.TabIndex = 2;
 | 
			
		||||
            this.serverUrl.Text = "https://ntfy.sh";
 | 
			
		||||
            this.serverUrl.Text = "wss://ntfy.sh";
 | 
			
		||||
            this.serverUrl.KeyDown += new System.Windows.Forms.KeyEventHandler(this.serverUrl_KeyDown);
 | 
			
		||||
            // 
 | 
			
		||||
            // label2
 | 
			
		||||
            // 
 | 
			
		||||
            this.label2.AutoSize = true;
 | 
			
		||||
            this.label2.Location = new System.Drawing.Point(10, 48);
 | 
			
		||||
            this.label2.Location = new System.Drawing.Point(12, 55);
 | 
			
		||||
            this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
 | 
			
		||||
            this.label2.Name = "label2";
 | 
			
		||||
            this.label2.Size = new System.Drawing.Size(66, 13);
 | 
			
		||||
            this.label2.Size = new System.Drawing.Size(66, 15);
 | 
			
		||||
            this.label2.TabIndex = 3;
 | 
			
		||||
            this.label2.Text = "Server URL:";
 | 
			
		||||
            // 
 | 
			
		||||
@@ -121,18 +128,20 @@ namespace ntfysh_client
 | 
			
		||||
            // 
 | 
			
		||||
            this.username.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
 | 
			
		||||
            | System.Windows.Forms.AnchorStyles.Right)));
 | 
			
		||||
            this.username.Location = new System.Drawing.Point(12, 103);
 | 
			
		||||
            this.username.Location = new System.Drawing.Point(14, 119);
 | 
			
		||||
            this.username.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
 | 
			
		||||
            this.username.Name = "username";
 | 
			
		||||
            this.username.Size = new System.Drawing.Size(273, 20);
 | 
			
		||||
            this.username.Size = new System.Drawing.Size(318, 23);
 | 
			
		||||
            this.username.TabIndex = 4;
 | 
			
		||||
            this.username.KeyDown += new System.Windows.Forms.KeyEventHandler(this.username_KeyDown);
 | 
			
		||||
            // 
 | 
			
		||||
            // label3
 | 
			
		||||
            // 
 | 
			
		||||
            this.label3.AutoSize = true;
 | 
			
		||||
            this.label3.Location = new System.Drawing.Point(10, 87);
 | 
			
		||||
            this.label3.Location = new System.Drawing.Point(12, 100);
 | 
			
		||||
            this.label3.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
 | 
			
		||||
            this.label3.Name = "label3";
 | 
			
		||||
            this.label3.Size = new System.Drawing.Size(58, 13);
 | 
			
		||||
            this.label3.Size = new System.Drawing.Size(63, 15);
 | 
			
		||||
            this.label3.TabIndex = 5;
 | 
			
		||||
            this.label3.Text = "Username:";
 | 
			
		||||
            // 
 | 
			
		||||
@@ -140,9 +149,10 @@ namespace ntfysh_client
 | 
			
		||||
            // 
 | 
			
		||||
            this.password.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
 | 
			
		||||
            | System.Windows.Forms.AnchorStyles.Right)));
 | 
			
		||||
            this.password.Location = new System.Drawing.Point(12, 142);
 | 
			
		||||
            this.password.Location = new System.Drawing.Point(14, 164);
 | 
			
		||||
            this.password.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
 | 
			
		||||
            this.password.Name = "password";
 | 
			
		||||
            this.password.Size = new System.Drawing.Size(273, 20);
 | 
			
		||||
            this.password.Size = new System.Drawing.Size(318, 23);
 | 
			
		||||
            this.password.TabIndex = 6;
 | 
			
		||||
            this.password.UseSystemPasswordChar = true;
 | 
			
		||||
            this.password.KeyDown += new System.Windows.Forms.KeyEventHandler(this.password_KeyDown);
 | 
			
		||||
@@ -150,18 +160,44 @@ namespace ntfysh_client
 | 
			
		||||
            // label4
 | 
			
		||||
            // 
 | 
			
		||||
            this.label4.AutoSize = true;
 | 
			
		||||
            this.label4.Location = new System.Drawing.Point(10, 126);
 | 
			
		||||
            this.label4.Location = new System.Drawing.Point(12, 145);
 | 
			
		||||
            this.label4.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
 | 
			
		||||
            this.label4.Name = "label4";
 | 
			
		||||
            this.label4.Size = new System.Drawing.Size(56, 13);
 | 
			
		||||
            this.label4.Size = new System.Drawing.Size(60, 15);
 | 
			
		||||
            this.label4.TabIndex = 7;
 | 
			
		||||
            this.label4.Text = "Password:";
 | 
			
		||||
            // 
 | 
			
		||||
            // label5
 | 
			
		||||
            // 
 | 
			
		||||
            this.label5.AutoSize = true;
 | 
			
		||||
            this.label5.Location = new System.Drawing.Point(12, 190);
 | 
			
		||||
            this.label5.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
 | 
			
		||||
            this.label5.Name = "label5";
 | 
			
		||||
            this.label5.Size = new System.Drawing.Size(99, 15);
 | 
			
		||||
            this.label5.TabIndex = 9;
 | 
			
		||||
            this.label5.Text = "Connection Type:";
 | 
			
		||||
            // 
 | 
			
		||||
            // connectionType
 | 
			
		||||
            // 
 | 
			
		||||
            this.connectionType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
 | 
			
		||||
            this.connectionType.FormattingEnabled = true;
 | 
			
		||||
            this.connectionType.Items.AddRange(new object[] {
 | 
			
		||||
            "Websockets (Recommended)",
 | 
			
		||||
            "Long HTTP JSON (Robust)"});
 | 
			
		||||
            this.connectionType.Location = new System.Drawing.Point(14, 208);
 | 
			
		||||
            this.connectionType.Name = "connectionType";
 | 
			
		||||
            this.connectionType.Size = new System.Drawing.Size(318, 23);
 | 
			
		||||
            this.connectionType.TabIndex = 10;
 | 
			
		||||
            this.connectionType.TextChanged += new System.EventHandler(this.connectionType_TextChanged);
 | 
			
		||||
            // 
 | 
			
		||||
            // SubscribeDialog
 | 
			
		||||
            // 
 | 
			
		||||
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
 | 
			
		||||
            this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
 | 
			
		||||
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
 | 
			
		||||
            this.BackColor = System.Drawing.Color.White;
 | 
			
		||||
            this.ClientSize = new System.Drawing.Size(297, 219);
 | 
			
		||||
            this.ClientSize = new System.Drawing.Size(346, 295);
 | 
			
		||||
            this.Controls.Add(this.connectionType);
 | 
			
		||||
            this.Controls.Add(this.label5);
 | 
			
		||||
            this.Controls.Add(this.password);
 | 
			
		||||
            this.Controls.Add(this.label4);
 | 
			
		||||
            this.Controls.Add(this.username);
 | 
			
		||||
@@ -172,6 +208,7 @@ namespace ntfysh_client
 | 
			
		||||
            this.Controls.Add(this.label1);
 | 
			
		||||
            this.Controls.Add(this.panel1);
 | 
			
		||||
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
 | 
			
		||||
            this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
 | 
			
		||||
            this.MaximizeBox = false;
 | 
			
		||||
            this.MinimizeBox = false;
 | 
			
		||||
            this.Name = "SubscribeDialog";
 | 
			
		||||
@@ -179,6 +216,7 @@ namespace ntfysh_client
 | 
			
		||||
            this.ShowInTaskbar = false;
 | 
			
		||||
            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
 | 
			
		||||
            this.Text = "Subscribe to new topic";
 | 
			
		||||
            this.Load += new System.EventHandler(this.SubscribeDialog_Load);
 | 
			
		||||
            this.panel1.ResumeLayout(false);
 | 
			
		||||
            this.ResumeLayout(false);
 | 
			
		||||
            this.PerformLayout();
 | 
			
		||||
@@ -199,5 +237,7 @@ namespace ntfysh_client
 | 
			
		||||
        private System.Windows.Forms.Label label3;
 | 
			
		||||
        private System.Windows.Forms.TextBox password;
 | 
			
		||||
        private System.Windows.Forms.Label label4;
 | 
			
		||||
        private System.Windows.Forms.Label label5;
 | 
			
		||||
        private System.Windows.Forms.ComboBox connectionType;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -17,12 +17,107 @@ namespace ntfysh_client
 | 
			
		||||
 | 
			
		||||
        public string Unique => $"{topicId.Text}@{serverUrl.Text}";
 | 
			
		||||
 | 
			
		||||
        public bool UseWebsockets
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                switch (connectionType.Text)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Websockets (Recommended)":
 | 
			
		||||
                        return true;
 | 
			
		||||
 | 
			
		||||
                    case "Long HTTP JSON (Robust)":
 | 
			
		||||
                        return false;
 | 
			
		||||
 | 
			
		||||
                    default:
 | 
			
		||||
                        throw new InvalidOperationException();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public SubscribeDialog(ListBox notificationTopics)
 | 
			
		||||
        {
 | 
			
		||||
            _notificationTopics = notificationTopics;
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void SubscribeDialog_Load(object sender, EventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            connectionType.SelectedIndex = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool ReparseAddress()
 | 
			
		||||
        {
 | 
			
		||||
            //Separate schema and address
 | 
			
		||||
            string[] parts = serverUrl.Text.Split("://", 2);
 | 
			
		||||
 | 
			
		||||
            //Validate the basic formatting is correct
 | 
			
		||||
            if (parts.Length != 2) return false;
 | 
			
		||||
 | 
			
		||||
            //Take the schema aside for parsing
 | 
			
		||||
            string schema = parts[0].ToLower();
 | 
			
		||||
 | 
			
		||||
            //Ensure the schema is actually valid
 | 
			
		||||
            switch (schema)
 | 
			
		||||
            {
 | 
			
		||||
                case "http":
 | 
			
		||||
                case "https":
 | 
			
		||||
                case "ws":
 | 
			
		||||
                case "wss":
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                default:
 | 
			
		||||
                    return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //Correct the schema based on connection type if required
 | 
			
		||||
            if (UseWebsockets)
 | 
			
		||||
            {
 | 
			
		||||
                switch (schema)
 | 
			
		||||
                {
 | 
			
		||||
                    case "http":
 | 
			
		||||
                        schema = "ws";
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case "https":
 | 
			
		||||
                        schema = "wss";
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case "ws":
 | 
			
		||||
                    case "wss":
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                switch (schema)
 | 
			
		||||
                {
 | 
			
		||||
                    case "ws":
 | 
			
		||||
                        schema = "http";
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case "wss":
 | 
			
		||||
                        schema = "https";
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case "http":
 | 
			
		||||
                    case "https":
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //Reconstruct the address
 | 
			
		||||
            string finalAddress = schema + "://" + parts[1];
 | 
			
		||||
 | 
			
		||||
            //Validate the address
 | 
			
		||||
            if (!Uri.IsWellFormedUriString(finalAddress, UriKind.Absolute)) return false;
 | 
			
		||||
 | 
			
		||||
            //Set the final address and OK it
 | 
			
		||||
            serverUrl.Text = finalAddress;
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void button1_Click(object sender, EventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            if (topicId.Text.Length < 1)
 | 
			
		||||
@@ -35,7 +130,7 @@ namespace ntfysh_client
 | 
			
		||||
 | 
			
		||||
            if (serverUrl.Text.Length < 1)
 | 
			
		||||
            {
 | 
			
		||||
                MessageBox.Show("You must specify a server URL. The default is https://ntfy.sh", "Server URL not specified", MessageBoxButtons.OK, MessageBoxIcon.Error);
 | 
			
		||||
                MessageBox.Show("You must specify a server URL. The default is wss://ntfy.sh", "Server URL not specified", MessageBoxButtons.OK, MessageBoxIcon.Error);
 | 
			
		||||
                DialogResult = DialogResult.None;
 | 
			
		||||
                serverUrl.Focus();
 | 
			
		||||
                return;
 | 
			
		||||
@@ -65,6 +160,26 @@ namespace ntfysh_client
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (!ReparseAddress())
 | 
			
		||||
                {
 | 
			
		||||
                    MessageBox.Show($"The specified server URL is invalid. Accepted schemas are: http:// https:// ws:// wss://", "Invalid Server URL", MessageBoxButtons.OK, MessageBoxIcon.Error);
 | 
			
		||||
                    DialogResult = DialogResult.None;
 | 
			
		||||
                    connectionType.Focus();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch (InvalidOperationException)
 | 
			
		||||
            {
 | 
			
		||||
                MessageBox.Show($"The selected Connection Type '{connectionType.Text}' is invalid.", "Invalid Connection Type", MessageBoxButtons.OK, MessageBoxIcon.Error);
 | 
			
		||||
                DialogResult = DialogResult.None;
 | 
			
		||||
                connectionType.Focus();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            DialogResult = DialogResult.OK;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -108,5 +223,10 @@ namespace ntfysh_client
 | 
			
		||||
                e.SuppressKeyPress = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void connectionType_TextChanged(object sender, EventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            ReparseAddress();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,64 +1,4 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<root>
 | 
			
		||||
  <!-- 
 | 
			
		||||
    Microsoft ResX Schema 
 | 
			
		||||
    
 | 
			
		||||
    Version 2.0
 | 
			
		||||
    
 | 
			
		||||
    The primary goals of this format is to allow a simple XML format 
 | 
			
		||||
    that is mostly human readable. The generation and parsing of the 
 | 
			
		||||
    various data types are done through the TypeConverter classes 
 | 
			
		||||
    associated with the data types.
 | 
			
		||||
    
 | 
			
		||||
    Example:
 | 
			
		||||
    
 | 
			
		||||
    ... ado.net/XML headers & schema ...
 | 
			
		||||
    <resheader name="resmimetype">text/microsoft-resx</resheader>
 | 
			
		||||
    <resheader name="version">2.0</resheader>
 | 
			
		||||
    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
 | 
			
		||||
    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
 | 
			
		||||
    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
 | 
			
		||||
    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
 | 
			
		||||
    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
 | 
			
		||||
        <value>[base64 mime encoded serialized .NET Framework object]</value>
 | 
			
		||||
    </data>
 | 
			
		||||
    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
 | 
			
		||||
        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
 | 
			
		||||
        <comment>This is a comment</comment>
 | 
			
		||||
    </data>
 | 
			
		||||
                
 | 
			
		||||
    There are any number of "resheader" rows that contain simple 
 | 
			
		||||
    name/value pairs.
 | 
			
		||||
    
 | 
			
		||||
    Each data row contains a name, and value. The row also contains a 
 | 
			
		||||
    type or mimetype. Type corresponds to a .NET class that support 
 | 
			
		||||
    text/value conversion through the TypeConverter architecture. 
 | 
			
		||||
    Classes that don't support this are serialized and stored with the 
 | 
			
		||||
    mimetype set.
 | 
			
		||||
    
 | 
			
		||||
    The mimetype is used for serialized objects, and tells the 
 | 
			
		||||
    ResXResourceReader how to depersist the object. This is currently not 
 | 
			
		||||
    extensible. For a given mimetype the value must be set accordingly:
 | 
			
		||||
    
 | 
			
		||||
    Note - application/x-microsoft.net.object.binary.base64 is the format 
 | 
			
		||||
    that the ResXResourceWriter will generate, however the reader can 
 | 
			
		||||
    read any of the formats listed below.
 | 
			
		||||
    
 | 
			
		||||
    mimetype: application/x-microsoft.net.object.binary.base64
 | 
			
		||||
    value   : The object must be serialized with 
 | 
			
		||||
            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
 | 
			
		||||
            : and then encoded with base64 encoding.
 | 
			
		||||
    
 | 
			
		||||
    mimetype: application/x-microsoft.net.object.soap.base64
 | 
			
		||||
    value   : The object must be serialized with 
 | 
			
		||||
            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
 | 
			
		||||
            : and then encoded with base64 encoding.
 | 
			
		||||
 | 
			
		||||
    mimetype: application/x-microsoft.net.object.bytearray.base64
 | 
			
		||||
    value   : The object must be serialized into a byte array 
 | 
			
		||||
            : using a System.ComponentModel.TypeConverter
 | 
			
		||||
            : and then encoded with base64 encoding.
 | 
			
		||||
    -->
 | 
			
		||||
<root>
 | 
			
		||||
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
 | 
			
		||||
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
 | 
			
		||||
    <xsd:element name="root" msdata:IsDataSet="true">
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user