Skip to content

Commit 2cfef04

Browse files
committed
Websocket exception handling
1 parent e70789e commit 2cfef04

File tree

7 files changed

+85
-17
lines changed

7 files changed

+85
-17
lines changed

Parse/Abstractions/Infrastructure/Execution/IWebSocketClient.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
2+
using System.IO;
23
using System.Threading;
34
using System.Threading.Tasks;
5+
using Parse.Infrastructure.Execution;
46

57
namespace Parse.Abstractions.Infrastructure.Execution;
68

@@ -16,7 +18,26 @@ public interface IWebSocketClient
1618
/// The event handler receives the message as a string parameter. This can be used to process incoming
1719
/// WebSocket messages, such as notifications, commands, or data updates.
1820
/// </remarks>
19-
public event EventHandler<string> MessageReceived;
21+
public event EventHandler<MessageReceivedEventArgs> MessageReceived;
22+
23+
/// <summary>
24+
/// An event that is triggered when an error occurs during the WebSocket operation.
25+
/// </summary>
26+
/// <remarks>
27+
/// This event communicates WebSocket-specific errors along with additional details encapsulated in
28+
/// the <see cref="ErrorEventArgs"/> object. It can be used to handle and log errors during WebSocket
29+
/// communication or connection lifecycle.
30+
/// </remarks>
31+
public event EventHandler<ErrorEventArgs> WebsocketError;
32+
33+
/// <summary>
34+
/// An event that is triggered when an unknown or unexpected error occurs during WebSocket communication.
35+
/// </summary>
36+
/// <remarks>
37+
/// This event can be used to handle errors that do not fall under typical WebSocket error events. The event
38+
/// handler receives an <see cref="ErrorEventArgs"/> parameter containing details about the error.
39+
/// </remarks>
40+
public event EventHandler<ErrorEventArgs> UnknownError;
2041

2142
/// <summary>
2243
/// Establishes a WebSocket connection to the specified server URI.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
3+
namespace Parse.Infrastructure.Execution;
4+
5+
/// <summary>
6+
/// Provides data for the event that is triggered when a message is received.
7+
/// </summary>
8+
public class MessageReceivedEventArgs(string message) : EventArgs
9+
{
10+
/// <summary>
11+
/// Gets the message content that was received.
12+
/// </summary>
13+
public string Message { get; } = message;
14+
}

Parse/Infrastructure/Execution/TextWebSocketClient.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
22
using System.Diagnostics;
3+
using System.IO;
4+
using System.Net;
35
using System.Net.WebSockets;
46
using System.Text;
57
using System.Threading;
@@ -39,7 +41,9 @@ class TextWebSocketClient(int bufferSize) : IWebSocketClient
3941
/// represented as a string. Handlers for this event can process or respond to the message
4042
/// based on the application's requirements.
4143
/// </summary>
42-
public event EventHandler<string> MessageReceived;
44+
public event EventHandler<MessageReceivedEventArgs> MessageReceived;
45+
public event EventHandler<ErrorEventArgs> WebsocketError;
46+
public event EventHandler<ErrorEventArgs> UnknownError;
4347

4448
private readonly object connectionLock = new object();
4549

