Skip to content
Merged
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
8 changes: 8 additions & 0 deletions Runtime/Client/LootLockerEndPoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ public class LootLockerEndPoints
public static EndPointClass uploadPlayerFile = new EndPointClass("player/files", LootLockerHTTPMethod.UPLOAD_FILE);
public static EndPointClass updatePlayerFile = new EndPointClass("/player/files/{0}", LootLockerHTTPMethod.UPDATE_FILE);
public static EndPointClass deletePlayerFile = new EndPointClass("/player/files/{0}", LootLockerHTTPMethod.DELETE);
public static EndPointClass listPlayerFileRevisions = new EndPointClass("player/files/{0}/revisions", LootLockerHTTPMethod.GET);
public static EndPointClass getPlayerFileRevision = new EndPointClass("player/files/{0}/revisions/{1}", LootLockerHTTPMethod.GET);
public static EndPointClass promotePlayerFileRevision = new EndPointClass("player/files/{0}/revisions/{1}/current", LootLockerHTTPMethod.POST);
public static EndPointClass getPlayerFileByKey = new EndPointClass("player/files/key/{0}", LootLockerHTTPMethod.GET);
public static EndPointClass listPlayerFileRevisionsByKey = new EndPointClass("player/files/key/{0}/revisions", LootLockerHTTPMethod.GET);
public static EndPointClass getPlayerFileRevisionByKey = new EndPointClass("player/files/key/{0}/revisions/{1}", LootLockerHTTPMethod.GET);
public static EndPointClass promotePlayerFileRevisionByKey = new EndPointClass("player/files/key/{0}/revisions/{1}/current", LootLockerHTTPMethod.POST);
public static EndPointClass deletePlayerFileByKey = new EndPointClass("player/files/key/{0}", LootLockerHTTPMethod.DELETE);

// Player Progressions
[Header("Player Progressions")]
Expand Down
331 changes: 328 additions & 3 deletions Runtime/Game/LootLockerSDKManager.cs

Large diffs are not rendered by default.

97 changes: 97 additions & 0 deletions Runtime/Game/Requests/PlayerRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,8 @@ public class LootLockerPlayerFile : LootLockerResponse
public string revision_id { get; set; }
/// <summary>The file name.</summary>
public string name { get; set; }
/// <summary>The optional key for upsert operations.</summary>
public string key { get; set; }
/// <summary>The file size in bytes.</summary>
public int size { get; set; }
/// <summary>The purpose or category tag for this file.</summary>
Expand All @@ -400,6 +402,53 @@ public class LootLockerPlayerFile : LootLockerResponse
public DateTime created_at { get; set; }
}

/// <summary>
/// Response containing a list of revisions for a player file.
/// </summary>
public class LootLockerPlayerFileRevisionsResponse : LootLockerResponse
{
/// <summary>The list of revisions.</summary>
public LootLockerPlayerFileContent[] revisions { get; set; }
/// <summary>Metadata about the file.</summary>
public LootLockerPlayerFileMetadata file { get; set; }
/// <summary>The ULID of the current (active) revision.</summary>
public string current_revision_id { get; set; }
}

/// <summary>
/// Metadata about a player file, returned as part of the revisions response.
/// </summary>
public class LootLockerPlayerFileMetadata
{
/// <summary>When the file was created.</summary>
public DateTime created_at { get; set; }
/// <summary>The file name.</summary>
public string name { get; set; }
/// <summary>The optional key for upsert operations.</summary>
public string key { get; set; }
/// <summary>The purpose or category tag for this file.</summary>
public string purpose { get; set; }
/// <summary>The unique identifier of this player file.</summary>
public int id { get; set; }
/// <summary>Whether this file is publicly accessible.</summary>
public bool is_public { get; set; }
}

/// <summary>
/// A single file revision with download URL and metadata.
/// </summary>
public class LootLockerPlayerFileContent : LootLockerResponse
{
/// <summary>The ULID of this revision.</summary>
public string id { get; set; }
/// <summary>The signed URL to download this revision.</summary>
public string url { get; set; }
/// <summary>The file size in bytes.</summary>
public int size { get; set; }
/// <summary>When this revision was created.</summary>
public DateTime created_at { get; set; }
}

/// <summary>
/// Response containing asset reward notifications for the current player.
/// </summary>
Expand Down Expand Up @@ -470,5 +519,53 @@ public static void LookupPlayer1stPartyPlatformIDs(string forPlayerWithUlid, Loo

LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.endPoint + queryParams.Build(), endPoint.httpMethod, null, onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); });
}

