Skip to content

feat: Add livequery support #411

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Parse/Abstractions/Infrastructure/CustomServiceHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Parse.Abstractions.Platform.Configuration;
using Parse.Abstractions.Platform.Files;
using Parse.Abstractions.Platform.Installations;
using Parse.Abstractions.Platform.LiveQueries;
using Parse.Abstractions.Platform.Objects;
using Parse.Abstractions.Platform.Push;
using Parse.Abstractions.Platform.Queries;
Expand All @@ -31,6 +32,8 @@ public abstract class CustomServiceHub : ICustomServiceHub

public virtual IParseCommandRunner CommandRunner => Services.CommandRunner;

public virtual IWebSocketClient WebSocketClient => Services.WebSocketClient;

public virtual IParseCloudCodeController CloudCodeController => Services.CloudCodeController;

public virtual IParseConfigurationController ConfigurationController => Services.ConfigurationController;
Expand All @@ -41,6 +44,8 @@ public abstract class CustomServiceHub : ICustomServiceHub

public virtual IParseQueryController QueryController => Services.QueryController;

public virtual IParseLiveQueryController LiveQueryController => Services.LiveQueryController;

public virtual IParseSessionController SessionController => Services.SessionController;

public virtual IParseUserController UserController => Services.UserController;
Expand All @@ -59,6 +64,8 @@ public abstract class CustomServiceHub : ICustomServiceHub

public virtual IServerConnectionData ServerConnectionData => Services.ServerConnectionData;

public virtual ILiveQueryServerConnectionData LiveQueryServerConnectionData => Services.LiveQueryServerConnectionData;

public virtual IParseDataDecoder Decoder => Services.Decoder;

public virtual IParseInstallationDataFinalizer InstallationDataFinalizer => Services.InstallationDataFinalizer;
Expand Down
50 changes: 50 additions & 0 deletions Parse/Abstractions/Infrastructure/Execution/IWebSocketClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Parse.Abstractions.Infrastructure.Execution;