@@ -111,7 +115,7 @@ private async Task ListenForMessages(CancellationToken cancellationToken)
111115
if (result.EndOfMessage)
112116
{
113117
string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
114-
MessageReceived?.Invoke(this, message);
118+
MessageReceived?.Invoke(this, new MessageReceivedEventArgs(message));
115119
}
116120
else
117121
{
@@ -126,7 +130,7 @@ private async Task ListenForMessages(CancellationToken cancellationToken)
126130
messageBuilder.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
127131
}
128132
string fullMessage = messageBuilder.ToString();
129-
MessageReceived?.Invoke(this, fullMessage);
133+
MessageReceived?.Invoke(this, new MessageReceivedEventArgs(fullMessage));;
130134
}
131135
}
132136
}
@@ -135,15 +139,17 @@ private async Task ListenForMessages(CancellationToken cancellationToken)
135139
// Normal cancellation, no need to handle
136140
Debug.WriteLine($"Websocket connection was closed: {ex.Message}");
137141
}
138-
catch (WebSocketException e)
142+
catch (WebSocketException ex)
139143
{
140144
// WebSocket error, notify the user
141-
Debug.WriteLine($"Websocket error: {e.Message}");
145+
Debug.WriteLine($"Websocket error ({ex.ErrorCode}): {ex.Message}");
146+
WebsocketError?.Invoke(this, new ErrorEventArgs(ex));
142147
}
143-
catch (Exception e)
148+
catch (Exception ex)
144149
{
145150
// Unexpected error, notify the user
146-
Debug.WriteLine($"Unexpected error in Websocket listener: {e.Message}");
151+
Debug.WriteLine($"Unexpected error in Websocket listener: {ex.Message}");
152+
UnknownError?.Invoke(this, new ErrorEventArgs(ex));
147153
}
148154
Debug.WriteLine("Websocket ListenForMessage stopped");
149155
}

Parse/Platform/LiveQueries/ParseLiveQueryController.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
44
using System.Diagnostics;
5+
using System.IO;
56
using System.Linq;
7+
using System.Net.WebSockets;
68
using System.Threading;
79
using System.Threading.Tasks;
810
using Parse.Abstractions.Infrastructure.Data;
911
using Parse.Abstractions.Infrastructure.Execution;
1012
using Parse.Abstractions.Platform.LiveQueries;
1113
using Parse.Infrastructure.Data;
14+
using Parse.Infrastructure.Execution;
1215
using Parse.Infrastructure.Utilities;
1316

1417
namespace Parse.Platform.LiveQueries;
@@ -133,6 +136,7 @@ private void ProcessMessage(IDictionary<string, object> message)
133136

134137
switch (op)
135138
{
139+
// CONNECTION
136140
case "connected":
137141
ProcessConnectionMessage(message);
138142
break;
@@ -354,16 +358,16 @@ private async Task OpenAsync(CancellationToken cancellationToken = default)
354358
await WebSocketClient.OpenAsync(ParseClient.Instance.Services.LiveQueryServerConnectionData.ServerURI, cancellationToken);
355359
}
356360

357-
private void WebSocketClientOnMessageReceived(object sender, string e)
361+
private void WebSocketClientOnMessageReceived(object sender, MessageReceivedEventArgs args)
358362
{
359-
object parsed = JsonUtilities.Parse(e);
363+
object parsed = JsonUtilities.Parse(args.Message);
360364
if (parsed is IDictionary<string, object> message)
361365
{
362366
ProcessMessage(message);
363367
}
364368
else
365369
{
366-
Debug.WriteLine($"Invalid message format received: {e}");
370+
Debug.WriteLine($"Invalid message format received: {args.Message}");
367371
}
368372
}
369373

@@ -386,6 +390,8 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)
386390
_state = ParseLiveQueryState.Connecting;
387391
await OpenAsync(cancellationToken);
388392
WebSocketClient.MessageReceived += WebSocketClientOnMessageReceived;
393+
WebSocketClient.WebsocketError += WebSocketClientOnWebsocketError;
394+
WebSocketClient.UnknownError += WebSocketClientOnUnknownError;
389395
Dictionary<string, object> message = new Dictionary<string, object>
390396
{
391397
{ "op", "connect" },
@@ -423,6 +429,22 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)
423429
}
424430
}
425431