public static void ListPlayerFileRevisions(string forPlayerWithUlid, int fileId, Action<LootLockerPlayerFileRevisionsResponse> onComplete)
{
var endpoint = LootLockerEndPoints.listPlayerFileRevisions.WithPathParameter(fileId);
LootLockerServerRequest.CallAPI(forPlayerWithUlid, endpoint, LootLockerHTTPMethod.GET, onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); });
}

public static void GetPlayerFileRevision(string forPlayerWithUlid, int fileId, string revisionId, Action<LootLockerPlayerFileContent> onComplete)
{
var endpoint = LootLockerEndPoints.getPlayerFileRevision.WithPathParameters(fileId, revisionId);
LootLockerServerRequest.CallAPI(forPlayerWithUlid, endpoint, LootLockerHTTPMethod.GET, onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); });
}

public static void PromotePlayerFileRevision(string forPlayerWithUlid, int fileId, string revisionId, Action<LootLockerResponse> onComplete)
{
var endpoint = LootLockerEndPoints.promotePlayerFileRevision.WithPathParameters(fileId, revisionId);
LootLockerServerRequest.CallAPI(forPlayerWithUlid, endpoint, LootLockerHTTPMethod.POST, onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); });
}

public static void GetPlayerFileByKey(string forPlayerWithUlid, string key, Action<LootLockerPlayerFile> onComplete)
{
var endpoint = LootLockerEndPoints.getPlayerFileByKey.WithPathParameter(key);
LootLockerServerRequest.CallAPI(forPlayerWithUlid, endpoint, LootLockerHTTPMethod.GET, onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); });
}

public static void ListPlayerFileRevisionsByKey(string forPlayerWithUlid, string key, Action<LootLockerPlayerFileRevisionsResponse> onComplete)
{
var endpoint = LootLockerEndPoints.listPlayerFileRevisionsByKey.WithPathParameter(key);
LootLockerServerRequest.CallAPI(forPlayerWithUlid, endpoint, LootLockerHTTPMethod.GET, onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); });
}

public static void GetPlayerFileRevisionByKey(string forPlayerWithUlid, string key, string revisionId, Action<LootLockerPlayerFileContent> onComplete)
{
var endpoint = LootLockerEndPoints.getPlayerFileRevisionByKey.WithPathParameters(key, revisionId);
LootLockerServerRequest.CallAPI(forPlayerWithUlid, endpoint, LootLockerHTTPMethod.GET, onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); });
}

public static void PromotePlayerFileRevisionByKey(string forPlayerWithUlid, string key, string revisionId, Action<LootLockerResponse> onComplete)
{
var endpoint = LootLockerEndPoints.promotePlayerFileRevisionByKey.WithPathParameters(key, revisionId);
LootLockerServerRequest.CallAPI(forPlayerWithUlid, endpoint, LootLockerHTTPMethod.POST, onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); });
}

public static void DeletePlayerFileByKey(string forPlayerWithUlid, string key, Action<LootLockerResponse> onComplete)
{
var endpoint = LootLockerEndPoints.deletePlayerFileByKey.WithPathParameter(key);
LootLockerServerRequest.CallAPI(forPlayerWithUlid, endpoint, LootLockerHTTPMethod.DELETE, onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); });
}
}
}
38 changes: 20 additions & 18 deletions Runtime/Game/Requests/RemoteSessionRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,17 @@ public class LootLockerLeaseRemoteSessionRequest
/// The Game Version configured for the game
/// </summary>
public string game_version { get; set; }
/// <summary>
/// Optional list of identity providers to restrict the remote session to (e.g., "steam", "apple")
/// </summary>
public string[] providers { get; set; }

public LootLockerLeaseRemoteSessionRequest(string titleId, string environmentId)
public LootLockerLeaseRemoteSessionRequest(string titleId, string environmentId, string[] providers = null)
{
title_id = titleId;
environment_id = environmentId;
game_version = LootLockerConfig.current.game_version;
this.providers = providers;
}
}

Expand Down Expand Up @@ -379,7 +384,7 @@ protected IEnumerator ContinualPollingAction(Guid processGuid)
{
yield break;
}
yield return new WaitForSeconds(preProcess.PollingIntervalSeconds);
yield return new WaitForSecondsRealtime(preProcess.PollingIntervalSeconds);
while (_remoteSessionsProcesses.TryGetValue(processGuid, out var process))
{
// Check if we should continue the polling
Expand Down Expand Up @@ -418,13 +423,21 @@ protected IEnumerator ContinualPollingAction(Guid processGuid)
yield break;
}

// If the process was cancelled while the HTTP poll was in-flight, skip
// the status-update callback and let the next while-iteration handle it
// via the ShouldCancel check at the top of the loop.
if (processAfterStatusCheck.ShouldCancel)
{
continue;
}