/// <summary>
/// Represents an interface for a WebSocket client to handle WebSocket connections and communications.
/// </summary>
public interface IWebSocketClient
{
/// <summary>
/// An event that is triggered when a message is received via the WebSocket connection.
/// </summary>
/// <remarks>
/// The event handler receives the message as a string parameter. This can be used to process incoming
/// WebSocket messages, such as notifications, commands, or data updates.
/// </remarks>
public event EventHandler<string> MessageReceived;

/// <summary>
/// Establishes a WebSocket connection to the specified server URI.
/// </summary>
/// <param name="serverUri">The URI of the WebSocket server to connect to.</param>
/// <param name="cancellationToken">
/// A token to observe cancellation requests. The operation will stop if the token is canceled.
/// </param>
/// <returns>A task that represents the asynchronous operation of opening the WebSocket connection.</returns>
public Task OpenAsync(string serverUri, CancellationToken cancellationToken = default);

/// <summary>
/// Closes the active WebSocket connection asynchronously.
/// </summary>
/// <param name="cancellationToken">
/// A token to observe cancellation requests. The operation will stop if the token is canceled.
/// </param>
/// <returns>A task that represents the asynchronous operation of closing the WebSocket connection.</returns>
public Task CloseAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Sends a message over the established WebSocket connection asynchronously.
/// </summary>
/// <param name="message">The message to send through the WebSocket connection.</param>
/// <param name="cancellationToken">
/// A token to observe cancellation requests. The operation will stop if the token is canceled.
/// </param>
/// <returns>A task that represents the asynchronous operation of sending the message.</returns>
/// <exception cref="InvalidOperationException">Thrown when trying to send a message on a WebSocket connection that is not in the Open state.</exception>
public Task SendAsync(string message, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Parse.Abstractions.Infrastructure;

public interface ILiveQueryServerConnectionData : IServerConnectionData
{
/// <summary>
/// Represents the default timeout duration, in milliseconds.
/// </summary>
public const int DefaultTimeOut = 5000; // 5 seconds

/// <summary>
/// The timeout duration, in milliseconds, used for various operations, such as
/// establishing a connection or completing a subscription.
/// </summary>
int TimeOut { get; set; }

/// <summary>
/// The default buffer size, in bytes.
/// </summary>
public const int DefaultBufferSize = 4096; // 4KB

/// <summary>
/// The buffer size, in bytes, used for the WebSocket operations to handle incoming messages.
/// </summary>
int MessageBufferSize { get; set; }
}
4 changes: 4 additions & 0 deletions Parse/Abstractions/Infrastructure/IMutableServiceHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Parse.Abstractions.Platform.Configuration;
using Parse.Abstractions.Platform.Files;
using Parse.Abstractions.Platform.Installations;
using Parse.Abstractions.Platform.LiveQueries;
using Parse.Abstractions.Platform.Objects;
using Parse.Abstractions.Platform.Push;
using Parse.Abstractions.Platform.Queries;
Expand All @@ -18,6 +19,7 @@ namespace Parse.Abstractions.Infrastructure;
public interface IMutableServiceHub : IServiceHub
{
IServerConnectionData ServerConnectionData { set; }
ILiveQueryServerConnectionData LiveQueryServerConnectionData { set; }
IMetadataController MetadataController { set; }

IServiceHubCloner Cloner { set; }
Expand All @@ -30,12 +32,14 @@ public interface IMutableServiceHub : IServiceHub

IParseInstallationController InstallationController { set; }
IParseCommandRunner CommandRunner { set; }
IWebSocketClient WebSocketClient { set; }

IParseCloudCodeController CloudCodeController { set; }
IParseConfigurationController ConfigurationController { set; }
IParseFileController FileController { set; }
IParseObjectController ObjectController { set; }
IParseQueryController QueryController { set; }
IParseLiveQueryController LiveQueryController { set; }
IParseSessionController SessionController { set; }
IParseUserController UserController { set; }
IParseCurrentUserController CurrentUserController { set; }
Expand Down
6 changes: 5 additions & 1 deletion Parse/Abstractions/Infrastructure/IServiceHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Parse.Abstractions.Platform.Configuration;
using Parse.Abstractions.Platform.Files;
using Parse.Abstractions.Platform.Installations;
using Parse.Abstractions.Platform.LiveQueries;
using Parse.Abstractions.Platform.Objects;
using Parse.Abstractions.Platform.Push;
using Parse.Abstractions.Platform.Queries;
Expand All @@ -23,9 +24,10 @@ namespace Parse.Abstractions.Infrastructure;
public interface IServiceHub
{
/// <summary>
/// The current server connection data that the the Parse SDK has been initialized with.
/// The current server connection data that the Parse SDK has been initialized with.
/// </summary>
IServerConnectionData ServerConnectionData { get; }
ILiveQueryServerConnectionData LiveQueryServerConnectionData { get; }
IMetadataController MetadataController { get; }

IServiceHubCloner Cloner { get; }
Expand All @@ -38,12 +40,14 @@ public interface IServiceHub

IParseInstallationController InstallationController { get; }
IParseCommandRunner CommandRunner { get; }
IWebSocketClient WebSocketClient { get; }

IParseCloudCodeController CloudCodeController { get; }
IParseConfigurationController ConfigurationController { get; }
IParseFileController FileController { get; }
IParseObjectController ObjectController { get; }
IParseQueryController QueryController { get; }
IParseLiveQueryController LiveQueryController { get; }
IParseSessionController SessionController { get; }
IParseUserController UserController { get; }
IParseCurrentUserController CurrentUserController { get; }
Expand Down
115 changes: 115 additions & 0 deletions Parse/Abstractions/Platform/LiveQueries/IParseLiveQueryController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Parse.Platform.LiveQueries;

namespace Parse.Abstractions.Platform.LiveQueries;

/// <summary>
/// Defines an interface for managing LiveQuery connections, subscriptions, and updates
/// in a Parse Server environment.
/// </summary>
public interface IParseLiveQueryController
{
/// <summary>
/// Event triggered when an error occurs during the operation of the ParseLiveQueryController.
/// </summary>
/// <remarks>
/// This event provides details about a live query operation failure, such as specific error messages,
/// error codes, and whether automatic reconnection is recommended.
/// It is raised in scenarios like:
/// - Receiving an error response from the LiveQuery server.
/// - Issues with subscriptions, unsubscriptions, or query updates.
/// Subscribers to this event can use the provided <see cref="ParseLiveQueryErrorEventArgs"/> to
/// understand the error and implement appropriate handling mechanisms.
/// </remarks>
public event EventHandler<ParseLiveQueryErrorEventArgs> Error;

/// <summary>
/// Establishes a connection to the live query server asynchronously.
/// </summary>
/// <param name="cancellationToken">
/// A cancellation token that can be used to cancel the connection process. If the token is triggered,
/// the connection process will be terminated.
/// </param>
/// <returns>
/// A task that represents the asynchronous connection operation.
/// </returns>
/// <exception cref="TimeoutException">
/// Thrown when the connection request times out before receiving confirmation from the server.
/// </exception>
Task ConnectAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Subscribes to a live query, enabling real-time updates for the specified query object.
/// </summary>
/// <typeparam name="T">
/// The type of the ParseObject associated with the live query.
/// </typeparam>
/// <param name="liveQuery">
/// The live query instance to subscribe to. It contains details about the query and its parameters.
/// </param>
/// <param name="cancellationToken">
/// A token to monitor for cancellation requests. It allows the operation to be canceled if requested.
/// </param>
/// <returns>
/// An object representing the active subscription for the specified query, enabling interaction with the subscribed events and updates.
/// </returns>
/// <exception cref="InvalidOperationException">
/// Thrown when attempting to subscribe while the live query connection is in a closed state.
/// </exception>
/// <exception cref="TimeoutException">
/// Thrown when the subscription request times out before receiving confirmation from the server.
/// </exception>
Task<IParseLiveQuerySubscription> SubscribeAsync<T>(ParseLiveQuery<T> liveQuery, CancellationToken cancellationToken = default) where T : ParseObject;

/// <summary>
/// Updates an active subscription. This method modifies the parameters of an existing subscription for a specific query.
/// </summary>
/// <param name="liveQuery">
/// The live query object that holds the query parameters to be updated.
/// </param>
/// <param name="requestId">
/// The unique identifier of the subscription to update.
/// </param>
/// <param name="cancellationToken">
/// A token to monitor for cancellation requests, allowing the operation to be cancelled before completion.
/// </param>
/// <typeparam name="T">
/// The type of the ParseObject that the query targets.
/// </typeparam>
/// <returns>
/// A task that represents the asynchronous operation of updating the subscription.
/// </returns>
Task UpdateSubscriptionAsync<T>(ParseLiveQuery<T> liveQuery, int requestId, CancellationToken cancellationToken = default) where T : ParseObject;

/// <summary>
/// Unsubscribes from a live query subscription associated with the given request identifier.
/// </summary>
/// <param name="requestId">
/// The unique identifier of the subscription to unsubscribe from.
/// </param>
/// <param name="cancellationToken">
/// A cancellation token that can be used to cancel the unsubscription operation before completion.
/// </param>
/// <returns>
/// A task that represents the asynchronous unsubscription operation.
/// </returns>
/// <exception cref="TimeoutException">
/// Thrown if the unsubscription process does not complete within the specified timeout period.
/// </exception>
Task UnsubscribeAsync(int requestId, CancellationToken cancellationToken = default);

/// <summary>
/// Closes the live query connection asynchronously.
/// </summary>
/// <param name="cancellationToken">
/// A token to monitor for cancellation requests while closing the live query connection.
/// If the operation is canceled, the task will terminate early.
/// </param>
/// <returns>
/// A task that represents the asynchronous operation of closing the live query connection.
/// The task completes when the connection is fully closed and resources are cleaned up.
/// </returns>
Task CloseAsync(CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Parse.Abstractions.Platform.Objects;
using Parse.Platform.LiveQueries;

namespace Parse.Abstractions.Platform.LiveQueries;

public interface IParseLiveQuerySubscription
{
/// <summary>
/// Represents the Create event for a live query subscription.
/// This event is triggered when a new object matching the subscription's query is created.
/// </summary>
event EventHandler<ParseLiveQueryEventArgs> Create;

/// <summary>
/// Represents the Enter event for a live query subscription.
/// This event is triggered when an object that did not previously match the query (and was thus not part of the subscription)
/// starts matching the query, typically due to an update.
/// </summary>
event EventHandler<ParseLiveQueryDualEventArgs> Enter;

/// <summary>
/// Represents the Update event for a live query subscription.
/// This event is triggered when an existing object matching the subscription's query is updated.
/// </summary>
event EventHandler<ParseLiveQueryDualEventArgs> Update;

/// <summary>
/// Represents the Leave event for a live query subscription.
/// This event is triggered when an object that previously matched the subscription's query
/// no longer matches the criteria and is removed.
/// </summary>
event EventHandler<ParseLiveQueryDualEventArgs> Leave;

/// <summary>
/// Represents the Delete event for a live query subscription.
/// This event is triggered when an object matching the subscription's query is deleted.
/// </summary>
event EventHandler<ParseLiveQueryEventArgs> Delete;

/// <summary>
/// Updates the current live query subscription with new query parameters,
/// effectively modifying the subscription to reflect the provided live query.
/// This allows adjustments to the filter or watched keys without unsubscribing
/// and re-subscribing.
/// </summary>
/// <typeparam name="T">The type of the ParseObject associated with the subscription.</typeparam>
/// <param name="liveQuery">The updated live query containing new parameters that
/// will replace the existing ones for this subscription.</param>
/// <param name="cancellationToken">A token to monitor for cancellation requests. If triggered,
/// the update process will be halted.</param>
/// <returns>A task that represents the asynchronous operation of updating
/// the subscription with the new query parameters.</returns>
Task UpdateAsync<T>(ParseLiveQuery<T> liveQuery, CancellationToken cancellationToken = default) where T : ParseObject;

/// <summary>
/// Cancels the current live query subscription by unsubscribing from the Parse Live Query server.
/// This ensures that the client will no longer receive real-time updates or notifications
/// associated with this subscription.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests. If triggered, the cancellation process will halt.</param>
/// <returns>A task that represents the asynchronous operation of canceling the subscription.</returns>
Task CancelAsync(CancellationToken cancellationToken = default);

internal void OnCreate(IObjectState objectState);
internal void OnEnter(IObjectState objectState, IObjectState originalState);
internal void OnUpdate(IObjectState objectState, IObjectState originalState);
internal void OnLeave(IObjectState objectState, IObjectState originalState);
internal void OnDelete(IObjectState objectState);
}
Loading