diff --git a/CHANGELOG.md b/CHANGELOG.md
index b110dadf..5470dd7a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
## [Unreleased]
+## [3.12.0] - 2024-04-08
+### Added
+- Satori: Added `IApiLiveEvent.Id` for accessing live event identifiers.
+- Satori: Added support for new Satori Messages API: `IClient.GetMessageListAsync`, `IClient.UpdateMessageAsync` and `IClient.DeleteMessageAsync`.
+
## [3.11.0] - 2024-03-08
### Added
- Nakama: New `IClient` event called `ReceivedSessionUpdated` when session expires and is refreshed.
diff --git a/Satori/ApiClient.gen.cs b/Satori/ApiClient.gen.cs
index afddbffc..4619edeb 100644
--- a/Satori/ApiClient.gen.cs
+++ b/Satori/ApiClient.gen.cs
@@ -40,6 +40,44 @@ public override string ToString()
}
}
+ ///
+ /// The request to update the status of a message.
+ ///
+ public interface IApiUpdateMessageRequest
+ {
+
+ ///
+ /// The time the message was consumed by the identity.
+ ///
+ string ConsumeTime { get; }
+
+ ///
+ /// The time the message was read at the client.
+ ///
+ string ReadTime { get; }
+ }
+
+ ///
+ internal class ApiUpdateMessageRequest : IApiUpdateMessageRequest
+ {
+
+ ///
+ [DataMember(Name="consume_time"), Preserve]
+ public string ConsumeTime { get; set; }
+
+ ///
+ [DataMember(Name="read_time"), Preserve]
+ public string ReadTime { get; set; }
+
+ public override string ToString()
+ {
+ var output = "";
+ output = string.Concat(output, "ConsumeTime: ", ConsumeTime, ", ");
+ output = string.Concat(output, "ReadTime: ", ReadTime, ", ");
+ return output;
+ }
+ }
+
///
/// Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user.
///
@@ -422,6 +460,66 @@ public override string ToString()
}
}
+ ///
+ /// A response containing all the messages for an identity.
+ ///
+ public interface IApiGetMessageListResponse
+ {
+
+ ///
+ /// Cacheable cursor to list newer messages. Durable and designed to be stored, unlike next/prev cursors.
+ ///
+ string CacheableCursor { get; }
+
+ ///
+ /// The list of messages.
+ ///
+ IEnumerable Messages { get; }
+
+ ///
+ /// The cursor to send when retrieving the next page, if any.
+ ///
+ string NextCursor { get; }
+
+ ///
+ /// The cursor to send when retrieving the previous page, if any.
+ ///
+ string PrevCursor { get; }
+ }
+
+ ///
+ internal class ApiGetMessageListResponse : IApiGetMessageListResponse
+ {
+
+ ///
+ [DataMember(Name="cacheable_cursor"), Preserve]
+ public string CacheableCursor { get; set; }
+
+ ///
+ [IgnoreDataMember]
+ public IEnumerable Messages => _messages ?? new List(0);
+ [DataMember(Name="messages"), Preserve]
+ public List _messages { get; set; }
+
+ ///
+ [DataMember(Name="next_cursor"), Preserve]
+ public string NextCursor { get; set; }
+
+ ///
+ [DataMember(Name="prev_cursor"), Preserve]
+ public string PrevCursor { get; set; }
+
+ public override string ToString()
+ {
+ var output = "";
+ output = string.Concat(output, "CacheableCursor: ", CacheableCursor, ", ");
+ output = string.Concat(output, "Messages: [", string.Join(", ", Messages), "], ");
+ output = string.Concat(output, "NextCursor: ", NextCursor, ", ");
+ output = string.Concat(output, "PrevCursor: ", PrevCursor, ", ");
+ return output;
+ }
+ }
+
///
/// Enrich/replace the current session with a new ID.
///
@@ -507,6 +605,11 @@ public interface IApiLiveEvent
///
string Description { get; }
+ ///
+ /// The live event identifier.
+ ///
+ string Id { get; }
+
///
/// Name.
///
@@ -534,6 +637,10 @@ internal class ApiLiveEvent : IApiLiveEvent
[DataMember(Name="description"), Preserve]
public string Description { get; set; }
+ ///
+ [DataMember(Name="id"), Preserve]
+ public string Id { get; set; }
+
///
[DataMember(Name="name"), Preserve]
public string Name { get; set; }
@@ -548,6 +655,7 @@ public override string ToString()
output = string.Concat(output, "ActiveEndTimeSec: ", ActiveEndTimeSec, ", ");
output = string.Concat(output, "ActiveStartTimeSec: ", ActiveStartTimeSec, ", ");
output = string.Concat(output, "Description: ", Description, ", ");
+ output = string.Concat(output, "Id: ", Id, ", ");
output = string.Concat(output, "Name: ", Name, ", ");
output = string.Concat(output, "Value: ", Value, ", ");
return output;
@@ -584,6 +692,122 @@ public override string ToString()
}
}
+ ///
+ /// A scheduled message.
+ ///
+ public interface IApiMessage
+ {
+
+ ///
+ /// The time the message was consumed by the identity.
+ ///
+ string ConsumeTime { get; }
+
+ ///
+ /// The time the message was created.
+ ///
+ string CreateTime { get; }
+
+ ///
+ /// The message's unique identifier.
+ ///
+ string Id { get; }
+
+ ///
+ /// A key-value pairs of metadata.
+ ///
+ IDictionary Metadata { get; }
+
+ ///
+ /// The time the message was read by the client.
+ ///
+ string ReadTime { get; }
+
+ ///
+ /// The identifier of the schedule.
+ ///
+ string ScheduleId { get; }
+
+ ///
+ /// The send time for the message.
+ ///
+ string SendTime { get; }
+
+ ///
+ /// The message's text.
+ ///
+ string Text { get; }
+
+ ///
+ /// The time the message was updated.
+ ///
+ string UpdateTime { get; }
+ }
+
+ ///
+ internal class ApiMessage : IApiMessage
+ {
+
+ ///
+ [DataMember(Name="consume_time"), Preserve]
+ public string ConsumeTime { get; set; }
+
+ ///
+ [DataMember(Name="create_time"), Preserve]
+ public string CreateTime { get; set; }
+
+ ///
+ [DataMember(Name="id"), Preserve]
+ public string Id { get; set; }
+
+ ///
+ [IgnoreDataMember]
+ public IDictionary Metadata => _metadata ?? new Dictionary();
+ [DataMember(Name="metadata"), Preserve]
+ public Dictionary _metadata { get; set; }
+
+ ///
+ [DataMember(Name="read_time"), Preserve]
+ public string ReadTime { get; set; }
+
+ ///
+ [DataMember(Name="schedule_id"), Preserve]
+ public string ScheduleId { get; set; }
+
+ ///
+ [DataMember(Name="send_time"), Preserve]
+ public string SendTime { get; set; }
+
+ ///
+ [DataMember(Name="text"), Preserve]
+ public string Text { get; set; }
+
+ ///
+ [DataMember(Name="update_time"), Preserve]
+ public string UpdateTime { get; set; }
+
+ public override string ToString()
+ {
+ var output = "";
+ output = string.Concat(output, "ConsumeTime: ", ConsumeTime, ", ");
+ output = string.Concat(output, "CreateTime: ", CreateTime, ", ");
+ output = string.Concat(output, "Id: ", Id, ", ");
+
+ var metadataString = "";
+ foreach (var kvp in Metadata)
+ {
+ metadataString = string.Concat(metadataString, "{" + kvp.Key + "=" + kvp.Value + "}");
+ }
+ output = string.Concat(output, "Metadata: [" + metadataString + "]");
+ output = string.Concat(output, "ReadTime: ", ReadTime, ", ");
+ output = string.Concat(output, "ScheduleId: ", ScheduleId, ", ");
+ output = string.Concat(output, "SendTime: ", SendTime, ", ");
+ output = string.Concat(output, "Text: ", Text, ", ");
+ output = string.Concat(output, "UpdateTime: ", UpdateTime, ", ");
+ return output;
+ }
+ }
+
///
/// Properties associated with an identity.
///
@@ -877,9 +1101,11 @@ public async Task SatoriHealthcheckAsync(
var queryParams = "";
+ string path = _baseUri.AbsolutePath.TrimEnd('/') + urlpath;
+
var uri = new UriBuilder(_baseUri)
{
- Path = urlpath,
+ Path = path,
Query = queryParams
}.Uri;
@@ -904,9 +1130,11 @@ public async Task SatoriReadycheckAsync(
var queryParams = "";
+ string path = _baseUri.AbsolutePath.TrimEnd('/') + urlpath;
+
var uri = new UriBuilder(_baseUri)
{
- Path = urlpath,
+ Path = path,
Query = queryParams
}.Uri;
@@ -937,9 +1165,11 @@ public async Task SatoriAuthenticateAsync(
var queryParams = "";
+ string path = _baseUri.AbsolutePath.TrimEnd('/') + urlpath;
+
var uri = new UriBuilder(_baseUri)
{
- Path = urlpath,
+ Path = path,
Query = queryParams
}.Uri;
@@ -976,9 +1206,11 @@ public async Task SatoriAuthenticateLogoutAsync(
var queryParams = "";
+ string path = _baseUri.AbsolutePath.TrimEnd('/') + urlpath;
+
var uri = new UriBuilder(_baseUri)
{
- Path = urlpath,
+ Path = path,
Query = queryParams
}.Uri;
@@ -1011,9 +1243,11 @@ public async Task SatoriAuthenticateRefreshAsync(
var queryParams = "";
+ string path = _baseUri.AbsolutePath.TrimEnd('/') + urlpath;
+
var uri = new UriBuilder(_baseUri)
{
- Path = urlpath,
+ Path = path,
Query = queryParams
}.Uri;
@@ -1050,9 +1284,11 @@ public async Task SatoriEventAsync(
var queryParams = "";
+ string path = _baseUri.AbsolutePath.TrimEnd('/') + urlpath;
+
var uri = new UriBuilder(_baseUri)
{
- Path = urlpath,
+ Path = path,
Query = queryParams
}.Uri;
@@ -1084,9 +1320,11 @@ public async Task SatoriGetExperimentsAsync(
queryParams = string.Concat(queryParams, "names=", Uri.EscapeDataString(elem), "&");
}
+ string path = _baseUri.AbsolutePath.TrimEnd('/') + urlpath;
+
var uri = new UriBuilder(_baseUri)
{
- Path = urlpath,
+ Path = path,
Query = queryParams
}.Uri;
@@ -1119,9 +1357,11 @@ public async Task SatoriGetFlagsAsync(
queryParams = string.Concat(queryParams, "names=", Uri.EscapeDataString(elem), "&");
}
+ string path = _baseUri.AbsolutePath.TrimEnd('/') + urlpath;
+
var uri = new UriBuilder(_baseUri)
{
- Path = urlpath,
+ Path = path,
Query = queryParams
}.Uri;
@@ -1161,9 +1401,11 @@ public async Task SatoriIdentifyAsync(
var queryParams = "";
+ string path = _baseUri.AbsolutePath.TrimEnd('/') + urlpath;
+
var uri = new UriBuilder(_baseUri)
{
- Path = urlpath,
+ Path = path,
Query = queryParams
}.Uri;
@@ -1191,9 +1433,11 @@ public async Task SatoriDeleteIdentityAsync(
var queryParams = "";
+ string path = _baseUri.AbsolutePath.TrimEnd('/') + urlpath;
+
var uri = new UriBuilder(_baseUri)
{
- Path = urlpath,
+ Path = path,
Query = queryParams
}.Uri;
@@ -1223,9 +1467,11 @@ public async Task SatoriGetLiveEventsAsync(
queryParams = string.Concat(queryParams, "names=", Uri.EscapeDataString(elem), "&");
}
+ string path = _baseUri.AbsolutePath.TrimEnd('/') + urlpath;
+
var uri = new UriBuilder(_baseUri)
{
- Path = urlpath,
+ Path = path,
Query = queryParams
}.Uri;
@@ -1239,6 +1485,125 @@ public async Task SatoriGetLiveEventsAsync(
return contents.FromJson();
}
+ ///
+ /// Get the list of messages for the identity.
+ ///
+ public async Task SatoriGetMessageListAsync(
+ string bearerToken,
+ int? limit,
+ bool? forward,
+ string cursor,
+ CancellationToken? cancellationToken)
+ {
+
+ var urlpath = "/v1/message";
+
+ var queryParams = "";
+ if (limit != null) {
+ queryParams = string.Concat(queryParams, "limit=", limit, "&");
+ }
+ if (forward != null) {
+ queryParams = string.Concat(queryParams, "forward=", forward.ToString().ToLower(), "&");
+ }
+ if (cursor != null) {
+ queryParams = string.Concat(queryParams, "cursor=", Uri.EscapeDataString(cursor), "&");
+ }
+
+ string path = _baseUri.AbsolutePath.TrimEnd('/') + urlpath;
+
+ var uri = new UriBuilder(_baseUri)
+ {
+ Path = path,
+ Query = queryParams
+ }.Uri;
+
+ var method = "GET";
+ var headers = new Dictionary();
+ var header = string.Concat("Bearer ", bearerToken);
+ headers.Add("Authorization", header);
+
+ byte[] content = null;
+ var contents = await HttpAdapter.SendAsync(method, uri, headers, content, Timeout, cancellationToken);
+ return contents.FromJson();
+ }
+
+ ///
+ /// Deletes a message for an identity.
+ ///
+ public async Task SatoriDeleteMessageAsync(
+ string bearerToken,
+ string id,
+ CancellationToken? cancellationToken)
+ {
+ if (id == null)
+ {
+ throw new ArgumentException("'id' is required but was null.");
+ }
+
+ var urlpath = "/v1/message/{id}";
+ urlpath = urlpath.Replace("{id}", Uri.EscapeDataString(id));
+
+ var queryParams = "";
+
+ string path = _baseUri.AbsolutePath.TrimEnd('/') + urlpath;
+
+ var uri = new UriBuilder(_baseUri)
+ {
+ Path = path,
+ Query = queryParams
+ }.Uri;
+
+ var method = "DELETE";
+ var headers = new Dictionary();
+ var header = string.Concat("Bearer ", bearerToken);
+ headers.Add("Authorization", header);
+
+ byte[] content = null;
+ await HttpAdapter.SendAsync(method, uri, headers, content, Timeout, cancellationToken);
+ }
+
+ ///
+ /// Updates a message for an identity.
+ ///
+ public async Task SatoriUpdateMessageAsync(
+ string bearerToken,
+ string id,
+ ApiUpdateMessageRequest body,
+ CancellationToken? cancellationToken)
+ {
+ if (id == null)
+ {
+ throw new ArgumentException("'id' is required but was null.");
+ }
+ if (body == null)
+ {
+ throw new ArgumentException("'body' is required but was null.");
+ }
+
+ var urlpath = "/v1/message/{id}";
+ urlpath = urlpath.Replace("{id}", Uri.EscapeDataString(id));
+
+ var queryParams = "";
+
+ string path = _baseUri.AbsolutePath.TrimEnd('/') + urlpath;
+
+ var uri = new UriBuilder(_baseUri)
+ {
+ Path = path,
+ Query = queryParams
+ }.Uri;
+
+ var method = "PUT";
+ var headers = new Dictionary();
+ var header = string.Concat("Bearer ", bearerToken);
+ headers.Add("Authorization", header);
+
+ byte[] content = null;
+ var jsonBody = body.ToJson();
+ content = Encoding.UTF8.GetBytes(jsonBody);
+ await HttpAdapter.SendAsync(method, uri, headers, content, Timeout, cancellationToken);
+ }
+
///
/// List properties associated with this identity.
///
@@ -1251,9 +1616,11 @@ public async Task SatoriListPropertiesAsync(
var queryParams = "";
+ string path = _baseUri.AbsolutePath.TrimEnd('/') + urlpath;
+
var uri = new UriBuilder(_baseUri)
{
- Path = urlpath,
+ Path = path,
Query = queryParams
}.Uri;
@@ -1284,9 +1651,11 @@ public async Task SatoriUpdatePropertiesAsync(
var queryParams = "";
+ string path = _baseUri.AbsolutePath.TrimEnd('/') + urlpath;
+
var uri = new UriBuilder(_baseUri)
{
- Path = urlpath,
+ Path = path,
Query = queryParams
}.Uri;
diff --git a/Satori/Client.cs b/Satori/Client.cs
index b3fb2aa6..24229b40 100644
--- a/Satori/Client.cs
+++ b/Satori/Client.cs
@@ -330,14 +330,14 @@ public async Task UpdatePropertiesAsync(ISession session, Dictionary
public async Task DeleteIdentityAsync(ISession session, CancellationToken? cancellationToken = default)
{
@@ -349,5 +349,41 @@ public async Task DeleteIdentityAsync(ISession session, CancellationToken? cance
await _apiClient.SatoriDeleteIdentityAsync(session.AuthToken, cancellationToken);
}
+
+ ///
+ public async Task GetMessageListAsync(ISession session, int limit = 1, bool forward = true, string cursor = null, CancellationToken? cancellationToken = default)
+ {
+ if (AutoRefreshSession && !string.IsNullOrEmpty(session.RefreshToken) &&
+ session.HasExpired(DateTime.UtcNow.Add(DefaultExpiredTimeSpan)))
+ {
+ await SessionRefreshAsync(session, cancellationToken);
+ }
+
+ return await _apiClient.SatoriGetMessageListAsync(session.AuthToken, limit, forward, cursor, cancellationToken);
+ }
+
+ ///
+ public async Task UpdateMessageAsync(ISession session, string id, string consumeTime, string readTime, CancellationToken? cancellationToken = default)
+ {
+ if (AutoRefreshSession && !string.IsNullOrEmpty(session.RefreshToken) &&
+ session.HasExpired(DateTime.UtcNow.Add(DefaultExpiredTimeSpan)))
+ {
+ await SessionRefreshAsync(session, cancellationToken);
+ }
+
+ await _apiClient.SatoriUpdateMessageAsync(session.AuthToken, id, new ApiUpdateMessageRequest{ConsumeTime = consumeTime, ReadTime = readTime}, cancellationToken);
+ }
+
+ ///
+ public async Task DeleteMessageAsync(ISession session, string id, CancellationToken? cancellationToken = default)
+ {
+ if (AutoRefreshSession && !string.IsNullOrEmpty(session.RefreshToken) &&
+ session.HasExpired(DateTime.UtcNow.Add(DefaultExpiredTimeSpan)))
+ {
+ await SessionRefreshAsync(session, cancellationToken);
+ }
+
+ await _apiClient.SatoriDeleteMessageAsync(session.AuthToken, id, cancellationToken);
+ }
}
-}
\ No newline at end of file
+}
diff --git a/Satori/IClient.cs b/Satori/IClient.cs
index cdd9ac83..d12fa8bf 100644
--- a/Satori/IClient.cs
+++ b/Satori/IClient.cs
@@ -211,5 +211,36 @@ public Task UpdatePropertiesAsync(ISession session, Dictionary d
/// The that can be used to cancel the request while mid-flight.
/// A task object.
public Task DeleteIdentityAsync(ISession session, CancellationToken? cancellationToken = default);
+
+ ///
+ /// Get all the messages for an identity.
+ ///
+ /// The session of the user.
+ /// Max number of messages to return. Between 1 and 100.
+ /// True if listing should be older messages to newer, false if reverse.
+ /// A pagination cursor, if any.
+ /// The that can be used to cancel the request while mid-flight.
+ /// A task object which resolves to a list of messages.
+ public Task GetMessageListAsync(ISession session, int limit = 1, bool forward = true, string cursor = null, CancellationToken? cancellationToken = default);
+
+ ///
+ /// Update the status of a message.
+ ///
+ /// The session of the user.
+ /// The message's unique identifier.
+ /// The time the message was consumed by the identity.
+ /// The time the message was read at the client.
+ /// The that can be used to cancel the request while mid-flight.
+ /// A task object.
+ public Task UpdateMessageAsync(ISession session, string id, string consumeTime, string readTime, CancellationToken? cancellationToken = default);
+
+ ///
+ /// Delete a scheduled message.
+ ///
+ /// The session of the user.
+ /// The identifier of the message.
+ /// The that can be used to cancel the request while mid-flight.
+ /// A task object.
+ public Task DeleteMessageAsync(ISession session, string id, CancellationToken? cancellationToken = default);
}
}