432+
void WebSocketClientOnWebsocketError(object sender, ErrorEventArgs args)
433+
{
434+
if (args.GetException() is WebSocketException ex)
435+
{
436+
Error?.Invoke(this, new ParseLiveQueryErrorEventArgs(ex.ErrorCode, ex.Message, false, ex));
437+
}
438+
}
439+
440+
void WebSocketClientOnUnknownError(object sender, ErrorEventArgs args)
441+
{
442+
if (args.GetException() is { } ex)
443+
{
444+
Error?.Invoke(this, new ParseLiveQueryErrorEventArgs(-1, ex.Message, false, ex));
445+
}
446+
}
447+
426448
private async Task SendAndWaitForSignalAsync(IDictionary<string, object> message,
427449
ConcurrentDictionary<int, TaskCompletionSource> signalDictionary,
428450
int requestId,
@@ -560,6 +582,8 @@ public async Task UnsubscribeAsync(int requestId, CancellationToken cancellation
560582
public async Task CloseAsync(CancellationToken cancellationToken = default)
561583
{
562584
WebSocketClient.MessageReceived -= WebSocketClientOnMessageReceived;
585+
WebSocketClient.WebsocketError -= WebSocketClientOnWebsocketError;
586+
WebSocketClient.UnknownError -= WebSocketClientOnUnknownError;
563587
await WebSocketClient.CloseAsync(cancellationToken);
564588
_state = ParseLiveQueryState.Closed;
565589
SubscriptionSignals.Clear();

Parse/Platform/LiveQueries/ParseLiveQueryDualEventArgs.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class ParseLiveQueryDualEventArgs : ParseLiveQueryEventArgs
1515
/// providing a snapshot of its previous state for comparison purposes during events
1616
/// such as updates or deletes.
1717
/// </summary>
18-
public ParseObject Original { get; private set; }
18+
public ParseObject Original { get; }
1919

2020
internal ParseLiveQueryDualEventArgs(ParseObject current, ParseObject original) : base(current) =>
2121
Original = original ?? throw new ArgumentNullException(nameof(original));

Parse/Platform/LiveQueries/ParseLiveQueryErrorEventArgs.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class ParseLiveQueryErrorEventArgs : EventArgs
1515
/// a live query operation. It can provide detailed information about the nature of the issue,
1616
/// which can be helpful for debugging or logging purposes.
1717
/// </remarks>
18-
public string Error { get; private set; }
18+
public string Error { get; }
1919

2020
/// <summary>
2121
/// Gets or sets the error code associated with a live query operation.
@@ -25,7 +25,7 @@ public class ParseLiveQueryErrorEventArgs : EventArgs
2525
/// the type or category of the error that occurred during a live query operation.
2626
/// This is used alongside the error message to provide detailed diagnostics or logging.
2727
/// </remarks>
28-
public int Code { get; private set; }
28+
public int Code { get; }
2929

3030
/// <summary>
3131
/// Gets or sets a value indicating whether the client should attempt to reconnect
@@ -37,15 +37,18 @@ public class ParseLiveQueryErrorEventArgs : EventArgs
3737
/// This can be used to determine the client's behavior in maintaining a continuous
3838
/// connection with the server.
3939
/// </remarks>
40-
public bool Reconnect { get; private set; }
40+
public bool Reconnect { get; }
41+
42+
public Exception LocalException { get; }
4143

4244
/// <summary>
4345
/// Represents the arguments for an error event that occurs during a live query in the Parse platform.
4446
/// </summary>
45-
internal ParseLiveQueryErrorEventArgs(int code, string error, bool reconnect)
47+
internal ParseLiveQueryErrorEventArgs(int code, string error, bool reconnect, Exception localException = null)
4648
{
4749
Error = error;
4850
Code = code;
4951
Reconnect = reconnect;
52+
LocalException = localException;
5053
}
5154
}

Parse/Platform/LiveQueries/ParseLiveQueryEventArgs.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class ParseLiveQueryEventArgs : EventArgs
1515
/// the event was triggered, reflecting any changes made during operations such as
1616
/// an update or creation.
1717
/// </summary>
18-
public ParseObject Object { get; private set; }
18+
public ParseObject Object { get; }
1919

2020
internal ParseLiveQueryEventArgs(ParseObject current) => Object = current ?? throw new ArgumentNullException(nameof(current));
2121
}

0 commit comments

Comments
 (0)