if (!startSessionResponse.success)
{
if (startSessionResponse.statusCode >= 500 && startSessionResponse.statusCode <= 599 && processAfterStatusCheck.Retries <= _leasingProcessPollingRetryLimit)
{
// Recoverable error
processAfterStatusCheck.Retries++;
yield return new WaitForSeconds(processAfterStatusCheck.PollingIntervalSeconds);
yield return new WaitForSecondsRealtime(processAfterStatusCheck.PollingIntervalSeconds);
continue;
}

Expand All @@ -451,7 +464,7 @@ protected IEnumerator ContinualPollingAction(Guid processGuid)
processAfterStatusCheck.LastUpdatedStatus = pollingResponse.lease_status;

// Sleep for a bit before checking again
yield return new WaitForSeconds(processAfterStatusCheck.PollingIntervalSeconds);
yield return new WaitForSecondsRealtime(processAfterStatusCheck.PollingIntervalSeconds);
}
}

Expand Down Expand Up @@ -549,29 +562,18 @@ private void LeaseRemoteSession(
Action<LootLockerLeaseRemoteSessionResponse> onComplete,
string providerUrlParam = null)
{
string[] providers = string.IsNullOrEmpty(providerUrlParam) ? null : new[] { providerUrlParam };
LootLockerLeaseRemoteSessionRequest leaseRemoteSessionRequest =
new LootLockerLeaseRemoteSessionRequest(titleId, environmentId);
new LootLockerLeaseRemoteSessionRequest(titleId, environmentId, providers);

EndPointClass endPoint = leaseIntent == LootLockerRemoteSessionLeaseIntent.login ? LootLockerEndPoints.leaseRemoteSession : LootLockerEndPoints.leaseRemoteSessionForLinking;

LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.endPoint,
endPoint.httpMethod,
LootLockerJson.SerializeObject(leaseRemoteSessionRequest),
(serverResponse) =>
{
var response = LootLockerResponse.Deserialize<LootLockerLeaseRemoteSessionResponse>(serverResponse);
if (!string.IsNullOrEmpty(providerUrlParam) && response != null)
{
if (response.redirect_url != null)
{
string separator = response.redirect_url.Contains("?") ? "&" : "?";
response.redirect_url = response.redirect_url + separator + "provider=" + providerUrlParam;
}
if (response.display_url != null)
{
string separator = response.display_url.Contains("?") ? "&" : "?";
response.display_url = response.display_url + separator + "provider=" + providerUrlParam;
}
}
onComplete?.Invoke(response);
},
leaseIntent == LootLockerRemoteSessionLeaseIntent.link);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ public static class LootLockerTestConfigurationTitleConfig

public enum TitleConfigKeys
{
global_player_presence
global_player_presence,
white_label_custom_signup_fields
}

public class PresenceTitleConfigRequest
Expand All @@ -17,6 +18,21 @@ public class PresenceTitleConfigRequest
public bool advanced_mode { get; set; }
}

public class WhiteLabelCustomSignUpFieldDefinition
{
public string question_text { get; set; }
public string metadata_key { get; set; }
public string field_type { get; set; }
public bool required { get; set; }
public bool sensitive { get; set; }
public int sort_order { get; set; }
}

public class WhiteLabelCustomSignUpFieldsConfigRequest
{
public WhiteLabelCustomSignUpFieldDefinition[] fields { get; set; }
}

public static void GetGameConfig(TitleConfigKeys ConfigKey, Action<LootLockerResponse> onComplete)
{
if (string.IsNullOrEmpty(LootLockerConfig.current.adminToken))
Expand Down Expand Up @@ -52,5 +68,29 @@ public static void UpdateGameConfig(TitleConfigKeys ConfigKey, bool Enabled, boo
onComplete?.Invoke(serverResponse);
}, true);
}

public static void SetCustomSignUpFields(WhiteLabelCustomSignUpFieldDefinition[] fields, Action<bool /*success*/, string /*errorMessage*/> onComplete)
{
if (string.IsNullOrEmpty(LootLockerConfig.current.adminToken))
{
onComplete?.Invoke(false, "Not logged in");
return;
}

var request = new WhiteLabelCustomSignUpFieldsConfigRequest
{
fields = fields
};
string json = LootLockerJson.SerializeObject(request);
LootLockerTestGameAdmin.SetGameConfig("white_label_custom_signup_fields", json, response =>
{
if (response == null)
{
onComplete?.Invoke(false, "Null response from SetGameConfig");
return;
}
onComplete?.Invoke(response.success, response.errorData?.message);
});
}
}
}
Loading
Loading