diff --git a/.github/workflows/run-tests-and-package.yml b/.github/workflows/run-tests-and-package.yml index d48dec107..9eb0f9b78 100644 --- a/.github/workflows/run-tests-and-package.yml +++ b/.github/workflows/run-tests-and-package.yml @@ -11,7 +11,6 @@ on: push: branches: # Made towards the following - main - - dev - ci/* workflow_dispatch: inputs: @@ -24,7 +23,6 @@ jobs: name: Test SDK in Editor runs-on: [ubuntu-latest] needs: [] -# if: ( true == false ) timeout-minutes: 10 env: LL_USE_STAGE: false @@ -99,7 +97,7 @@ jobs: Library- - name: Run Smoke Tests ${{ matrix.unityVersion }}-${{ matrix.testMode }} id: editor-smoke-tests-gameci - uses: game-ci/unity-test-runner@v4 + uses: game-ci/unity-test-runner@v4.3.1 env: UNITY_LICENSE: ${{ SECRETS.UNITY_LICENSE }} UNITY_EMAIL: ${{ SECRETS.UNITY_EMAIL }} @@ -116,6 +114,7 @@ jobs: name: Test SDK in StandaloneLinux64 build runs-on: [ubuntu-latest] needs: [editor-smoke-test] + if: false timeout-minutes: 10 env: LL_USE_STAGE: false @@ -199,7 +198,7 @@ jobs: Library- - name: Run Smoke Tests ${{ matrix.unityVersion }}-${{ matrix.testMode }}-${{ matrix.targetPlatform }} id: build-smoke-tests-gameci - uses: game-ci/unity-builder@v4 + uses: game-ci/unity-builder@v4.7.0 env: UNITY_LICENSE: ${{ SECRETS.UNITY_LICENSE }} UNITY_EMAIL: ${{ SECRETS.UNITY_EMAIL }} @@ -228,7 +227,7 @@ jobs: runs-on: [ubuntu-latest] needs: [editor-smoke-test] timeout-minutes: 20 - if: (startsWith(github.ref, 'refs/pull') && endsWith(github.base_ref, 'main')) || startsWith(github.ref, 'refs/tags/v') || (startsWith(github.ref, 'refs/heads') && endsWith(github.ref, 'main')) + if: false && (startsWith(github.ref, 'refs/pull') && endsWith(github.base_ref, 'main')) || startsWith(github.ref, 'refs/tags/v') || (startsWith(github.ref, 'refs/heads') && endsWith(github.ref, 'main')) env: LL_USE_STAGE: false strategy: @@ -311,7 +310,7 @@ jobs: Library- - name: Run Smoke Tests ${{ matrix.unityVersion }}-${{ matrix.testMode }}-${{ matrix.targetPlatform }} id: build-smoke-tests-gameci - uses: game-ci/unity-builder@v4 + uses: game-ci/unity-builder@v4.7.0 env: UNITY_LICENSE: ${{ SECRETS.UNITY_LICENSE }} UNITY_EMAIL: ${{ SECRETS.UNITY_EMAIL }} @@ -418,7 +417,7 @@ jobs: - name: Wait for Go backend ready healthcheck if: ${{ VARS.LL_USE_LOCAL_BACKEND == 'true' }} run: | - curl --get http://localhost:9999/__/ready --retry 30 --retry-delay 1 --retry-all-errors --fail-with-body --verbose + curl --get http://localhost:9999/__/ready --retry 60 --retry-delay 2 --retry-all-errors --fail-with-body --verbose ####### CONFIGURE TESTS ########### - name: Configure variables run: | @@ -507,7 +506,7 @@ jobs: key: Library-${{ matrix.unityVersion }}-${{ ENV.JSON_LIBRARY }} restore-keys: Library- - name: Run tests in ${{ matrix.unityVersion }} towards ${{ ENV.TARGET_ENVIRONMENT }} environment with json library ${{ ENV.JSON_LIBRARY }} - uses: game-ci/unity-test-runner@v4 + uses: game-ci/unity-test-runner@v4.3.1 if: ${{ vars.ENABLE_INTEGRATION_TESTS == 'true' }} id: tests env: @@ -533,6 +532,12 @@ jobs: run: | cd devenv mage env:downsilent dev + - name: Upload logs + uses: actions/upload-artifact@v4 + if: always() + with: + name: Integration tests (${{ matrix.unityVersion }}-${{ ENV.JSON_LIBRARY }}) Logs + path: logs - name: Upload test results uses: actions/upload-artifact@v4 if: always() @@ -543,7 +548,7 @@ jobs: name: Test Samples runs-on: [ubuntu-latest] needs: [editor-smoke-test] - timeout-minutes: 8 + timeout-minutes: 12 env: LL_USE_STAGE: false strategy: @@ -595,7 +600,7 @@ jobs: Library- - name: Compile and run all sample scenes ${{ matrix.unityVersion }} id: test-samples - uses: game-ci/unity-test-runner@v4 + uses: game-ci/unity-test-runner@v4.3.1 env: UNITY_LICENSE: ${{ SECRETS.UNITY_LICENSE }} UNITY_EMAIL: ${{ SECRETS.UNITY_EMAIL }} @@ -664,7 +669,7 @@ jobs: Library- - name: Validate SDK using asset store tools id: validate-sdk - uses: game-ci/unity-test-runner@v4 + uses: game-ci/unity-test-runner@v4.3.1 env: UNITY_LICENSE: ${{ SECRETS.UNITY_LICENSE }} UNITY_EMAIL: ${{ SECRETS.UNITY_EMAIL }} @@ -733,7 +738,7 @@ jobs: Library- - name: Package SDK for ${{ matrix.unityVersion }} id: package-sdk-gameci - uses: game-ci/unity-test-runner@v4 + uses: game-ci/unity-test-runner@v4.3.1 env: UNITY_LICENSE: ${{ SECRETS.UNITY_LICENSE }} UNITY_EMAIL: ${{ SECRETS.UNITY_EMAIL }} diff --git a/Runtime/Client/LootLockerEndPoints.cs b/Runtime/Client/LootLockerEndPoints.cs index 54c07ebdc..1dcea118c 100644 --- a/Runtime/Client/LootLockerEndPoints.cs +++ b/Runtime/Client/LootLockerEndPoints.cs @@ -211,6 +211,7 @@ public class LootLockerEndPoints public static EndPointClass redeemAppleAppStorePurchase = new EndPointClass("store/apple/redeem", LootLockerHTTPMethod.POST); public static EndPointClass redeemGooglePlayStorePurchase = new EndPointClass("store/google/redeem", LootLockerHTTPMethod.POST); public static EndPointClass redeemEpicStorePurchase = new EndPointClass("store/epic/redeem", LootLockerHTTPMethod.POST); + public static EndPointClass redeemPlayStationStorePurchase = new EndPointClass("store/playstation/redeem", LootLockerHTTPMethod.POST); public static EndPointClass beginSteamPurchaseRedemption = new EndPointClass("store/steam/redeem/begin", LootLockerHTTPMethod.POST); public static EndPointClass querySteamPurchaseRedemptionStatus = new EndPointClass("store/steam/redeem/query", LootLockerHTTPMethod.POST); @@ -272,7 +273,8 @@ public class LootLockerEndPoints // Catalogs [Header("Catalogs")] public static EndPointClass listCatalogs = new EndPointClass("catalogs", LootLockerHTTPMethod.GET); - public static EndPointClass listCatalogItemsByKey = new EndPointClass("catalog/key/{0}/prices", LootLockerHTTPMethod.GET); + public static EndPointClass deprecatedListCatalogItemsByKey = new EndPointClass("catalog/key/{0}/prices", LootLockerHTTPMethod.GET); + public static EndPointClass listCatalogItemsByKey = new EndPointClass("catalogs/inspired-ibex/v1/catalog/key/{key}/list", LootLockerHTTPMethod.GET); // Misc [Header("Misc")] @@ -329,6 +331,10 @@ public class LootLockerEndPoints public static EndPointClass ListNotifications = new EndPointClass("notifications/v1", LootLockerHTTPMethod.GET); public static EndPointClass ReadNotifications = new EndPointClass("notifications/v1/read", LootLockerHTTPMethod.PUT); public static EndPointClass ReadAllNotifications = new EndPointClass("notifications/v1/read/all", LootLockerHTTPMethod.PUT); + + // Broadcasts + [Header("Broadcasts")] + public static EndPointClass ListBroadcasts = new EndPointClass("broadcasts/v1", LootLockerHTTPMethod.GET); } [Serializable] diff --git a/Runtime/Client/LootLockerHTTPClient.cs b/Runtime/Client/LootLockerHTTPClient.cs index b2caad613..d00519ab9 100644 --- a/Runtime/Client/LootLockerHTTPClient.cs +++ b/Runtime/Client/LootLockerHTTPClient.cs @@ -590,7 +590,7 @@ private IEnumerator RefreshSession(string refreshForPlayerUlid, string forExecut { newSessionResponse = response; callCompleted = true; - }); + }, playerData.SessionOptionals); } break; case LL_AuthPlatforms.WhiteLabel: @@ -599,7 +599,7 @@ private IEnumerator RefreshSession(string refreshForPlayerUlid, string forExecut { newSessionResponse = response; callCompleted = true; - }, playerData.ULID); + }, playerData.ULID, playerData.SessionOptionals); } break; case LL_AuthPlatforms.AppleGameCenter: @@ -608,7 +608,7 @@ private IEnumerator RefreshSession(string refreshForPlayerUlid, string forExecut { newSessionResponse = response; callCompleted = true; - }, refreshForPlayerUlid); + }, refreshForPlayerUlid, playerData.SessionOptionals); } break; case LL_AuthPlatforms.AppleSignIn: @@ -617,7 +617,7 @@ private IEnumerator RefreshSession(string refreshForPlayerUlid, string forExecut { newSessionResponse = response; callCompleted = true; - }, refreshForPlayerUlid); + }, refreshForPlayerUlid, playerData.SessionOptionals); } break; case LL_AuthPlatforms.Epic: @@ -626,7 +626,7 @@ private IEnumerator RefreshSession(string refreshForPlayerUlid, string forExecut { newSessionResponse = response; callCompleted = true; - }, refreshForPlayerUlid); + }, refreshForPlayerUlid, playerData.SessionOptionals); } break; case LL_AuthPlatforms.Google: @@ -635,7 +635,7 @@ private IEnumerator RefreshSession(string refreshForPlayerUlid, string forExecut { newSessionResponse = response; callCompleted = true; - }, refreshForPlayerUlid); + }, refreshForPlayerUlid, playerData.SessionOptionals); } break; case LL_AuthPlatforms.Remote: @@ -655,7 +655,7 @@ private IEnumerator RefreshSession(string refreshForPlayerUlid, string forExecut { newSessionResponse = response; callCompleted = true; - }); + }, playerData.SessionOptionals); } break; case LL_AuthPlatforms.NintendoSwitch: diff --git a/Runtime/Client/LootLockerPlayerData.cs b/Runtime/Client/LootLockerPlayerData.cs index 22795ecb7..468ad8e16 100644 --- a/Runtime/Client/LootLockerPlayerData.cs +++ b/Runtime/Client/LootLockerPlayerData.cs @@ -57,5 +57,9 @@ public class LootLockerPlayerData /// The id of the wallet for this player /// public string WalletID { get; set; } + /// + /// Optional parameters used when starting/refreshing a session + /// + public LootLockerSessionOptionals SessionOptionals { get; set; } = new LootLockerSessionOptionals(); } } \ No newline at end of file diff --git a/Runtime/Game/LootLockerSDKManager.cs b/Runtime/Game/LootLockerSDKManager.cs index f5001996d..634e5e958 100644 --- a/Runtime/Game/LootLockerSDKManager.cs +++ b/Runtime/Game/LootLockerSDKManager.cs @@ -347,8 +347,9 @@ public static void StartPlaystationNetworkSession(string psnOnlineId, ActionThe authorization code received from PSN after a successful login /// The numeric representation of the account id received from PSN after a successful login /// Optional: The PSN issuer id to use when verifying the player towards PSN. If not supplied, will be defaulted to 256=production. + /// Optional: Additional session options /// onComplete Action for handling the response - public static void VerifyPlayerAndStartPlaystationNetworkSession(string AuthCode, long AccountId, Action onComplete, int PsnIssuerId = 256) + public static void VerifyPlayerAndStartPlaystationNetworkSession(string AuthCode, long AccountId, Action onComplete, int PsnIssuerId = 256, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -370,7 +371,7 @@ public static void VerifyPlayerAndStartPlaystationNetworkSession(string AuthCode return; } - LootLockerSessionRequest sessionRequest = new LootLockerSessionRequest(AccountId.ToString(), LL_AuthPlatforms.PlayStationNetwork); + LootLockerSessionRequest sessionRequest = new LootLockerSessionRequest(AccountId.ToString(), LL_AuthPlatforms.PlayStationNetwork, Optionals); LootLockerServerRequest.CallAPI(null, LootLockerEndPoints.authenticationRequest.endPoint, LootLockerEndPoints.authenticationRequest.httpMethod, LootLockerJson.SerializeObject(sessionRequest), onComplete: (serverResponse) => { @@ -392,6 +393,7 @@ public static void VerifyPlayerAndStartPlaystationNetworkSession(string AuthCode LastSignIn = DateTime.Now, CreatedAt = sessionResponse.player_created_at, WalletID = sessionResponse.wallet_id, + SessionOptionals = Optionals }); } @@ -408,8 +410,9 @@ public static void VerifyPlayerAndStartPlaystationNetworkSession(string AuthCode /// /// The authorization code received from PSN after a successful login /// Optional: The PSN Environment issuer id to use when verifying the player towards PSN. If not supplied, will be defaulted to 256=production. + /// Optional: Additional session options /// onComplete Action for handling the response - public static void VerifyPlayerAndStartPlaystationNetworkV3Session(string AuthCode, Action onComplete, int EnvIssuerId = 256) + public static void VerifyPlayerAndStartPlaystationNetworkV3Session(string AuthCode, Action onComplete, int EnvIssuerId = 256, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -420,7 +423,8 @@ public static void VerifyPlayerAndStartPlaystationNetworkV3Session(string AuthCo LootLockerPlaystationNetworkV3SessionRequest sessionRequest = new LootLockerPlaystationNetworkV3SessionRequest { auth_code = AuthCode, - env_iss_id = EnvIssuerId + env_iss_id = EnvIssuerId, + optionals = Optionals }; LootLockerServerRequest.CallAPI(null, LootLockerEndPoints.playstationNetworkv3SessionRequest.endPoint, LootLockerEndPoints.playstationNetworkv3SessionRequest.httpMethod, LootLockerJson.SerializeObject(sessionRequest), onComplete: (serverResponse) => @@ -443,6 +447,7 @@ public static void VerifyPlayerAndStartPlaystationNetworkV3Session(string AuthCo LastSignIn = DateTime.Now, CreatedAt = sessionResponse.player_created_at, WalletID = sessionResponse.wallet_id, + SessionOptionals = Optionals }); } @@ -455,8 +460,9 @@ public static void VerifyPlayerAndStartPlaystationNetworkV3Session(string AuthCo /// A game can support multiple platforms, but it is recommended that a build only supports one platform. /// /// The player's Device ID + /// Optional: Additional session options /// onComplete Action for handling the response of type LootLockerSessionResponse - public static void StartAndroidSession(string deviceId, Action onComplete) + public static void StartAndroidSession(string deviceId, Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -466,7 +472,7 @@ public static void StartAndroidSession(string deviceId, Action { var response = LootLockerResponse.Deserialize(serverResponse); @@ -487,6 +493,7 @@ public static void StartAndroidSession(string deviceId, Action /// The player's Amazon Luna GUID + /// Optional: Additional session options /// onComplete Action for handling the response of type LootLockerSessionResponse - public static void StartAmazonLunaSession(string amazonLunaGuid, Action onComplete) + public static void StartAmazonLunaSession(string amazonLunaGuid, Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -512,7 +520,7 @@ public static void StartAmazonLunaSession(string amazonLunaGuid, Action { var response = LootLockerResponse.Deserialize(serverResponse); @@ -533,6 +541,7 @@ public static void StartAmazonLunaSession(string amazonLunaGuid, Action /// Start a guest session. /// + /// Optional: Additional session options /// onComplete Action for handling the response of type LootLockerGuestSessionResponse - public static void StartGuestSession(Action onComplete) + public static void StartGuestSession(Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -568,15 +578,16 @@ public static void StartGuestSession(Action onCo return; } - StartGuestSession(LootLockerStateData.GetStateForPlayerOrDefaultStateOrEmpty(defaultPlayerUlid)?.Identifier, onComplete); + StartGuestSession(LootLockerStateData.GetStateForPlayerOrDefaultStateOrEmpty(defaultPlayerUlid)?.Identifier, onComplete, Optionals); } /// /// Start a guest session for an already existing player that has previously had active guest sessions on this device /// /// Execute the request for the specified player + /// Optional: Additional session options /// onComplete Action for handling the response of type LootLockerGuestSessionResponse - public static void StartGuestSessionForPlayer(string forPlayerWithUlid, Action onComplete) + public static void StartGuestSessionForPlayer(string forPlayerWithUlid, Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -590,15 +601,16 @@ public static void StartGuestSessionForPlayer(string forPlayerWithUlid, Action /// Start a guest session with an identifier, you can use something like SystemInfo.deviceUniqueIdentifier to tie the account to a device. /// /// Identifier for the player. Set this to empty if you want an identifier to be generated for you. + /// Optional: Additional session options /// onComplete Action for handling the response of type LootLockerGuestSessionResponse - public static void StartGuestSession(string identifier, Action onComplete) + public static void StartGuestSession(string identifier, Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -608,7 +620,7 @@ public static void StartGuestSession(string identifier, Action { var response = LootLockerResponse.Deserialize(serverResponse); @@ -629,6 +641,7 @@ public static void StartGuestSession(string identifier, Action /// The Steam session ticket received from Steam Authentication /// The size of the Steam session ticket received from Steam Authentication + /// Optional: Additional session options /// onComplete Action for handling the response of type LootLockerSessionResponse - public static void VerifyPlayerAndStartSteamSession(ref byte[] ticket, uint ticketSize, Action onComplete) + public static void VerifyPlayerAndStartSteamSession(ref byte[] ticket, uint ticketSize, Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -653,7 +667,7 @@ public static void VerifyPlayerAndStartSteamSession(ref byte[] ticket, uint tick } var sessionTicket = _SteamSessionTicket(ref ticket, ticketSize); - LootLockerServerRequest.CallAPI(null, LootLockerEndPoints.steamSessionRequest.endPoint, LootLockerEndPoints.steamSessionRequest.httpMethod, LootLockerJson.SerializeObject(new LootLockerSteamSessionRequest{ steam_ticket = sessionTicket }), onComplete: (serverResponse) => { + LootLockerServerRequest.CallAPI(null, LootLockerEndPoints.steamSessionRequest.endPoint, LootLockerEndPoints.steamSessionRequest.httpMethod, LootLockerJson.SerializeObject(new LootLockerSteamSessionRequest{ steam_ticket = sessionTicket , optionals = Optionals }), onComplete: (serverResponse) => { var sessionResponse = LootLockerResponse.Deserialize(serverResponse); if (sessionResponse.success) { @@ -672,6 +686,7 @@ public static void VerifyPlayerAndStartSteamSession(ref byte[] ticket, uint tick LastSignIn = DateTime.Now, CreatedAt = sessionResponse.player_created_at, WalletID = sessionResponse.wallet_id, + SessionOptionals = Optionals }); } @@ -685,8 +700,9 @@ public static void VerifyPlayerAndStartSteamSession(ref byte[] ticket, uint tick /// The Steam session ticket received from Steam Authentication /// The size of the Steam session ticket received from Steam Authentication /// The steam app id to start this steam session for + /// Optional: Additional session options /// onComplete Action for handling the response of type LootLockerSessionResponse - public static void VerifyPlayerAndStartSteamSessionWithSteamAppId(ref byte[] ticket, uint ticketSize, string steamAppId, Action onComplete) + public static void VerifyPlayerAndStartSteamSessionWithSteamAppId(ref byte[] ticket, uint ticketSize, string steamAppId, Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -695,7 +711,7 @@ public static void VerifyPlayerAndStartSteamSessionWithSteamAppId(ref byte[] tic } var sessionTicket = _SteamSessionTicket(ref ticket, ticketSize); - LootLockerServerRequest.CallAPI(null, LootLockerEndPoints.steamSessionRequest.endPoint, LootLockerEndPoints.steamSessionRequest.httpMethod, LootLockerJson.SerializeObject(new LootLockerSteamSessionWithAppIdRequest { steam_ticket = sessionTicket, steam_app_id = steamAppId }), onComplete: (serverResponse) => { + LootLockerServerRequest.CallAPI(null, LootLockerEndPoints.steamSessionRequest.endPoint, LootLockerEndPoints.steamSessionRequest.httpMethod, LootLockerJson.SerializeObject(new LootLockerSteamSessionWithAppIdRequest { steam_ticket = sessionTicket, steam_app_id = steamAppId, optionals = Optionals }), onComplete: (serverResponse) => { var sessionResponse = LootLockerResponse.Deserialize(serverResponse); if (sessionResponse.success) { @@ -714,6 +730,7 @@ public static void VerifyPlayerAndStartSteamSessionWithSteamAppId(ref byte[] tic LastSignIn = DateTime.Now, CreatedAt = sessionResponse.player_created_at, WalletID = sessionResponse.wallet_id, + SessionOptionals = Optionals }); } @@ -743,8 +760,9 @@ private static string _SteamSessionTicket(ref byte[] ticket, uint ticketSize) /// The Nintendo Switch platform must be enabled in the web console for this to work. /// /// nsa (Nintendo Switch Account) id token as a string + /// Optional: Additional session options /// onComplete Action for handling the response of type LootLockerSessionResponse - public static void StartNintendoSwitchSession(string nsa_id_token, Action onComplete) + public static void StartNintendoSwitchSession(string nsa_id_token, Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -754,7 +772,7 @@ public static void StartNintendoSwitchSession(string nsa_id_token, Action { var response = LootLockerResponse.Deserialize(serverResponse); @@ -775,6 +793,7 @@ public static void StartNintendoSwitchSession(string nsa_id_token, Action /// Xbox user token as a string + /// Optional: Additional session options /// onComplete Action for handling the response of typeLootLockerSessionResponse - public static void StartXboxOneSession(string xbox_user_token, Action onComplete) + public static void StartXboxOneSession(string xbox_user_token, Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -800,7 +820,7 @@ public static void StartXboxOneSession(string xbox_user_token, Action { var response = LootLockerResponse.Deserialize(serverResponse); @@ -821,6 +841,7 @@ public static void StartXboxOneSession(string xbox_user_token, Action /// The Id Token from google sign in + /// Optional: Additional session options /// onComplete Action for handling the response of type LootLockerSessionResponse - public static void StartGoogleSession(string idToken, Action onComplete) + public static void StartGoogleSession(string idToken, Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -846,7 +868,7 @@ public static void StartGoogleSession(string idToken, Action { var response = LootLockerGoogleSessionResponse.Deserialize(serverResponse); @@ -867,6 +889,7 @@ public static void StartGoogleSession(string idToken, Action /// The Id Token from google sign in /// Google OAuth2 ClientID platform + /// Optional: Additional session options /// onComplete Action for handling the response - public static void StartGoogleSession(string idToken, GooglePlatform googlePlatform, Action onComplete) + public static void StartGoogleSession(string idToken, GooglePlatform googlePlatform, Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -894,7 +918,7 @@ public static void StartGoogleSession(string idToken, GooglePlatform googlePlatf LootLockerServerRequest.CallAPI(null, LootLockerEndPoints.googleSessionRequest.endPoint, LootLockerEndPoints.googleSessionRequest.httpMethod, - LootLockerJson.SerializeObject(new LootLockerGoogleSignInWithPlatformSessionRequest(idToken, googlePlatform.ToString())), + LootLockerJson.SerializeObject(new LootLockerGoogleSignInWithPlatformSessionRequest(idToken, googlePlatform.ToString(), Optionals)), (serverResponse) => { var response = LootLockerGoogleSessionResponse.Deserialize(serverResponse); @@ -915,6 +939,7 @@ public static void StartGoogleSession(string idToken, GooglePlatform googlePlatf LastSignIn = DateTime.Now, CreatedAt = response.player_created_at, WalletID = response.wallet_id, + SessionOptionals = Optionals }); } @@ -930,10 +955,11 @@ public static void StartGoogleSession(string idToken, GooglePlatform googlePlatf /// The Google sign in platform must be enabled in the web console for this to work. /// /// onComplete Action for handling the response + /// Optional: Additional session options /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. - public static void RefreshGoogleSession(Action onComplete, string forPlayerWithUlid = null) + public static void RefreshGoogleSession(Action onComplete, string forPlayerWithUlid = null, LootLockerSessionOptionals Optionals = null) { - RefreshGoogleSession(null, onComplete, forPlayerWithUlid); + RefreshGoogleSession(null, onComplete, forPlayerWithUlid, Optionals); } /// @@ -944,8 +970,9 @@ public static void RefreshGoogleSession(Action /// /// Token received in response from StartGoogleSession request /// onComplete Action for handling the response + /// Optional: Additional session options /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. - public static void RefreshGoogleSession(string refresh_token, Action onComplete, string forPlayerWithUlid = null) + public static void RefreshGoogleSession(string refresh_token, Action onComplete, string forPlayerWithUlid = null, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -965,9 +992,14 @@ public static void RefreshGoogleSession(string refresh_token, Action { var response = LootLockerGoogleSessionResponse.Deserialize(serverResponse); @@ -988,6 +1020,7 @@ public static void RefreshGoogleSession(string refresh_token, Action /// The auth code received from Google Play Games Services authentication. + /// Optional: Additional session options /// onComplete Action for handling the response - public static void StartGooglePlayGamesSession(string authCode, Action onComplete) + public static void StartGooglePlayGamesSession(string authCode, Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { onComplete?.Invoke(null); return; } - var request = new Requests.LootLockerGooglePlayGamesSessionRequest(authCode); + var request = new Requests.LootLockerGooglePlayGamesSessionRequest(authCode, Optionals); LootLockerServerRequest.CallAPI( null, LootLocker.LootLockerEndPoints.googlePlayGamesSessionRequest.endPoint, @@ -1036,6 +1070,7 @@ public static void StartGooglePlayGamesSession(string authCode, Action /// The refresh token received from a previous GPGS session. + /// Optional: Additional session options /// onComplete Action for handling the response - public static void RefreshGooglePlayGamesSession(string refreshToken, Action onComplete) + public static void RefreshGooglePlayGamesSession(string refreshToken, Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { onComplete?.Invoke(null); return; } - var request = new Requests.LootLockerGooglePlayGamesRefreshSessionRequest(refreshToken); + var request = new Requests.LootLockerGooglePlayGamesRefreshSessionRequest(refreshToken, Optionals); LootLockerServerRequest.CallAPI( null, LootLocker.LootLockerEndPoints.googlePlayGamesRefreshSessionRequest.endPoint, @@ -1085,6 +1121,7 @@ public static void RefreshGooglePlayGamesSession(string refreshToken, Action /// onComplete Action for handling the response + /// Optional: Additional session options /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. - public static void RefreshGooglePlayGamesSession(Action onComplete, string forPlayerWithUlid = null) + public static void RefreshGooglePlayGamesSession(Action onComplete, string forPlayerWithUlid = null, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -1115,7 +1153,7 @@ public static void RefreshGooglePlayGamesSession(Action @@ -1123,8 +1161,9 @@ public static void RefreshGooglePlayGamesSession(Action /// Authorization code, provided by apple + /// Optional: Additional session options /// onComplete Action for handling the response of type LootLockerAppleSessionResponse - public static void StartAppleSession(string authorization_code, Action onComplete) + public static void StartAppleSession(string authorization_code, Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -1134,7 +1173,7 @@ public static void StartAppleSession(string authorization_code, Action { var response = LootLockerAppleSessionResponse.Deserialize(serverResponse); @@ -1155,6 +1194,7 @@ public static void StartAppleSession(string authorization_code, Action /// onComplete Action for handling the response of type LootLockerAppleSessionResponse + /// Optional: Additional session options /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. - public static void RefreshAppleSession(Action onComplete, string forPlayerWithUlid = null) + public static void RefreshAppleSession(Action onComplete, string forPlayerWithUlid = null, LootLockerSessionOptionals Optionals = null) { - RefreshAppleSession(null, onComplete, forPlayerWithUlid); + RefreshAppleSession(null, onComplete, forPlayerWithUlid, Optionals); } /// @@ -1185,7 +1226,8 @@ public static void RefreshAppleSession(Action on /// Token received in response from StartAppleSession request /// onComplete Action for handling the response of type LootLockerAppleSessionResponse /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. - public static void RefreshAppleSession(string refresh_token, Action onComplete, string forPlayerWithUlid = null) + /// Optional: Additional session options + public static void RefreshAppleSession(string refresh_token, Action onComplete, string forPlayerWithUlid = null, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -1205,9 +1247,14 @@ public static void RefreshAppleSession(string refresh_token, Action { var response = LootLockerAppleSessionResponse.Deserialize(serverResponse); @@ -1228,6 +1275,7 @@ public static void RefreshAppleSession(string refresh_token, ActionThe signature generated from Apple Game Center Identity Verification /// The salt of the signature generated from Apple Game Center Identity Verification /// The timestamp of the verification generated from Apple Game Center Identity Verification - /// onComplete Action for handling the response of type for handling the response of type LootLockerAppleGameCenterSessionRe - public static void StartAppleGameCenterSession(string bundleId, string playerId, string publicKeyUrl, string signature, string salt, long timestamp, Action onComplete) + /// Optional: Additional session options + /// onComplete Action for handling the response of type for handling the response + public static void StartAppleGameCenterSession(string bundleId, string playerId, string publicKeyUrl, string signature, string salt, long timestamp, Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -1258,7 +1307,7 @@ public static void StartAppleGameCenterSession(string bundleId, string playerId, LootLockerServerRequest.CallAPI(null, LootLockerEndPoints.appleGameCenterSessionRequest.endPoint, LootLockerEndPoints.appleGameCenterSessionRequest.httpMethod, - LootLockerJson.SerializeObject(new LootLockerAppleGameCenterSessionRequest(bundleId, playerId, publicKeyUrl, signature, salt, timestamp)), + LootLockerJson.SerializeObject(new LootLockerAppleGameCenterSessionRequest(bundleId, playerId, publicKeyUrl, signature, salt, timestamp, Optionals)), (serverResponse) => { var response = LootLockerAppleGameCenterSessionResponse.Deserialize(serverResponse); @@ -1279,6 +1328,7 @@ public static void StartAppleGameCenterSession(string bundleId, string playerId, LastSignIn = DateTime.Now, CreatedAt = response.player_created_at, WalletID = response.wallet_id, + SessionOptionals = Optionals }); } @@ -1295,7 +1345,8 @@ public static void StartAppleGameCenterSession(string bundleId, string playerId, /// /// onComplete Action for handling the response of type for handling the response of type LootLockerAppleGameCenterSessionResponse /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. - public static void RefreshAppleGameCenterSession(Action onComplete, string forPlayerWithUlid = null) + /// Optional: Additional session options + public static void RefreshAppleGameCenterSession(Action onComplete, string forPlayerWithUlid = null, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -1310,9 +1361,14 @@ public static void RefreshAppleGameCenterSession(Action { var response = LootLockerAppleGameCenterSessionResponse.Deserialize(serverResponse); @@ -1333,6 +1389,7 @@ public static void RefreshAppleGameCenterSession(Action /// EOS Id Token as a string /// onComplete Action for handling the response of type LootLockerEpicSessionResponse - public static void StartEpicSession(string id_token, Action onComplete) + /// Optional: Additional session options + public static void StartEpicSession(string id_token, Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -1358,7 +1416,7 @@ public static void StartEpicSession(string id_token, Action { var response = LootLockerResponse.Deserialize(serverResponse); @@ -1379,6 +1437,7 @@ public static void StartEpicSession(string id_token, Action /// onComplete Action for handling the response of type LootLockerEpicSessionResponse /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. - public static void RefreshEpicSession(Action onComplete, string forPlayerWithUlid = null) + /// Optional: Additional session options + public static void RefreshEpicSession(Action onComplete, string forPlayerWithUlid = null, LootLockerSessionOptionals Optionals = null) { - RefreshEpicSession(null, onComplete, forPlayerWithUlid); + RefreshEpicSession(null, onComplete, forPlayerWithUlid, Optionals); } /// @@ -1409,7 +1469,8 @@ public static void RefreshEpicSession(Action onCo /// Token received in response from StartEpicSession request /// onComplete Action for handling the response of type LootLockerEpicSessionResponse /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. - public static void RefreshEpicSession(string refresh_token, Action onComplete, string forPlayerWithUlid = null) + /// Optional: Additional session options + public static void RefreshEpicSession(string refresh_token, Action onComplete, string forPlayerWithUlid = null, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -1429,9 +1490,14 @@ public static void RefreshEpicSession(string refresh_token, Action { var response = LootLockerResponse.Deserialize(serverResponse); @@ -1452,6 +1518,7 @@ public static void RefreshEpicSession(string refresh_token, Action /// User ID as a string /// Nonce as a string + /// Optional: Additional session options /// Action to handle the response of type LootLockerMetaSessionResponse - public static void StartMetaSession(string user_id, string nonce, Action onComplete) + public static void StartMetaSession(string user_id, string nonce, Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -1479,7 +1547,8 @@ public static void StartMetaSession(string user_id, string nonce, Action /// onComplete Action for handling the response of type LootLockerMetaSessionResponse + /// Optional: Additional session options /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. - public static void RefreshMetaSession(Action onComplete, string forPlayerWithUlid = null) + public static void RefreshMetaSession(Action onComplete, string forPlayerWithUlid = null, LootLockerSessionOptionals Optionals = null) { - RefreshMetaSession(null, onComplete, forPlayerWithUlid); + RefreshMetaSession(null, onComplete, forPlayerWithUlid, Optionals); } /// @@ -1531,7 +1602,8 @@ public static void RefreshMetaSession(Action onCo /// Token received in response from StartMetaSession request /// onComplete Action for handling the response of type LootLockerMetaSessionResponse /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. - public static void RefreshMetaSession(string refresh_token, Action onComplete, string forPlayerWithUlid = null) + /// Optional: Additional session options + public static void RefreshMetaSession(string refresh_token, Action onComplete, string forPlayerWithUlid = null, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -1551,9 +1623,14 @@ public static void RefreshMetaSession(string refresh_token, Action { var response = LootLockerResponse.Deserialize(serverResponse); @@ -1574,6 +1651,7 @@ public static void RefreshMetaSession(string refresh_token, Action /// The player's Discord OAuth token + /// Optional: Additional session options /// onComplete Action for handling the response - public static void StartDiscordSession(string accessToken, Action onComplete) + public static void StartDiscordSession(string accessToken, Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -1601,7 +1680,7 @@ public static void StartDiscordSession(string accessToken, Action { var response = LootLockerResponse.Deserialize(serverResponse); @@ -1622,6 +1701,7 @@ public static void StartDiscordSession(string accessToken, Action /// onComplete Action for handling the response /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. - public static void RefreshDiscordSession(Action onComplete, string forPlayerWithUlid = null) + public static void RefreshDiscordSession(Action onComplete, string forPlayerWithUlid = null, LootLockerSessionOptionals Optionals = null) { var playerData = LootLockerStateData.GetStateForPlayerOrDefaultStateOrEmpty(forPlayerWithUlid); if (string.IsNullOrEmpty(playerData?.RefreshToken)) @@ -1647,7 +1727,12 @@ public static void RefreshDiscordSession(Action @@ -1659,7 +1744,8 @@ public static void RefreshDiscordSession(ActionToken received in response from StartDiscordSession request /// onComplete Action for handling the response of type LootLockerDiscordSessionResponse /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. - public static void RefreshDiscordSession(string refresh_token, Action onComplete, string forPlayerWithUlid = null) + /// Optional: Additional session options + public static void RefreshDiscordSession(string refresh_token, Action onComplete, string forPlayerWithUlid = null, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -1679,9 +1765,14 @@ public static void RefreshDiscordSession(string refresh_token, Action { var response = LootLockerResponse.Deserialize(serverResponse); @@ -1702,6 +1793,7 @@ public static void RefreshDiscordSession(string refresh_token, Action /// onComplete Action for handling the response of type LootLockerSessionResponse - public static void StartWhiteLabelSession(Action onComplete, string forPlayerWithUlid = null) + /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. + /// Optional parameters for the session start request + public static void StartWhiteLabelSession(Action onComplete, string forPlayerWithUlid = null, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -2486,7 +2580,12 @@ public static void StartWhiteLabelSession(Action onCo return; } - StartWhiteLabelSession(new LootLockerWhiteLabelSessionRequest() { email = email, token = token }, onComplete); + if (Optionals == null) + { + Optionals = playerData?.SessionOptionals; + } + + StartWhiteLabelSession(new LootLockerWhiteLabelSessionRequest() { email = email, token = token, optionals = Optionals }, onComplete); } /// @@ -2494,8 +2593,9 @@ public static void StartWhiteLabelSession(Action onCo /// White Label platform must be enabled in the web console for this to work. /// /// The email of the White Label user to start a WL session for + /// Optional parameters for the session start request /// onComplete Action for handling the response of type LootLockerSessionResponse - public static void StartWhiteLabelSession(string email, Action onComplete) + public static void StartWhiteLabelSession(string email, Action onComplete, LootLockerSessionOptionals Optionals = null) { if (!CheckInitialized(true)) { @@ -2521,6 +2621,10 @@ public static void StartWhiteLabelSession(string email, Action @@ -2542,6 +2646,7 @@ public static void StartWhiteLabelSession(string email, Action /// A White Label Session Request with inner values already set + /// Optional parameters for the session start request /// onComplete Action for handling the response of type LootLockerSessionResponse public static void StartWhiteLabelSession(LootLockerWhiteLabelSessionRequest sessionRequest, Action onComplete) { @@ -2574,6 +2679,7 @@ public static void StartWhiteLabelSession(LootLockerWhiteLabelSessionRequest ses LastSignIn = DateTime.Now, CreatedAt = response.player_created_at, WalletID = response.wallet_id, + SessionOptionals = sessionRequest.optionals }); _wllProcessesDictionary.Remove(sessionRequest.email); } @@ -6120,6 +6226,103 @@ public static void RedeemEpicStorePurchaseForClass(string accountId, string bear LootLockerServerRequest.CallAPI(forPlayerWithUlid, LootLockerEndPoints.redeemEpicStorePurchase.endPoint, LootLockerEndPoints.redeemEpicStorePurchase.httpMethod, body, onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); } +#if LOOTLOCKER_BETA_PLAYSTATION_IAP + /// + /// Redeem a purchase that was made successfully towards the Playstation Store for the current player + /// + /// The transaction id from the PlayStation Store of the purchase to redeem + /// The authorization code from the PlayStation Store of the purchase to redeem + /// The entitlement label configured in the NP service for the entitlement that this redemption relates to + /// onComplete Action for handling the response + /// Optional: The NP service label. + /// Optional: The abreviation of the service name of the ASM service ID service that was used when configuring the serviceIds. Possible Values: pssdc, cce. Default Value: pssdc + /// Optional: The id of the environment you wish to make the request against. Allowed values: 1, 8, 256 + /// Optional: The use count for this redemption + /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. + public static void RedeemPlaystationStorePurchaseForPlayer(string transaction_id, string auth_code, string entitlement_label, Action onComplete, string service_label = "", string service_name = "", int environment = -1, int use_count = -1, string forPlayerWithUlid = null) + { + if (!CheckInitialized(false, forPlayerWithUlid)) + { + onComplete?.Invoke(LootLockerResponseFactory.SDKNotInitializedError(forPlayerWithUlid)); + return; + } + Dictionary bodyDict = new Dictionary + { + { "transaction_id", transaction_id }, + { "auth_code", auth_code }, + { "entitlement_label", entitlement_label } + }; + if (!string.IsNullOrEmpty(service_label)) + { + bodyDict.Add("service_label", service_label); + } + if (!string.IsNullOrEmpty(service_name)) + { + bodyDict.Add("service_name", service_name); + } + if (environment != -1) + { + bodyDict.Add("environment", environment); + } + if (use_count != -1) + { + bodyDict.Add("use_count", use_count); + } + var body = LootLockerJson.SerializeObject(bodyDict); + + LootLockerServerRequest.CallAPI(forPlayerWithUlid, LootLockerEndPoints.redeemPlayStationStorePurchase.endPoint, LootLockerEndPoints.redeemPlayStationStorePurchase.httpMethod, body, onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); + } + + /// + /// Redeem a purchase that was made successfully towards the Playstation Store for a class that the current player owns + /// + /// The transaction id from the PlayStation Store of the purchase to redeem + /// The authorization code from the PlayStation Store of the purchase to redeem + /// The entitlement label configured in the NP service for the entitlement that this redemption relates to + /// The id of the class to redeem this purchase for + /// onComplete Action for handling the response + /// Optional: The NP service label. + /// Optional: The abreviation of the service name of the ASM service ID service that was used when configuring the serviceIds. Possible Values: pssdc, cce. Default Value: pssdc + /// Optional: The id of the environment you wish to make the request against. Allowed values: 1, 8, 256 + /// Optional: The use count for this redemption + /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. + public static void RedeemPlaystationStorePurchaseForClass(string transaction_id, string auth_code, string entitlement_label, int classId, Action onComplete, string service_label = "", string service_name = "", int environment = -1, int use_count = -1, string forPlayerWithUlid = null) + { + if (!CheckInitialized(false, forPlayerWithUlid)) + { + onComplete?.Invoke(LootLockerResponseFactory.SDKNotInitializedError(forPlayerWithUlid)); + return; + } + + Dictionary bodyDict = new Dictionary + { + { "transaction_id", transaction_id }, + { "auth_code", auth_code }, + { "entitlement_label", entitlement_label }, + { "character_id", classId } + }; + if (!string.IsNullOrEmpty(service_label)) + { + bodyDict.Add("service_label", service_label); + } + if (!string.IsNullOrEmpty(service_name)) + { + bodyDict.Add("service_name", service_name); + } + if (environment != -1) + { + bodyDict.Add("environment", environment); + } + if (use_count != -1) + { + bodyDict.Add("use_count", use_count); + } + var body = LootLockerJson.SerializeObject(bodyDict); + + LootLockerServerRequest.CallAPI(forPlayerWithUlid, LootLockerEndPoints.redeemPlayStationStorePurchase.endPoint, LootLockerEndPoints.redeemPlayStationStorePurchase.httpMethod, body, onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); + } +#endif + /// /// Begin a Steam purchase with the given settings that when finalized will redeem the specified catalog item /// @@ -7809,6 +8012,7 @@ public static void ListCatalogs(Action onComplet /// Used for pagination, this is the cursor to start getting items from. Use null to get items from the beginning. Use the cursor from a previous call to get the next count of items in the list. /// onComplete Action for handling the response /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. + [Obsolete("This method is deprecated, please use ListCatalogItems(string catalogKey, int PerPage, int Page, Action onComplete, string forPlayerWithUlid = null) instead.")] // Deprecation date 20251016 public static void ListCatalogItems(string catalogKey, int count, string after, Action onComplete, string forPlayerWithUlid = null) { if (!CheckInitialized(false, forPlayerWithUlid)) @@ -7816,7 +8020,7 @@ public static void ListCatalogItems(string catalogKey, int count, string after, onComplete?.Invoke(LootLockerResponseFactory.SDKNotInitializedError(forPlayerWithUlid)); return; } - var endpoint = LootLockerEndPoints.listCatalogItemsByKey.WithPathParameter(catalogKey); + var endpoint = LootLockerEndPoints.deprecatedListCatalogItemsByKey.WithPathParameter(catalogKey); var queryParams = new LootLocker.Utilities.HTTP.QueryParamaterBuilder(); if (count > 0) @@ -7826,7 +8030,35 @@ public static void ListCatalogItems(string catalogKey, int count, string after, endpoint += queryParams.Build(); - LootLockerServerRequest.CallAPI(forPlayerWithUlid, endpoint, LootLockerEndPoints.listCatalogItemsByKey.httpMethod, onComplete: (serverResponse) => { onComplete?.Invoke(new LootLockerListCatalogPricesResponse(serverResponse)); }); + LootLockerServerRequest.CallAPI(forPlayerWithUlid, endpoint, LootLockerEndPoints.deprecatedListCatalogItemsByKey.httpMethod, onComplete: (serverResponse) => { onComplete?.Invoke(new LootLockerListCatalogPricesResponse(serverResponse)); }); + } + + /// + /// List the items available in a specific catalog + /// + /// Unique Key of the catalog that you want to get items for + /// The number of results to return per page + /// The page number to retrieve + /// onComplete Action for handling the response + /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. + public static void ListCatalogItems(string catalogKey, int PerPage, int Page, Action onComplete, string forPlayerWithUlid = null) + { + if (!CheckInitialized(false, forPlayerWithUlid)) + { + onComplete?.Invoke(LootLockerResponseFactory.SDKNotInitializedError(forPlayerWithUlid)); + return; + } + var endpoint = LootLockerEndPoints.listCatalogItemsByKey.WithPathParameter(catalogKey); + + var queryParams = new LootLocker.Utilities.HTTP.QueryParamaterBuilder(); + if (PerPage > 0) + queryParams.Add("per_page", PerPage); + if (Page > 0) + queryParams.Add("page", Page); + + endpoint += queryParams.Build(); + + LootLockerServerRequest.CallAPI(forPlayerWithUlid, endpoint, LootLockerEndPoints.listCatalogItemsByKey.httpMethod, onComplete: (serverResponse) => { onComplete?.Invoke(new LootLockerListCatalogPricesV2Response(serverResponse)); }); } #endregion @@ -8175,7 +8407,91 @@ public static void MarkNotificationsAsRead(string[] NotificationIds, Action { LootLockerResponse.Deserialize(onComplete, response); }); } #endregion - + + #region Broadcasts + /// + /// List broadcasts for this game with default localisation and limit + /// + /// Delegate for handling the server response + /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. + public static void ListTopBroadcasts(Action onComplete, string forPlayerWithUlid = null) + { + if (!CheckInitialized(false, forPlayerWithUlid)) + { + onComplete?.Invoke(LootLockerResponseFactory.SDKNotInitializedError(forPlayerWithUlid)); + return; + } + + LootLockerServerRequest.CallAPI(forPlayerWithUlid, LootLockerEndPoints.ListBroadcasts.endPoint, LootLockerEndPoints.ListBroadcasts.httpMethod, null, (response) => { LootLockerResponse.Deserialize(onComplete, response); }); + } + + /// + /// List broadcasts for this game with specified localisation and default limit + /// + /// Array of language codes to filter the broadcasts by. Language codes are typically ISO 639-1 codes (e.g. "en", "fr", "es") with regional variations (e.g. "en-US", "fr-FR"), but can also be custom defined by the game developer. + /// Delegate for handling the server response + /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. + public static void ListTopBroadcastsLocalized(string[] languages, Action onComplete, string forPlayerWithUlid = null) + { + if (!CheckInitialized(false, forPlayerWithUlid)) + { + onComplete?.Invoke(LootLockerResponseFactory.SDKNotInitializedError(forPlayerWithUlid)); + return; + } + + string acceptLanguages = ""; + if (languages != null && languages.Length > 0) + { + acceptLanguages = string.Join(",", languages); + } + var headers = new Dictionary(); + if (!string.IsNullOrEmpty(acceptLanguages)) + { + headers.Add("Accept-Language", acceptLanguages); + } + LootLockerServerRequest.CallAPI(forPlayerWithUlid, LootLockerEndPoints.ListBroadcasts.endPoint, LootLockerEndPoints.ListBroadcasts.httpMethod, null, additionalHeaders: headers, onComplete: (response) => { LootLockerResponse.Deserialize(onComplete, response); }); + } + + /// + /// List broadcasts for this game + /// + /// Array of language codes to filter the broadcasts by. Language codes are typically ISO 639-1 codes (e.g. "en", "fr", "es") with regional variations (e.g. "en-US", "fr-FR"), but can also be custom defined by the game developer. + /// Limit the number of broadcasts returned. + /// Delegate for handling the server response + /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. + public static void ListBroadcasts(string[] languages, int limit, Action onComplete, string forPlayerWithUlid = null) + { + if (!CheckInitialized(false, forPlayerWithUlid)) + { + onComplete?.Invoke(LootLockerResponseFactory.SDKNotInitializedError(forPlayerWithUlid)); + return; + } + + var endpoint = LootLockerEndPoints.ListBroadcasts.endPoint; + + var queryParams = new LootLocker.Utilities.HTTP.QueryParamaterBuilder(); + if (limit > 0) + queryParams.Add("limit", limit); + + endpoint += queryParams.Build(); + + string acceptLanguages = ""; + if (languages != null && languages.Length > 0) + { + acceptLanguages = string.Join(",", languages); + } + var headers = new Dictionary(); + if (!string.IsNullOrEmpty(acceptLanguages)) + { + headers.Add("Accept-Language", acceptLanguages); + } + LootLockerServerRequest.CallAPI(forPlayerWithUlid, endpoint, LootLockerEndPoints.ListBroadcasts.httpMethod, null, additionalHeaders: headers, onComplete: (response) => { + var internalResponse = LootLockerResponse.Deserialize<__LootLockerInternalListBroadcastsResponse>(response); + onComplete?.Invoke(new LootLockerListBroadcastsResponse(internalResponse)); + }); + } + #endregion + #region Misc /// diff --git a/Runtime/Game/Requests/BroadcastRequest.cs b/Runtime/Game/Requests/BroadcastRequest.cs new file mode 100644 index 000000000..4c502624d --- /dev/null +++ b/Runtime/Game/Requests/BroadcastRequest.cs @@ -0,0 +1,289 @@ +using System; +using System.Collections.Generic; + +namespace LootLocker.Requests +{ + //================================================== + // Data Definitions + //================================================== + + /// + /// Represents a publication setting for a broadcast message + /// + public class LootLockerBroadcastPublicationSetting + { + /// + /// The id of the publication setting + /// + public int id { get; set; } + /// + /// The time of publication + /// + public DateTime start { get; set; } + /// + /// The optional time of when the broadcast will no longer be returned + /// + public DateTime end { get; set; } + /// + /// The IANA timezone that the start and end times are specified in, eg. UTC, Asia/Tokyo, or America/Washington + /// + public string tz { get; set; } + }; + + /// + /// Indicates which games are allowed to see this broadcast. + /// This may be useful if you want to know what other games might be seeing this broadcast at the point of display. + /// + public class LootLockerBroadcastGame + { + /// + /// The id of the game + /// + public int id { get; set; } + /// + /// The name of the game + /// + public string name { get; set; } + }; + + public class __LootLockerInternalBroadcastLocalization + { + /// + /// The key for this localization entry + /// Some keys are system defined, eg. ll.headline, ll.body, ll.image_url, ll.action + /// + public string key { get; set; } + /// + /// The value for this localization entry + /// + public string value { get; set; } + } + + /// + /// Represents a localised version of a broadcast message + /// + public class __LootLockerInternalBroadcastLanguage + { + /// + /// The language code for this localised version of the broadcast message, eg. en-GB + /// + public string language_code { get; set; } + /// + /// Metadata associated with the localised version of this broadcast message + /// + public __LootLockerInternalBroadcastLocalization[] localizations { get; set; } + }; + + /// + /// Represents a localised version of a broadcast message + /// + public class LootLockerBroadcastLanguage + { + /// + /// The language code for this localised version of the broadcast message, eg. en-GB + /// + public string language_code { get; set; } + /// + /// The headline for this broadcast message + /// + public string headline { get; set; } + /// + /// The body for this broadcast message + /// + public string body { get; set; } + /// + /// The image URL for this broadcast message + /// + public string image_url { get; set; } + /// + /// The action for this broadcast message + /// + public string action { get; set; } + /// + /// List of the keys available in the localizations dictionary + /// + public string[] localization_keys { get; set; } + /// + /// Localized entries for this broadcast message + /// + public Dictionary localizations { get; set; } + }; + + /// + /// Represents a broadcast message + /// + public class __LootLockerInternalBroadcast + { + /// + /// The unique identifier (ULID) for this broadcast message + /// + public string id { get; set; } + /// + /// The name of this broadcast message + /// + public string name { get; set; } + /// + /// Name of the current game you're seeing this broadcast on. + /// + public string game_name { get; set; } + /// + /// Indicates which games are allowed to see this broadcast. + /// This may be useful if you want to know what other games might be seeing this broadcast at the point of display. + /// + public List games { get; set; } + /// + /// A list of publication settings for this broadcast message + /// This list will always contain at least the publication time in UTC, but may also contain additional publication settings for different timezones + /// + public LootLockerBroadcastPublicationSetting[] publication_settings { get; set; } + /// + /// Localised versions of this broadcast message + /// + public __LootLockerInternalBroadcastLanguage[] languages { get; set; } + }; + + /// + /// Represents a broadcast message + /// + public class LootLockerBroadcast + { + /// + /// The unique identifier (ULID) for this broadcast message + /// + public string id { get; set; } + /// + /// The name of this broadcast message + /// + public string name { get; set; } + /// + /// Name of the current game you're seeing this broadcast on. + /// + public string game_name { get; set; } + /// + /// Indicates which games are allowed to see this broadcast. + /// This may be useful if you want to know what other games might be seeing this broadcast at the point of display. + /// + public List games { get; set; } + /// + /// A list of publication settings for this broadcast message + /// This list will always contain at least the publication time in UTC, but may also contain additional publication settings for different timezones + /// + public LootLockerBroadcastPublicationSetting[] publication_settings { get; set; } + /// + /// The language codes available for this broadcast message + /// eg. ["en", "en-US", "zh"] + /// + public string[] language_codes { get; set; } + /// + /// Localised versions of this broadcast message + /// + public Dictionary languages { get; set; } + }; + + //================================================== + // Response Definitions + //================================================== + + /// + /// Response for listing broadcasts + /// + public class __LootLockerInternalListBroadcastsResponse : LootLockerResponse + { + /// + /// A list of cronologically ordered broadcasts + /// + public __LootLockerInternalBroadcast[] broadcasts { get; set; } + }; + + /// + /// Response for listing broadcasts + /// + public class LootLockerListBroadcastsResponse : LootLockerResponse + { + /// + /// A list of cronologically ordered broadcasts + /// + public LootLockerBroadcast[] broadcasts { get; set; } + + public LootLockerListBroadcastsResponse() + { + broadcasts = Array.Empty(); + } + + public LootLockerListBroadcastsResponse(__LootLockerInternalListBroadcastsResponse internalResponse) + { + if (internalResponse == null) + { + broadcasts = Array.Empty(); + return; + } + + success = internalResponse.success; + statusCode = internalResponse.statusCode; + requestContext = internalResponse.requestContext; + EventId = internalResponse.EventId; + errorData = internalResponse.errorData; + text = internalResponse.text; + + if (internalResponse.broadcasts == null || internalResponse.broadcasts.Length == 0) + { + broadcasts = Array.Empty(); + return; + } + + // Convert internal broadcasts to public broadcasts + broadcasts = new LootLockerBroadcast[internalResponse.broadcasts.Length]; + for (int i = 0; i < internalResponse.broadcasts.Length; i++) + { + __LootLockerInternalBroadcast internalBroadcast = internalResponse.broadcasts[i]; + LootLockerBroadcast translatedBroadcast = new LootLockerBroadcast(); + translatedBroadcast.id = internalBroadcast.id; + translatedBroadcast.name = internalBroadcast.name; + translatedBroadcast.game_name = internalBroadcast.game_name; + translatedBroadcast.games = internalBroadcast.games; + translatedBroadcast.publication_settings = internalBroadcast.publication_settings; + translatedBroadcast.language_codes = new string[internalBroadcast.languages?.Length ?? 0]; + translatedBroadcast.languages = new Dictionary(); + + for (int j = 0; j < internalBroadcast.languages.Length; j++) + { + var internalLang = internalBroadcast.languages[j]; + if (internalLang == null || string.IsNullOrEmpty(internalLang.language_code)) + continue; + LootLockerBroadcastLanguage lang = new LootLockerBroadcastLanguage(); + translatedBroadcast.language_codes[j] = internalLang.language_code; + lang.language_code = internalLang.language_code; + lang.localizations = new Dictionary(); + + List localizationKeys = new List(); + for (int k = 0; k < (internalLang.localizations?.Length ?? 0); k++) + { + switch (internalLang.localizations[k].key) + { + case "ll.headline": + lang.headline = internalLang.localizations[k].value; + break; + case "ll.body": + lang.body = internalLang.localizations[k].value; + break; + case "ll.image_url": + lang.image_url = internalLang.localizations[k].value; + break; + case "ll.action": + lang.action = internalLang.localizations[k].value; + break; + default: + localizationKeys.Add(internalLang.localizations[k].key); + lang.localizations[internalLang.localizations[k].key] = internalLang.localizations[k].value; + break; + } + } + lang.localization_keys = localizationKeys.ToArray(); + translatedBroadcast.languages[lang.language_code] = lang; + } + + broadcasts[i] = translatedBroadcast; + } + } + }; +} diff --git a/Runtime/Game/Requests/BroadcastRequest.cs.meta b/Runtime/Game/Requests/BroadcastRequest.cs.meta new file mode 100644 index 000000000..4d649c84b --- /dev/null +++ b/Runtime/Game/Requests/BroadcastRequest.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f6595923df8ce46418692570c9cae403 \ No newline at end of file diff --git a/Runtime/Game/Requests/CatalogRequests.cs b/Runtime/Game/Requests/CatalogRequests.cs index d7d2bffc6..af1f5870b 100644 --- a/Runtime/Game/Requests/CatalogRequests.cs +++ b/Runtime/Game/Requests/CatalogRequests.cs @@ -772,4 +772,319 @@ public LootLockerInlinedCatalogEntry[] GetLootLockerInlinedCatalogEntries() return inlinedEntries.ToArray(); } } + + /// + /// + public class LootLockerListCatalogPricesV2Response : LootLockerResponse + { + /// + /// Details about the catalog that the prices is in + /// + public LootLockerCatalog catalog { get; set; } + + /// + /// A list of entries available in this catalog + /// + public LootLockerCatalogEntry[] entries { get; set; } + + /// + /// Lookup map for details about entities of entity type assets + /// + public Dictionary asset_details { get; set; } + + /// + /// Lookup map for details about entities of entity type progression_points + /// + public Dictionary progression_points_details + { + get; + set; + } + + /// + /// Lookup map for details about entities of entity type progression_reset + /// + public Dictionary progression_resets_details + { + get; + set; + } + + /// + /// Lookup map for details about entities of entity type currency + /// + public Dictionary currency_details { get; set; } + + /// + /// Lookup map for details about entities of entity type group + /// + public Dictionary group_details { get; set; } + + /// + /// Pagination data to use for subsequent requests + /// + public LootLockerExtendedPagination pagination { get; set; } + + public void AppendCatalogItems(LootLockerListCatalogPricesV2Response catalogPrices) + { + var concatenatedArray = new LootLockerCatalogEntry[entries.Length + catalogPrices.entries.Length]; + entries.CopyTo(concatenatedArray, 0); + catalogPrices.entries.CopyTo(concatenatedArray, entries.Length); + pagination.total = catalogPrices.pagination.total; + pagination.offset = catalogPrices.pagination.offset; + pagination.per_page = catalogPrices.pagination.per_page; + pagination.last_page = catalogPrices.pagination.last_page; + pagination.current_page = catalogPrices.pagination.current_page; + pagination.next_page = catalogPrices.pagination.next_page; + pagination.prev_page = catalogPrices.pagination.prev_page; + pagination.errors = catalogPrices.pagination.errors; + + foreach (var assetDetail in catalogPrices.asset_details) + { + asset_details.Add(assetDetail.Key, assetDetail.Value); + } + + foreach (var progressionPointDetail in catalogPrices.progression_points_details) + { + progression_points_details.Add(progressionPointDetail.Key, progressionPointDetail.Value); + } + + foreach (var progressionResetDetail in catalogPrices.progression_resets_details) + { + progression_resets_details.Add(progressionResetDetail.Key, progressionResetDetail.Value); + } + + foreach (var currencyDetail in catalogPrices.currency_details) + { + currency_details.Add(currencyDetail.Key, currencyDetail.Value); + } + + foreach (var groupDetail in catalogPrices.group_details) + { + group_details.Add(groupDetail.Key, groupDetail.Value); + } + + } + + public LootLockerListCatalogPricesV2Response() { } + + /// This is the way that the response actually looks, but we don't want to expose it, hence the conversion + private class LootLockerListCatalogItemsWithArraysResponse : LootLockerResponse + { + public LootLockerCatalog catalog { get; set; } + public LootLockerCatalogEntry[] entries { get; set; } + public LootLockerAssetDetails[] assets_details { get; set; } + public LootLockerProgressionPointDetails[] progression_points_details { get; set; } + public LootLockerProgressionResetDetails[] progression_resets_details { get; set; } + public LootLockerCurrencyDetails[] currency_details { get; set; } + public LootLockerGroupDetails[] group_details { get; set; } + public LootLockerExtendedPagination pagination { get; set; } + } + + public LootLockerListCatalogPricesV2Response(LootLockerResponse serverResponse) + { + LootLockerListCatalogItemsWithArraysResponse parsedResponse = + Deserialize(serverResponse); + success = parsedResponse.success; + statusCode = parsedResponse.statusCode; + text = parsedResponse.text; + errorData = parsedResponse.errorData; + if (!success) + { + return; + } + + catalog = parsedResponse.catalog; + entries = parsedResponse.entries; + pagination = parsedResponse.pagination; + + if (parsedResponse.assets_details != null && parsedResponse.assets_details.Length > 0) + { + asset_details = new Dictionary(); + foreach (var detail in parsedResponse.assets_details) + { + asset_details[detail.GetItemDetailsKey()] = detail; + } + } + + if (parsedResponse.progression_points_details != null && + parsedResponse.progression_points_details.Length > 0) + { + progression_points_details = new Dictionary(); + foreach (var detail in parsedResponse.progression_points_details) + { + progression_points_details[detail.GetItemDetailsKey()] = detail; + } + } + + if (parsedResponse.progression_resets_details != null && + parsedResponse.progression_resets_details.Length > 0) + { + progression_resets_details = new Dictionary(); + foreach (var detail in parsedResponse.progression_resets_details) + { + progression_resets_details[detail.GetItemDetailsKey()] = detail; + } + } + + if (parsedResponse.currency_details != null && parsedResponse.currency_details.Length > 0) + { + currency_details = new Dictionary(); + foreach (var detail in parsedResponse.currency_details) + { + currency_details[detail.GetItemDetailsKey()] = detail; + } + } + + if (parsedResponse.group_details != null && parsedResponse.group_details.Length > 0) + { + group_details = new Dictionary(); + foreach (var detail in parsedResponse.group_details) + { + group_details[detail.GetItemDetailsKey()] = detail; + } + } + } + + /// + /// + public class LootLockerInlinedCatalogEntry : LootLockerCatalogEntry + { + + /// + /// Asset details inlined for this catalog entry, will be null if the entity_kind is not asset + /// + public LootLockerAssetDetails asset_details { get; set; } + + /// + /// Progression point details inlined for this catalog entry, will be null if the entity_kind is not progression_points + /// + public LootLockerProgressionPointDetails progression_point_details { get; set; } + + /// + /// Progression reset details inlined for this catalog entry, will be null if the entity_kind is not progression_reset + /// + public LootLockerProgressionResetDetails progression_reset_details { get; set; } + + /// + /// Currency details inlined for this catalog entry, will be null if the entity_kind is not currency + /// + public LootLockerCurrencyDetails currency_details { get; set; } + + /// + /// Group details inlined for this catalog entry, will be null if the entity_kind is not group + /// + public LootLockerInlinedGroupDetails group_details { get; set; } + + public LootLockerInlinedCatalogEntry(LootLockerCatalogEntry entry, LootLockerListCatalogPricesV2Response catalogListing) + { + created_at = entry.created_at; + entity_kind = entry.entity_kind; + entity_name = entry.entity_name; + entity_id = entry.entity_id; + listings = entry.listings; + prices = entry.prices; + catalog_listing_id = entry.catalog_listing_id; + purchasable = entry.purchasable; + + switch (entity_kind) + { + case LootLockerCatalogEntryEntityKind.asset: + if (catalogListing.asset_details.ContainsKey(entry.GetItemDetailsKey())) + { + asset_details = catalogListing.asset_details[entry.GetItemDetailsKey()]; + } + break; + case LootLockerCatalogEntryEntityKind.currency: + if (catalogListing.currency_details.ContainsKey(entry.GetItemDetailsKey())) + { + currency_details = catalogListing.currency_details[entry.GetItemDetailsKey()]; + } + break; + case LootLockerCatalogEntryEntityKind.progression_points: + if (catalogListing.progression_points_details.ContainsKey(entry.GetItemDetailsKey())) + { + progression_point_details = catalogListing.progression_points_details[entry.GetItemDetailsKey()]; + } + break; + case LootLockerCatalogEntryEntityKind.progression_reset: + if (catalogListing.progression_resets_details.ContainsKey(entry.GetItemDetailsKey())) + { + progression_reset_details = catalogListing.progression_resets_details[entry.GetItemDetailsKey()]; + } + break; + case LootLockerCatalogEntryEntityKind.group: + if (!catalogListing.group_details.ContainsKey(entry.GetItemDetailsKey())) + break; + + var catalogLevelGroup = catalogListing.group_details[entry.GetItemDetailsKey()]; + + LootLockerInlinedGroupDetails inlinedGroupDetails = new LootLockerInlinedGroupDetails(); + + inlinedGroupDetails.name = catalogLevelGroup.name; + inlinedGroupDetails.description = catalogLevelGroup.description; + inlinedGroupDetails.metadata = catalogLevelGroup.metadata; + inlinedGroupDetails.id = catalogLevelGroup.id; + inlinedGroupDetails.associations = catalogLevelGroup.associations; + + foreach (var association in catalogLevelGroup.associations) + { + switch (association.kind) + { + case LootLockerCatalogEntryEntityKind.asset: + if (catalogListing.asset_details.ContainsKey(association.GetItemDetailsKey())) + { + inlinedGroupDetails.assetDetails.Add(catalogListing.asset_details[association.GetItemDetailsKey()]); + } + break; + case LootLockerCatalogEntryEntityKind.progression_points: + if (catalogListing.progression_points_details.ContainsKey(association.GetItemDetailsKey())) + { + inlinedGroupDetails.progressionPointDetails.Add(catalogListing.progression_points_details[association.GetItemDetailsKey()]); + } + break; + case LootLockerCatalogEntryEntityKind.progression_reset: + if (catalogListing.progression_resets_details.ContainsKey(association.GetItemDetailsKey())) + { + inlinedGroupDetails.progressionResetDetails.Add(catalogListing.progression_resets_details[association.GetItemDetailsKey()]); + } + break; + case LootLockerCatalogEntryEntityKind.currency: + if (catalogListing.currency_details.ContainsKey(association.GetItemDetailsKey())) + { + inlinedGroupDetails.currencyDetails.Add(catalogListing.currency_details[association.GetItemDetailsKey()]); + } + break; + case LootLockerCatalogEntryEntityKind.group: + default: + break; + } + } + + group_details = inlinedGroupDetails; + + break; + default: + break; + } + + } + } + + /// + /// Get all the entries with details inlined into the entries themselves + /// + public LootLockerInlinedCatalogEntry[] GetLootLockerInlinedCatalogEntries() + { + List inlinedEntries = new List(); + foreach (var lootLockerCatalogEntry in entries) + { + inlinedEntries.Add(new LootLockerInlinedCatalogEntry( + lootLockerCatalogEntry, + this + )); + } + return inlinedEntries.ToArray(); + } + } } diff --git a/Runtime/Game/Requests/LootLockerSessionRequest.cs b/Runtime/Game/Requests/LootLockerSessionRequest.cs index ee97ab506..154612a2d 100644 --- a/Runtime/Game/Requests/LootLockerSessionRequest.cs +++ b/Runtime/Game/Requests/LootLockerSessionRequest.cs @@ -4,11 +4,25 @@ namespace LootLocker.Requests { + + /// + /// Optional parameters that can be sent when starting a session. + /// These are a collection of configuration options relating to the player whom the session is being started for. + /// + public class LootLockerSessionOptionals + { + /// + /// Timezone in IANA format. If not supplied, will be set to UTC. + /// + public string timezone { get; set; } = null; + } + public class LootLockerSteamSessionRequest { public string game_api_key { get; set; } = LootLockerConfig.current.apiKey; public string game_version { get; set; } = LootLockerConfig.current.game_version; public string steam_ticket { get; set; } + public LootLockerSessionOptionals optionals { get; set; } = null; } public class LootLockerPlaystationNetworkVerificationRequest @@ -25,6 +39,7 @@ public class LootLockerPlaystationNetworkV3SessionRequest public string game_version => LootLockerConfig.current.game_version; public string auth_code { get; set; } public int env_iss_id { get; set; } = 256; // Default to production + public LootLockerSessionOptionals optionals { get; set; } = null; } public class LootLockerSteamSessionWithAppIdRequest : LootLockerSteamSessionRequest @@ -44,13 +59,16 @@ public LootLockerSessionRequest() { player_identifier = ""; platform = LootLockerAuthPlatform.GetPlatformRepresentation(LL_AuthPlatforms.None).PlatformString; + optionals = null; } - public LootLockerSessionRequest(string playerIdentifier, LL_AuthPlatforms forPlatform) + public LootLockerSessionRequest(string playerIdentifier, LL_AuthPlatforms forPlatform, LootLockerSessionOptionals optionals = null) { player_identifier = playerIdentifier; platform = LootLockerAuthPlatform.GetPlatformRepresentation(forPlatform).PlatformString; + this.optionals = optionals; } + public LootLockerSessionOptionals optionals { get; set; } = null; } [Serializable] @@ -60,17 +78,20 @@ public class LootLockerWhiteLabelSessionRequest : LootLockerGetRequest public string email { get; set; } public string token { get; set; } public string game_version => LootLockerConfig.current.game_version; + public LootLockerSessionOptionals optionals { get; set; } = null; public LootLockerWhiteLabelSessionRequest(string email) { this.email = email; this.token = null; + this.optionals = null; } public LootLockerWhiteLabelSessionRequest() { this.email = null; this.token = null; + this.optionals = null; } } @@ -209,10 +230,12 @@ public class LootLockerNintendoSwitchSessionRequest : LootLockerGetRequest public string game_key => LootLockerConfig.current.apiKey; public string nsa_id_token { get; set; } public string game_version => LootLockerConfig.current.game_version; + public LootLockerSessionOptionals optionals { get; set; } = null; - public LootLockerNintendoSwitchSessionRequest(string nsa_id_token) + public LootLockerNintendoSwitchSessionRequest(string nsa_id_token, LootLockerSessionOptionals optionals = null) { this.nsa_id_token = nsa_id_token; + this.optionals = optionals; } } @@ -222,10 +245,12 @@ public class LootLockerEpicSessionRequest : LootLockerGetRequest public string game_key => LootLockerConfig.current.apiKey; public string id_token { get; set; } public string game_version => LootLockerConfig.current.game_version; + public LootLockerSessionOptionals optionals { get; set; } = null; - public LootLockerEpicSessionRequest(string id_token) + public LootLockerEpicSessionRequest(string id_token, LootLockerSessionOptionals optionals = null) { this.id_token = id_token; + this.optionals = optionals; } } @@ -235,10 +260,12 @@ public class LootLockerEpicRefreshSessionRequest : LootLockerGetRequest public string game_key => LootLockerConfig.current.apiKey; public string refresh_token { get; set; } public string game_version => LootLockerConfig.current.game_version; + public LootLockerSessionOptionals optionals { get; set; } = null; - public LootLockerEpicRefreshSessionRequest(string refresh_token) + public LootLockerEpicRefreshSessionRequest(string refresh_token, LootLockerSessionOptionals optionals = null) { this.refresh_token = refresh_token; + this.optionals = optionals; } } @@ -251,6 +278,7 @@ public class LootLockerMetaSessionRequest : LootLockerGetRequest public string user_id { get; set; } public string nonce { get; set; } + public LootLockerSessionOptionals optionals { get; set; } = null; } [Serializable] @@ -259,6 +287,7 @@ public class LootLockerMetaRefreshSessionRequest : LootLockerGetRequest public string game_key => LootLockerConfig.current.apiKey; public string refresh_token { get; set; } public string game_version => LootLockerConfig.current.game_version; + public LootLockerSessionOptionals optionals { get; set; } = null; } public class LootLockerXboxOneSessionRequest : LootLockerGetRequest @@ -266,10 +295,12 @@ public class LootLockerXboxOneSessionRequest : LootLockerGetRequest public string game_key => LootLockerConfig.current.apiKey; public string xbox_user_token { get; set; } public string game_version => LootLockerConfig.current.game_version; + public LootLockerSessionOptionals optionals { get; set; } = null; - public LootLockerXboxOneSessionRequest(string xbox_user_token) + public LootLockerXboxOneSessionRequest(string xbox_user_token, LootLockerSessionOptionals optionals = null) { this.xbox_user_token = xbox_user_token; + this.optionals = optionals; } } @@ -278,10 +309,12 @@ public class LootLockerGoogleSignInSessionRequest : LootLockerGetRequest public string game_key => LootLockerConfig.current.apiKey; public string id_token { get; set; } public string game_version => LootLockerConfig.current.game_version; + public LootLockerSessionOptionals optionals { get; set; } = null; - public LootLockerGoogleSignInSessionRequest(string id_token) + public LootLockerGoogleSignInSessionRequest(string id_token, LootLockerSessionOptionals optionals = null) { this.id_token = id_token; + this.optionals = optionals; } } @@ -294,7 +327,7 @@ public class LootLockerGoogleSignInWithPlatformSessionRequest : LootLockerGoogle { public string platform { get; set; } - public LootLockerGoogleSignInWithPlatformSessionRequest(string id_token, string platform) : base(id_token) + public LootLockerGoogleSignInWithPlatformSessionRequest(string id_token, string platform, LootLockerSessionOptionals optionals = null) : base(id_token, optionals) { this.platform = platform; } @@ -305,10 +338,12 @@ public class LootLockerGoogleRefreshSessionRequest : LootLockerGetRequest public string game_key => LootLockerConfig.current.apiKey; public string refresh_token { get; set; } public string game_version => LootLockerConfig.current.game_version; + public LootLockerSessionOptionals optionals { get; set; } = null; - public LootLockerGoogleRefreshSessionRequest(string refresh_token) + public LootLockerGoogleRefreshSessionRequest(string refresh_token, LootLockerSessionOptionals optionals = null) { this.refresh_token = refresh_token; + this.optionals = optionals; } } public class LootLockerGooglePlayGamesSessionRequest @@ -316,10 +351,12 @@ public class LootLockerGooglePlayGamesSessionRequest public string game_api_key => LootLockerConfig.current.apiKey; public string auth_code { get; set; } public string game_version => LootLockerConfig.current.game_version; + public LootLockerSessionOptionals optionals { get; set; } = null; - public LootLockerGooglePlayGamesSessionRequest(string authCode) + public LootLockerGooglePlayGamesSessionRequest(string authCode, LootLockerSessionOptionals optionals = null) { this.auth_code = authCode; + this.optionals = optionals; } } @@ -328,10 +365,12 @@ public class LootLockerGooglePlayGamesRefreshSessionRequest public string game_api_key => LootLockerConfig.current.apiKey; public string refresh_token { get; set; } public string game_version => LootLockerConfig.current.game_version; + public LootLockerSessionOptionals optionals { get; set; } = null; - public LootLockerGooglePlayGamesRefreshSessionRequest(string refreshToken) + public LootLockerGooglePlayGamesRefreshSessionRequest(string refreshToken, LootLockerSessionOptionals optionals = null) { this.refresh_token = refreshToken; + this.optionals = optionals; } } @@ -340,10 +379,12 @@ public class LootLockerAppleSignInSessionRequest : LootLockerGetRequest public string game_key => LootLockerConfig.current.apiKey; public string apple_authorization_code { get; set; } public string game_version => LootLockerConfig.current.game_version; + public LootLockerSessionOptionals optionals { get; set; } = null; - public LootLockerAppleSignInSessionRequest(string apple_authorization_code) + public LootLockerAppleSignInSessionRequest(string apple_authorization_code, LootLockerSessionOptionals optionals = null) { this.apple_authorization_code = apple_authorization_code; + this.optionals = optionals; } } @@ -352,10 +393,12 @@ public class LootLockerAppleRefreshSessionRequest : LootLockerGetRequest public string game_key => LootLockerConfig.current.apiKey; public string refresh_token { get; set; } public string game_version => LootLockerConfig.current.game_version; + public LootLockerSessionOptionals optionals { get; set; } = null; - public LootLockerAppleRefreshSessionRequest(string refresh_token) + public LootLockerAppleRefreshSessionRequest(string refresh_token, LootLockerSessionOptionals optionals = null) { this.refresh_token = refresh_token; + this.optionals = optionals; } } @@ -369,8 +412,9 @@ public class LootLockerAppleGameCenterSessionRequest : LootLockerGetRequest public string signature { get; private set; } public string salt { get; private set; } public long timestamp { get; private set; } + public LootLockerSessionOptionals optionals { get; set; } = null; - public LootLockerAppleGameCenterSessionRequest(string bundleId, string playerId, string publicKeyUrl, string signature, string salt, long timestamp) + public LootLockerAppleGameCenterSessionRequest(string bundleId, string playerId, string publicKeyUrl, string signature, string salt, long timestamp, LootLockerSessionOptionals optionals = null) { this.bundle_id = bundleId; this.player_id = playerId; @@ -378,6 +422,7 @@ public LootLockerAppleGameCenterSessionRequest(string bundleId, string playerId, this.signature = signature; this.salt = salt; this.timestamp = timestamp; + this.optionals = optionals; } } @@ -386,10 +431,12 @@ public class LootLockerAppleGameCenterRefreshSessionRequest : LootLockerGetReque public string game_key => LootLockerConfig.current.apiKey; public string game_version => LootLockerConfig.current.game_version; public string refresh_token { get; private set; } + public LootLockerSessionOptionals optionals { get; set; } = null; - public LootLockerAppleGameCenterRefreshSessionRequest(string refreshToken) + public LootLockerAppleGameCenterRefreshSessionRequest(string refreshToken, LootLockerSessionOptionals optionals = null) { this.refresh_token = refreshToken; + this.optionals = optionals; } } public class LootLockerDiscordSessionRequest @@ -397,10 +444,12 @@ public class LootLockerDiscordSessionRequest public string game_key => LootLockerConfig.current.apiKey; public string access_token { get; set; } public string game_version => LootLockerConfig.current.game_version; + public LootLockerSessionOptionals optionals { get; set; } = null; - public LootLockerDiscordSessionRequest(string accessToken) + public LootLockerDiscordSessionRequest(string accessToken, LootLockerSessionOptionals optionals = null) { this.access_token = accessToken; + this.optionals = optionals; } } @@ -409,10 +458,12 @@ public class LootLockerDiscordRefreshSessionRequest public string game_key => LootLockerConfig.current.apiKey; public string refresh_token { get; set; } public string game_version => LootLockerConfig.current.game_version; + public LootLockerSessionOptionals optionals { get; set; } = null; - public LootLockerDiscordRefreshSessionRequest(string refreshToken) + public LootLockerDiscordRefreshSessionRequest(string refreshToken, LootLockerSessionOptionals optionals = null) { this.refresh_token = refreshToken; + this.optionals = optionals; } } diff --git a/Runtime/Game/Utilities/LootLockerTimezoneConverter.cs b/Runtime/Game/Utilities/LootLockerTimezoneConverter.cs new file mode 100644 index 000000000..485b6940d --- /dev/null +++ b/Runtime/Game/Utilities/LootLockerTimezoneConverter.cs @@ -0,0 +1,743 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace LootLocker +{ + public static class LootLockerTimezoneConverter + { + public static readonly Dictionary WindowsToIana = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + {"Dateline Standard Time", "Etc/GMT+12"}, + {"UTC-11", "Etc/GMT+11"}, + {"Aleutian Standard Time", "America/Adak"}, + {"Hawaiian Standard Time", "Pacific/Honolulu"}, + {"Marquesas Standard Time", "Pacific/Marquesas"}, + {"Alaskan Standard Time", "America/Anchorage"}, + {"UTC-09", "Etc/GMT+9"}, + {"Pacific Standard Time (Mexico)", "America/Tijuana"}, + {"UTC-08", "Etc/GMT+8"}, + {"Pacific Standard Time", "America/Los_Angeles"}, + {"US Mountain Standard Time", "America/Phoenix"}, + {"Mountain Standard Time (Mexico)", "America/Chihuahua"}, + {"Mountain Standard Time", "America/Denver"}, + {"Central America Standard Time", "America/Guatemala"}, + {"Central Standard Time", "America/Chicago"}, + {"Easter Island Standard Time", "Pacific/Easter"}, + {"Central Standard Time (Mexico)", "America/Mexico_City"}, + {"Canada Central Standard Time", "America/Regina"}, + {"SA Pacific Standard Time", "America/Bogota"}, + {"Eastern Standard Time (Mexico)", "America/Cancun"}, + {"Eastern Standard Time", "America/New_York"}, + {"Haiti Standard Time", "America/Port-au-Prince"}, + {"Cuba Standard Time", "America/Havana"}, + {"US Eastern Standard Time", "America/Indianapolis"}, + {"Turks And Caicos Standard Time", "America/Grand_Turk"}, + {"Paraguay Standard Time", "America/Asuncion"}, + {"Atlantic Standard Time", "America/Halifax"}, + {"Venezuela Standard Time", "America/Caracas"}, + {"Central Brazilian Standard Time", "America/Cuiaba"}, + {"SA Western Standard Time", "America/La_Paz"}, + {"Pacific SA Standard Time", "America/Santiago"}, + {"Newfoundland Standard Time", "America/St_Johns"}, + {"Tocantins Standard Time", "America/Araguaina"}, + {"E. South America Standard Time", "America/Sao_Paulo"}, + {"SA Eastern Standard Time", "America/Cayenne"}, + {"Argentina Standard Time", "America/Buenos_Aires"}, + {"Greenland Standard Time", "America/Godthab"}, + {"Montevideo Standard Time", "America/Montevideo"}, + {"Magallanes Standard Time", "America/Punta_Arenas"}, + {"Saint Pierre Standard Time", "America/Miquelon"}, + {"Bahia Standard Time", "America/Bahia"}, + {"UTC-02", "Etc/GMT+2"}, + {"Azores Standard Time", "Atlantic/Azores"}, + {"Cape Verde Standard Time", "Atlantic/Cape_Verde"}, + {"UTC", "Etc/GMT"}, + {"GMT Standard Time", "Europe/London"}, + {"Greenwich Standard Time", "Atlantic/Reykjavik"}, + {"Sao Tome Standard Time", "Africa/Sao_Tome"}, + {"Morocco Standard Time", "Africa/Casablanca"}, + {"W. Europe Standard Time", "Europe/Berlin"}, + {"Central Europe Standard Time", "Europe/Budapest"}, + {"Romance Standard Time", "Europe/Paris"}, + {"Central European Standard Time", "Europe/Warsaw"}, + {"W. Central Africa Standard Time", "Africa/Lagos"}, + {"Jordan Standard Time", "Asia/Amman"}, + {"GTB Standard Time", "Europe/Bucharest"}, + {"Middle East Standard Time", "Asia/Beirut"}, + {"Egypt Standard Time", "Africa/Cairo"}, + {"E. Europe Standard Time", "Europe/Chisinau"}, + {"Syria Standard Time", "Asia/Damascus"}, + {"West Bank Standard Time", "Asia/Hebron"}, + {"South Africa Standard Time", "Africa/Johannesburg"}, + {"FLE Standard Time", "Europe/Kiev"}, + {"Israel Standard Time", "Asia/Jerusalem"}, + {"Kaliningrad Standard Time", "Europe/Kaliningrad"}, + {"Sudan Standard Time", "Africa/Khartoum"}, + {"Libya Standard Time", "Africa/Tripoli"}, + {"Namibia Standard Time", "Africa/Windhoek"}, + {"Arabic Standard Time", "Asia/Baghdad"}, + {"Turkey Standard Time", "Europe/Istanbul"}, + {"Arab Standard Time", "Asia/Riyadh"}, + {"Belarus Standard Time", "Europe/Minsk"}, + {"E. Africa Standard Time", "Africa/Nairobi"}, + {"Iran Standard Time", "Asia/Tehran"}, + {"Arabian Standard Time", "Asia/Dubai"}, + {"Astrakhan Standard Time", "Europe/Astrakhan"}, + {"Azerbaijan Standard Time", "Asia/Baku"}, + {"Russia Time Zone 3", "Europe/Samara"}, + {"Mauritius Standard Time", "Indian/Mauritius"}, + {"Saratov Standard Time", "Europe/Saratov"}, + {"Georgian Standard Time", "Asia/Tbilisi"}, + {"Volgograd Standard Time", "Europe/Volgograd"}, + {"Caucasus Standard Time", "Asia/Yerevan"}, + {"Afghanistan Standard Time", "Asia/Kabul"}, + {"West Asia Standard Time", "Asia/Tashkent"}, + {"Ekaterinburg Standard Time", "Asia/Yekaterinburg"}, + {"Pakistan Standard Time", "Asia/Karachi"}, + {"Qyzylorda Standard Time", "Asia/Qyzylorda"}, + {"India Standard Time", "Asia/Calcutta"}, + {"Sri Lanka Standard Time", "Asia/Colombo"}, + {"Nepal Standard Time", "Asia/Katmandu"}, + {"Central Asia Standard Time", "Asia/Almaty"}, + {"Bangladesh Standard Time", "Asia/Dhaka"}, + {"Omsk Standard Time", "Asia/Omsk"}, + {"Myanmar Standard Time", "Asia/Rangoon"}, + {"SE Asia Standard Time", "Asia/Bangkok"}, + {"Altai Standard Time", "Asia/Barnaul"}, + {"W. Mongolia Standard Time", "Asia/Hovd"}, + {"North Asia Standard Time", "Asia/Krasnoyarsk"}, + {"N. Central Asia Standard Time", "Asia/Novosibirsk"}, + {"Tomsk Standard Time", "Asia/Tomsk"}, + {"China Standard Time", "Asia/Shanghai"}, + {"North Asia East Standard Time", "Asia/Irkutsk"}, + {"Singapore Standard Time", "Asia/Singapore"}, + {"W. Australia Standard Time", "Australia/Perth"}, + {"Taipei Standard Time", "Asia/Taipei"}, + {"Ulaanbaatar Standard Time", "Asia/Ulaanbaatar"}, + {"Aus Central W. Standard Time", "Australia/Eucla"}, + {"Transbaikal Standard Time", "Asia/Chita"}, + {"Tokyo Standard Time", "Asia/Tokyo"}, + {"North Korea Standard Time", "Asia/Pyongyang"}, + {"Korea Standard Time", "Asia/Seoul"}, + {"Yakutsk Standard Time", "Asia/Yakutsk"}, + {"Cen. Australia Standard Time", "Australia/Adelaide"}, + {"AUS Central Standard Time", "Australia/Darwin"}, + {"E. Australia Standard Time", "Australia/Brisbane"}, + {"AUS Eastern Standard Time", "Australia/Sydney"}, + {"West Pacific Standard Time", "Pacific/Port_Moresby"}, + {"Tasmania Standard Time", "Australia/Hobart"}, + {"Vladivostok Standard Time", "Asia/Vladivostok"}, + {"Lord Howe Standard Time", "Australia/Lord_Howe"}, + {"Bougainville Standard Time", "Pacific/Bougainville"}, + {"Russia Time Zone 10", "Asia/Srednekolymsk"}, + {"Magadan Standard Time", "Asia/Magadan"}, + {"Norfolk Standard Time", "Pacific/Norfolk"}, + {"Sakhalin Standard Time", "Asia/Sakhalin"}, + {"Central Pacific Standard Time", "Pacific/Guadalcanal"}, + {"Russia Time Zone 11", "Asia/Kamchatka"}, + {"New Zealand Standard Time", "Pacific/Auckland"}, + {"UTC+12", "Etc/GMT-12"}, + {"Fiji Standard Time", "Pacific/Fiji"}, + {"Chatham Islands Standard Time", "Pacific/Chatham"}, + {"UTC+13", "Etc/GMT-13"}, + {"Tonga Standard Time", "Pacific/Tongatapu"}, + {"Samoa Standard Time", "Pacific/Apia"}, + {"Line Islands Standard Time", "Pacific/Kiritimati"}, + }; + + public static readonly Dictionary IanaToWindows = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + {"Etc/GMT+12", "Dateline Standard Time"}, + {"Etc/GMT+11", "UTC-11"}, + {"Pacific/Pago_Pago", "UTC-11"}, + {"Pacific/Niue", "UTC-11"}, + {"Pacific/Midway", "UTC-11"}, + {"America/Adak", "Aleutian Standard Time"}, + {"Pacific/Honolulu", "Hawaiian Standard Time"}, + {"Pacific/Rarotonga", "Hawaiian Standard Time"}, + {"Pacific/Tahiti", "Hawaiian Standard Time"}, + {"Pacific/Johnston", "Hawaiian Standard Time"}, + {"Etc/GMT+10", "Hawaiian Standard Time"}, + {"Pacific/Marquesas", "Marquesas Standard Time"}, + {"America/Anchorage", "Alaskan Standard Time"}, + {"America/Juneau", "Alaskan Standard Time"}, + {"America/Metlakatla", "Alaskan Standard Time"}, + {"America/Nome", "Alaskan Standard Time"}, + {"America/Sitka", "Alaskan Standard Time"}, + {"America/Yakutat", "Alaskan Standard Time"}, + {"Etc/GMT+9", "UTC-09"}, + {"Pacific/Gambier", "UTC-09"}, + {"America/Tijuana", "Pacific Standard Time (Mexico)"}, + {"America/Santa_Isabel", "Pacific Standard Time (Mexico)"}, + {"Etc/GMT+8", "UTC-08"}, + {"Pacific/Pitcairn", "UTC-08"}, + {"America/Los_Angeles", "Pacific Standard Time"}, + {"America/Vancouver", "Pacific Standard Time"}, + {"America/Dawson", "Pacific Standard Time"}, + {"America/Whitehorse", "Pacific Standard Time"}, + {"PST8PDT", "Pacific Standard Time"}, + {"America/Phoenix", "US Mountain Standard Time"}, + {"America/Dawson_Creek", "US Mountain Standard Time"}, + {"America/Creston", "US Mountain Standard Time"}, + {"America/Fort_Nelson", "US Mountain Standard Time"}, + {"America/Hermosillo", "US Mountain Standard Time"}, + {"Etc/GMT+7", "US Mountain Standard Time"}, + {"America/Chihuahua", "Mountain Standard Time (Mexico)"}, + {"America/Mazatlan", "Mountain Standard Time (Mexico)"}, + {"America/Denver", "Mountain Standard Time"}, + {"America/Edmonton", "Mountain Standard Time"}, + {"America/Cambridge_Bay", "Mountain Standard Time"}, + {"America/Inuvik", "Mountain Standard Time"}, + {"America/Yellowknife", "Mountain Standard Time"}, + {"America/Ojinaga", "Mountain Standard Time"}, + {"America/Boise", "Mountain Standard Time"}, + {"MST7MDT", "Mountain Standard Time"}, + {"America/Guatemala", "Central America Standard Time"}, + {"America/Belize", "Central America Standard Time"}, + {"America/Costa_Rica", "Central America Standard Time"}, + {"Pacific/Galapagos", "Central America Standard Time"}, + {"America/Tegucigalpa", "Central America Standard Time"}, + {"America/Managua", "Central America Standard Time"}, + {"America/El_Salvador", "Central America Standard Time"}, + {"Etc/GMT+6", "Central America Standard Time"}, + {"America/Chicago", "Central Standard Time"}, + {"America/Winnipeg", "Central Standard Time"}, + {"America/Rainy_River", "Central Standard Time"}, + {"America/Rankin_Inlet", "Central Standard Time"}, + {"America/Resolute", "Central Standard Time"}, + {"America/Matamoros", "Central Standard Time"}, + {"America/Indiana/Knox", "Central Standard Time"}, + {"America/Indiana/Tell_City", "Central Standard Time"}, + {"America/Menominee", "Central Standard Time"}, + {"America/North_Dakota/Beulah", "Central Standard Time"}, + {"America/North_Dakota/Center", "Central Standard Time"}, + {"America/North_Dakota/New_Salem", "Central Standard Time"}, + {"CST6CDT", "Central Standard Time"}, + {"Pacific/Easter", "Easter Island Standard Time"}, + {"America/Mexico_City", "Central Standard Time (Mexico)"}, + {"America/Bahia_Banderas", "Central Standard Time (Mexico)"}, + {"America/Merida", "Central Standard Time (Mexico)"}, + {"America/Monterrey", "Central Standard Time (Mexico)"}, + {"America/Regina", "Canada Central Standard Time"}, + {"America/Swift_Current", "Canada Central Standard Time"}, + {"America/Bogota", "SA Pacific Standard Time"}, + {"America/Rio_Branco", "SA Pacific Standard Time"}, + {"America/Eirunepe", "SA Pacific Standard Time"}, + {"America/Coral_Harbour", "SA Pacific Standard Time"}, + {"America/Guayaquil", "SA Pacific Standard Time"}, + {"America/Jamaica", "SA Pacific Standard Time"}, + {"America/Cayman", "SA Pacific Standard Time"}, + {"America/Panama", "SA Pacific Standard Time"}, + {"America/Lima", "SA Pacific Standard Time"}, + {"Etc/GMT+5", "SA Pacific Standard Time"}, + {"America/Cancun", "Eastern Standard Time (Mexico)"}, + {"America/New_York", "Eastern Standard Time"}, + {"America/Nassau", "Eastern Standard Time"}, + {"America/Toronto", "Eastern Standard Time"}, + {"America/Iqaluit", "Eastern Standard Time"}, + {"America/Montreal", "Eastern Standard Time"}, + {"America/Nipigon", "Eastern Standard Time"}, + {"America/Pangnirtung", "Eastern Standard Time"}, + {"America/Thunder_Bay", "Eastern Standard Time"}, + {"America/Detroit", "Eastern Standard Time"}, + {"America/Indiana/Petersburg", "Eastern Standard Time"}, + {"America/Indiana/Vincennes", "Eastern Standard Time"}, + {"America/Indiana/Winamac", "Eastern Standard Time"}, + {"America/Kentucky/Monticello", "Eastern Standard Time"}, + {"America/Louisville", "Eastern Standard Time"}, + {"EST5EDT", "Eastern Standard Time"}, + {"America/Port-au-Prince", "Haiti Standard Time"}, + {"America/Havana", "Cuba Standard Time"}, + {"America/Indianapolis", "US Eastern Standard Time"}, + {"America/Indiana/Marengo", "US Eastern Standard Time"}, + {"America/Indiana/Vevay", "US Eastern Standard Time"}, + {"America/Grand_Turk", "Turks And Caicos Standard Time"}, + {"America/Asuncion", "Paraguay Standard Time"}, + {"America/Halifax", "Atlantic Standard Time"}, + {"Atlantic/Bermuda", "Atlantic Standard Time"}, + {"America/Glace_Bay", "Atlantic Standard Time"}, + {"America/Goose_Bay", "Atlantic Standard Time"}, + {"America/Moncton", "Atlantic Standard Time"}, + {"America/Thule", "Atlantic Standard Time"}, + {"America/Caracas", "Venezuela Standard Time"}, + {"America/Cuiaba", "Central Brazilian Standard Time"}, + {"America/Campo_Grande", "Central Brazilian Standard Time"}, + {"America/La_Paz", "SA Western Standard Time"}, + {"America/Antigua", "SA Western Standard Time"}, + {"America/Anguilla", "SA Western Standard Time"}, + {"America/Aruba", "SA Western Standard Time"}, + {"America/Barbados", "SA Western Standard Time"}, + {"America/St_Barthelemy", "SA Western Standard Time"}, + {"America/Kralendijk", "SA Western Standard Time"}, + {"America/Manaus", "SA Western Standard Time"}, + {"America/Boa_Vista", "SA Western Standard Time"}, + {"America/Porto_Velho", "SA Western Standard Time"}, + {"America/Blanc-Sablon", "SA Western Standard Time"}, + {"America/Curacao", "SA Western Standard Time"}, + {"America/Dominica", "SA Western Standard Time"}, + {"America/Santo_Domingo", "SA Western Standard Time"}, + {"America/Grenada", "SA Western Standard Time"}, + {"America/Guadeloupe", "SA Western Standard Time"}, + {"America/Guyana", "SA Western Standard Time"}, + {"America/St_Kitts", "SA Western Standard Time"}, + {"America/St_Lucia", "SA Western Standard Time"}, + {"America/Marigot", "SA Western Standard Time"}, + {"America/Martinique", "SA Western Standard Time"}, + {"America/Montserrat", "SA Western Standard Time"}, + {"America/Puerto_Rico", "SA Western Standard Time"}, + {"America/Lower_Princes", "SA Western Standard Time"}, + {"America/Port_of_Spain", "SA Western Standard Time"}, + {"America/St_Vincent", "SA Western Standard Time"}, + {"America/Tortola", "SA Western Standard Time"}, + {"America/St_Thomas", "SA Western Standard Time"}, + {"Etc/GMT+4", "SA Western Standard Time"}, + {"America/Santiago", "Pacific SA Standard Time"}, + {"America/St_Johns", "Newfoundland Standard Time"}, + {"America/Araguaina", "Tocantins Standard Time"}, + {"America/Sao_Paulo", "E. South America Standard Time"}, + {"America/Cayenne", "SA Eastern Standard Time"}, + {"Antarctica/Rothera", "SA Eastern Standard Time"}, + {"Antarctica/Palmer", "SA Eastern Standard Time"}, + {"America/Fortaleza", "SA Eastern Standard Time"}, + {"America/Belem", "SA Eastern Standard Time"}, + {"America/Maceio", "SA Eastern Standard Time"}, + {"America/Recife", "SA Eastern Standard Time"}, + {"America/Santarem", "SA Eastern Standard Time"}, + {"Atlantic/Stanley", "SA Eastern Standard Time"}, + {"America/Paramaribo", "SA Eastern Standard Time"}, + {"Etc/GMT+3", "SA Eastern Standard Time"}, + {"America/Buenos_Aires", "Argentina Standard Time"}, + {"America/Argentina/La_Rioja", "Argentina Standard Time"}, + {"America/Argentina/Rio_Gallegos", "Argentina Standard Time"}, + {"America/Argentina/Salta", "Argentina Standard Time"}, + {"America/Argentina/San_Juan", "Argentina Standard Time"}, + {"America/Argentina/San_Luis", "Argentina Standard Time"}, + {"America/Argentina/Tucuman", "Argentina Standard Time"}, + {"America/Argentina/Ushuaia", "Argentina Standard Time"}, + {"America/Catamarca", "Argentina Standard Time"}, + {"America/Cordoba", "Argentina Standard Time"}, + {"America/Jujuy", "Argentina Standard Time"}, + {"America/Mendoza", "Argentina Standard Time"}, + {"America/Godthab", "Greenland Standard Time"}, + {"America/Montevideo", "Montevideo Standard Time"}, + {"America/Punta_Arenas", "Magallanes Standard Time"}, + {"America/Miquelon", "Saint Pierre Standard Time"}, + {"America/Bahia", "Bahia Standard Time"}, + {"Etc/GMT+2", "UTC-02"}, + {"America/Noronha", "UTC-02"}, + {"Atlantic/South_Georgia", "UTC-02"}, + {"Atlantic/Azores", "Azores Standard Time"}, + {"America/Scoresbysund", "Azores Standard Time"}, + {"Atlantic/Cape_Verde", "Cape Verde Standard Time"}, + {"Etc/GMT+1", "Cape Verde Standard Time"}, + {"Etc/GMT", "UTC"}, + {"America/Danmarkshavn", "UTC"}, + {"Etc/UTC", "UTC"}, + {"Europe/London", "GMT Standard Time"}, + {"Atlantic/Canary", "GMT Standard Time"}, + {"Atlantic/Faeroe", "GMT Standard Time"}, + {"Europe/Guernsey", "GMT Standard Time"}, + {"Europe/Dublin", "GMT Standard Time"}, + {"Europe/Isle_of_Man", "GMT Standard Time"}, + {"Europe/Jersey", "GMT Standard Time"}, + {"Europe/Lisbon", "GMT Standard Time"}, + {"Atlantic/Madeira", "GMT Standard Time"}, + {"Atlantic/Reykjavik", "Greenwich Standard Time"}, + {"Africa/Ouagadougou", "Greenwich Standard Time"}, + {"Africa/Abidjan", "Greenwich Standard Time"}, + {"Africa/Accra", "Greenwich Standard Time"}, + {"Africa/Banjul", "Greenwich Standard Time"}, + {"Africa/Conakry", "Greenwich Standard Time"}, + {"Africa/Bissau", "Greenwich Standard Time"}, + {"Africa/Monrovia", "Greenwich Standard Time"}, + {"Africa/Bamako", "Greenwich Standard Time"}, + {"Africa/Nouakchott", "Greenwich Standard Time"}, + {"Atlantic/St_Helena", "Greenwich Standard Time"}, + {"Africa/Freetown", "Greenwich Standard Time"}, + {"Africa/Dakar", "Greenwich Standard Time"}, + {"Africa/Lome", "Greenwich Standard Time"}, + {"Africa/Sao_Tome", "Sao Tome Standard Time"}, + {"Africa/Casablanca", "Morocco Standard Time"}, + {"Africa/El_Aaiun", "Morocco Standard Time"}, + {"Europe/Berlin", "W. Europe Standard Time"}, + {"Europe/Andorra", "W. Europe Standard Time"}, + {"Europe/Vienna", "W. Europe Standard Time"}, + {"Europe/Zurich", "W. Europe Standard Time"}, + {"Europe/Busingen", "W. Europe Standard Time"}, + {"Europe/Gibraltar", "W. Europe Standard Time"}, + {"Europe/Rome", "W. Europe Standard Time"}, + {"Europe/Vaduz", "W. Europe Standard Time"}, + {"Europe/Luxembourg", "W. Europe Standard Time"}, + {"Europe/Monaco", "W. Europe Standard Time"}, + {"Europe/Malta", "W. Europe Standard Time"}, + {"Europe/Amsterdam", "W. Europe Standard Time"}, + {"Europe/Oslo", "W. Europe Standard Time"}, + {"Europe/Stockholm", "W. Europe Standard Time"}, + {"Arctic/Longyearbyen", "W. Europe Standard Time"}, + {"Europe/San_Marino", "W. Europe Standard Time"}, + {"Europe/Vatican", "W. Europe Standard Time"}, + {"Europe/Budapest", "Central Europe Standard Time"}, + {"Europe/Tirane", "Central Europe Standard Time"}, + {"Europe/Prague", "Central Europe Standard Time"}, + {"Europe/Podgorica", "Central Europe Standard Time"}, + {"Europe/Belgrade", "Central Europe Standard Time"}, + {"Europe/Ljubljana", "Central Europe Standard Time"}, + {"Europe/Bratislava", "Central Europe Standard Time"}, + {"Europe/Paris", "Romance Standard Time"}, + {"Europe/Brussels", "Romance Standard Time"}, + {"Europe/Copenhagen", "Romance Standard Time"}, + {"Europe/Madrid", "Romance Standard Time"}, + {"Africa/Ceuta", "Romance Standard Time"}, + {"Europe/Warsaw", "Central European Standard Time"}, + {"Europe/Sarajevo", "Central European Standard Time"}, + {"Europe/Zagreb", "Central European Standard Time"}, + {"Europe/Skopje", "Central European Standard Time"}, + {"Africa/Lagos", "W. Central Africa Standard Time"}, + {"Africa/Luanda", "W. Central Africa Standard Time"}, + {"Africa/Porto-Novo", "W. Central Africa Standard Time"}, + {"Africa/Kinshasa", "W. Central Africa Standard Time"}, + {"Africa/Bangui", "W. Central Africa Standard Time"}, + {"Africa/Brazzaville", "W. Central Africa Standard Time"}, + {"Africa/Douala", "W. Central Africa Standard Time"}, + {"Africa/Algiers", "W. Central Africa Standard Time"}, + {"Africa/Libreville", "W. Central Africa Standard Time"}, + {"Africa/Malabo", "W. Central Africa Standard Time"}, + {"Africa/Niamey", "W. Central Africa Standard Time"}, + {"Africa/Ndjamena", "W. Central Africa Standard Time"}, + {"Africa/Tunis", "W. Central Africa Standard Time"}, + {"Etc/GMT-1", "W. Central Africa Standard Time"}, + {"Asia/Amman", "Jordan Standard Time"}, + {"Europe/Bucharest", "GTB Standard Time"}, + {"Asia/Nicosia", "GTB Standard Time"}, + {"Asia/Famagusta", "GTB Standard Time"}, + {"Europe/Athens", "GTB Standard Time"}, + {"Asia/Beirut", "Middle East Standard Time"}, + {"Africa/Cairo", "Egypt Standard Time"}, + {"Europe/Chisinau", "E. Europe Standard Time"}, + {"Asia/Damascus", "Syria Standard Time"}, + {"Asia/Hebron", "West Bank Standard Time"}, + {"Asia/Gaza", "West Bank Standard Time"}, + {"Africa/Johannesburg", "South Africa Standard Time"}, + {"Africa/Bujumbura", "South Africa Standard Time"}, + {"Africa/Gaborone", "South Africa Standard Time"}, + {"Africa/Lubumbashi", "South Africa Standard Time"}, + {"Africa/Maseru", "South Africa Standard Time"}, + {"Africa/Blantyre", "South Africa Standard Time"}, + {"Africa/Maputo", "South Africa Standard Time"}, + {"Africa/Kigali", "South Africa Standard Time"}, + {"Africa/Mbabane", "South Africa Standard Time"}, + {"Africa/Lusaka", "South Africa Standard Time"}, + {"Africa/Harare", "South Africa Standard Time"}, + {"Etc/GMT-2", "South Africa Standard Time"}, + {"Europe/Kiev", "FLE Standard Time"}, + {"Europe/Mariehamn", "FLE Standard Time"}, + {"Europe/Sofia", "FLE Standard Time"}, + {"Europe/Tallinn", "FLE Standard Time"}, + {"Europe/Helsinki", "FLE Standard Time"}, + {"Europe/Vilnius", "FLE Standard Time"}, + {"Europe/Riga", "FLE Standard Time"}, + {"Europe/Uzhgorod", "FLE Standard Time"}, + {"Europe/Zaporozhye", "FLE Standard Time"}, + {"Asia/Jerusalem", "Israel Standard Time"}, + {"Europe/Kaliningrad", "Kaliningrad Standard Time"}, + {"Africa/Khartoum", "Sudan Standard Time"}, + {"Africa/Tripoli", "Libya Standard Time"}, + {"Africa/Windhoek", "Namibia Standard Time"}, + {"Asia/Baghdad", "Arabic Standard Time"}, + {"Europe/Istanbul", "Turkey Standard Time"}, + {"Asia/Riyadh", "Arab Standard Time"}, + {"Asia/Bahrain", "Arab Standard Time"}, + {"Asia/Kuwait", "Arab Standard Time"}, + {"Asia/Qatar", "Arab Standard Time"}, + {"Asia/Aden", "Arab Standard Time"}, + {"Europe/Minsk", "Belarus Standard Time"}, + {"Europe/Moscow", "Russian Standard Time"}, + {"Europe/Kirov", "Russian Standard Time"}, + {"Europe/Simferopol", "Russian Standard Time"}, + {"Africa/Nairobi", "E. Africa Standard Time"}, + {"Antarctica/Syowa", "E. Africa Standard Time"}, + {"Africa/Djibouti", "E. Africa Standard Time"}, + {"Africa/Asmera", "E. Africa Standard Time"}, + {"Africa/Addis_Ababa", "E. Africa Standard Time"}, + {"Indian/Comoro", "E. Africa Standard Time"}, + {"Indian/Antananarivo", "E. Africa Standard Time"}, + {"Africa/Mogadishu", "E. Africa Standard Time"}, + {"Africa/Juba", "E. Africa Standard Time"}, + {"Africa/Dar_es_Salaam", "E. Africa Standard Time"}, + {"Africa/Kampala", "E. Africa Standard Time"}, + {"Indian/Mayotte", "E. Africa Standard Time"}, + {"Etc/GMT-3", "E. Africa Standard Time"}, + {"Asia/Tehran", "Iran Standard Time"}, + {"Asia/Dubai", "Arabian Standard Time"}, + {"Asia/Muscat", "Arabian Standard Time"}, + {"Etc/GMT-4", "Arabian Standard Time"}, + {"Europe/Astrakhan", "Astrakhan Standard Time"}, + {"Europe/Ulyanovsk", "Astrakhan Standard Time"}, + {"Asia/Baku", "Azerbaijan Standard Time"}, + {"Europe/Samara", "Russia Time Zone 3"}, + {"Indian/Mauritius", "Mauritius Standard Time"}, + {"Indian/Reunion", "Mauritius Standard Time"}, + {"Indian/Mahe", "Mauritius Standard Time"}, + {"Europe/Saratov", "Saratov Standard Time"}, + {"Asia/Tbilisi", "Georgian Standard Time"}, + {"Europe/Volgograd", "Volgograd Standard Time"}, + {"Asia/Yerevan", "Caucasus Standard Time"}, + {"Asia/Kabul", "Afghanistan Standard Time"}, + {"Asia/Tashkent", "West Asia Standard Time"}, + {"Antarctica/Mawson", "West Asia Standard Time"}, + {"Asia/Oral", "West Asia Standard Time"}, + {"Asia/Aqtau", "West Asia Standard Time"}, + {"Asia/Aqtobe", "West Asia Standard Time"}, + {"Asia/Atyrau", "West Asia Standard Time"}, + {"Indian/Maldives", "West Asia Standard Time"}, + {"Indian/Kerguelen", "West Asia Standard Time"}, + {"Asia/Dushanbe", "West Asia Standard Time"}, + {"Asia/Ashgabat", "West Asia Standard Time"}, + {"Asia/Samarkand", "West Asia Standard Time"}, + {"Etc/GMT-5", "West Asia Standard Time"}, + {"Asia/Yekaterinburg", "Ekaterinburg Standard Time"}, + {"Asia/Karachi", "Pakistan Standard Time"}, + {"Asia/Qyzylorda", "Qyzylorda Standard Time"}, + {"Asia/Calcutta", "India Standard Time"}, + {"Asia/Colombo", "Sri Lanka Standard Time"}, + {"Asia/Katmandu", "Nepal Standard Time"}, + {"Asia/Almaty", "Central Asia Standard Time"}, + {"Antarctica/Vostok", "Central Asia Standard Time"}, + {"Asia/Urumqi", "Central Asia Standard Time"}, + {"Indian/Chagos", "Central Asia Standard Time"}, + {"Asia/Bishkek", "Central Asia Standard Time"}, + {"Asia/Qostanay", "Central Asia Standard Time"}, + {"Etc/GMT-6", "Central Asia Standard Time"}, + {"Asia/Dhaka", "Bangladesh Standard Time"}, + {"Asia/Thimphu", "Bangladesh Standard Time"}, + {"Asia/Omsk", "Omsk Standard Time"}, + {"Asia/Rangoon", "Myanmar Standard Time"}, + {"Indian/Cocos", "Myanmar Standard Time"}, + {"Asia/Bangkok", "SE Asia Standard Time"}, + {"Antarctica/Davis", "SE Asia Standard Time"}, + {"Indian/Christmas", "SE Asia Standard Time"}, + {"Asia/Jakarta", "SE Asia Standard Time"}, + {"Asia/Pontianak", "SE Asia Standard Time"}, + {"Asia/Phnom_Penh", "SE Asia Standard Time"}, + {"Asia/Vientiane", "SE Asia Standard Time"}, + {"Asia/Saigon", "SE Asia Standard Time"}, + {"Etc/GMT-7", "SE Asia Standard Time"}, + {"Asia/Barnaul", "Altai Standard Time"}, + {"Asia/Hovd", "W. Mongolia Standard Time"}, + {"Asia/Krasnoyarsk", "North Asia Standard Time"}, + {"Asia/Novokuznetsk", "North Asia Standard Time"}, + {"Asia/Novosibirsk", "N. Central Asia Standard Time"}, + {"Asia/Tomsk", "Tomsk Standard Time"}, + {"Asia/Shanghai", "China Standard Time"}, + {"Asia/Hong_Kong", "China Standard Time"}, + {"Asia/Macau", "China Standard Time"}, + {"Asia/Irkutsk", "North Asia East Standard Time"}, + {"Asia/Singapore", "Singapore Standard Time"}, + {"Antarctica/Casey", "Singapore Standard Time"}, + {"Asia/Brunei", "Singapore Standard Time"}, + {"Asia/Makassar", "Singapore Standard Time"}, + {"Asia/Kuala_Lumpur", "Singapore Standard Time"}, + {"Asia/Kuching", "Singapore Standard Time"}, + {"Asia/Manila", "Singapore Standard Time"}, + {"Etc/GMT-8", "Singapore Standard Time"}, + {"Australia/Perth", "W. Australia Standard Time"}, + {"Asia/Taipei", "Taipei Standard Time"}, + {"Asia/Ulaanbaatar", "Ulaanbaatar Standard Time"}, + {"Asia/Choibalsan", "Ulaanbaatar Standard Time"}, + {"Australia/Eucla", "Aus Central W. Standard Time"}, + {"Asia/Chita", "Transbaikal Standard Time"}, + {"Asia/Tokyo", "Tokyo Standard Time"}, + {"Asia/Jayapura", "Tokyo Standard Time"}, + {"Pacific/Palau", "Tokyo Standard Time"}, + {"Asia/Dili", "Tokyo Standard Time"}, + {"Etc/GMT-9", "Tokyo Standard Time"}, + {"Asia/Pyongyang", "North Korea Standard Time"}, + {"Asia/Seoul", "Korea Standard Time"}, + {"Asia/Yakutsk", "Yakutsk Standard Time"}, + {"Asia/Khandyga", "Yakutsk Standard Time"}, + {"Australia/Adelaide", "Cen. Australia Standard Time"}, + {"Australia/Broken_Hill", "Cen. Australia Standard Time"}, + {"Australia/Darwin", "AUS Central Standard Time"}, + {"Australia/Brisbane", "E. Australia Standard Time"}, + {"Australia/Lindeman", "E. Australia Standard Time"}, + {"Australia/Sydney", "AUS Eastern Standard Time"}, + {"Australia/Melbourne", "AUS Eastern Standard Time"}, + {"Pacific/Port_Moresby", "West Pacific Standard Time"}, + {"Antarctica/DumontDUrville", "West Pacific Standard Time"}, + {"Pacific/Truk", "West Pacific Standard Time"}, + {"Pacific/Guam", "West Pacific Standard Time"}, + {"Pacific/Saipan", "West Pacific Standard Time"}, + {"Etc/GMT-10", "West Pacific Standard Time"}, + {"Australia/Hobart", "Tasmania Standard Time"}, + {"Australia/Currie", "Tasmania Standard Time"}, + {"Asia/Vladivostok", "Vladivostok Standard Time"}, + {"Asia/Ust-Nera", "Vladivostok Standard Time"}, + {"Australia/Lord_Howe", "Lord Howe Standard Time"}, + {"Pacific/Bougainville", "Bougainville Standard Time"}, + {"Asia/Srednekolymsk", "Russia Time Zone 10"}, + {"Asia/Magadan", "Magadan Standard Time"}, + {"Pacific/Norfolk", "Norfolk Standard Time"}, + {"Asia/Sakhalin", "Sakhalin Standard Time"}, + {"Pacific/Guadalcanal", "Central Pacific Standard Time"}, + {"Antarctica/Macquarie", "Central Pacific Standard Time"}, + {"Pacific/Ponape", "Central Pacific Standard Time"}, + {"Pacific/Kosrae", "Central Pacific Standard Time"}, + {"Pacific/Noumea", "Central Pacific Standard Time"}, + {"Pacific/Efate", "Central Pacific Standard Time"}, + {"Etc/GMT-11", "Central Pacific Standard Time"}, + {"Asia/Kamchatka", "Russia Time Zone 11"}, + {"Asia/Anadyr", "Russia Time Zone 11"}, + {"Pacific/Auckland", "New Zealand Standard Time"}, + {"Antarctica/McMurdo", "New Zealand Standard Time"}, + {"Etc/GMT-12", "UTC+12"}, + {"Pacific/Tarawa", "UTC+12"}, + {"Pacific/Majuro", "UTC+12"}, + {"Pacific/Kwajalein", "UTC+12"}, + {"Pacific/Nauru", "UTC+12"}, + {"Pacific/Funafuti", "UTC+12"}, + {"Pacific/Wake", "UTC+12"}, + {"Pacific/Wallis", "UTC+12"}, + {"Pacific/Fiji", "Fiji Standard Time"}, + {"Pacific/Chatham", "Chatham Islands Standard Time"}, + {"Etc/GMT-13", "UTC+13"}, + {"Pacific/Enderbury", "UTC+13"}, + {"Pacific/Fakaofo", "UTC+13"}, + {"Pacific/Tongatapu", "Tonga Standard Time"}, + {"Pacific/Apia", "Samoa Standard Time"}, + {"Pacific/Kiritimati", "Line Islands Standard Time"}, + {"Etc/GMT-14", "Line Islands Standard Time"}, + }; + + public static string ConvertWindowsToIanaTzString(string windowsTimezone) + { + if (windowsTimezone == null) + { + // Return default IANA timezone if null + return "Etc/UTC"; + } + + if (WindowsToIana.TryGetValue(windowsTimezone, out string iana)) + return iana; + + // Return default IANA timezone if not found + return "Etc/UTC"; + } + + public static string ConvertIANAToWindowsTzString(string ianaTimezone) + { + // Reverse dictionary for IANA to Windows + var ianaToWindows = WindowsToIana.ToDictionary(x => x.Value, x => x.Key); + + if (ianaToWindows.TryGetValue(ianaTimezone, out string windows)) + { + return windows; + } + // Return default Windows timezone if not found + return "UTC"; + } + + public static bool isValidIanaTimezone(string timezone) + { + return IanaToWindows.ContainsKey(timezone); + } + + public static bool isValidWindowsTimezone(string timezone) + { + return WindowsToIana.ContainsKey(timezone); + } + + public static string convertUTCOffsetToIanaTzString(int utcOffset) + { +#if UNITY_2021_1_OR_NEWER + utcOffset = Math.Clamp(utcOffset, -12, 14); +#endif + if (utcOffset < 0) + { + return $"Etc/GMT+{Math.Abs(utcOffset)}"; // Note the reversed sign for Etc/GMT + } + else if (utcOffset > 0) + { + return $"Etc/GMT-{utcOffset}"; // Note the reversed sign for Etc/GMT + } + else // utcOffset == 0 + { + return "Etc/UTC"; + } + } + + public static string convertGMTOffsetToIanaTzString(int gmtOffset) + { +#if UNITY_2021_1_OR_NEWER + gmtOffset = Math.Clamp(gmtOffset, -14, 12); +#endif + if (gmtOffset < 0) + { + return $"Etc/GMT-{Math.Abs(gmtOffset)}"; + } + else if (gmtOffset > 0) + { + return $"Etc/GMT+{gmtOffset}"; + } + else // gmtOffset == 0 + { + return "Etc/UTC"; + } + } + + public static bool TryConvertStringToIanaTzString(string timezone, out string ianaTimezone) + { + if (string.IsNullOrEmpty(timezone)) + { + ianaTimezone = "Etc/UTC"; // Default fallback + return false; + } + + if(int.TryParse(timezone, out int offset)) + { + ianaTimezone = convertUTCOffsetToIanaTzString(offset); + return true; + } + else if (isValidIanaTimezone(timezone)) + { + ianaTimezone = timezone; + return true; + } + else if (isValidWindowsTimezone(timezone)) + { + ianaTimezone = ConvertWindowsToIanaTzString(timezone); + return true; + } + else + { + ianaTimezone = "Etc/UTC"; // Default fallback + return false; + } + } + + public static bool TryConvertStringToWindowsTzString(string timezone, out string windowsTimezone) + { + if (string.IsNullOrEmpty(timezone)) + { + windowsTimezone = "UTC"; // Default fallback + return false; + } + + else if (isValidIanaTimezone(timezone)) + { + windowsTimezone = ConvertIANAToWindowsTzString(timezone); + return true; + } + else if (isValidWindowsTimezone(timezone)) + { + windowsTimezone = timezone; + return true; + } + else + { + windowsTimezone = "UTC"; // Default fallback + return false; + } + } + } +} diff --git a/Runtime/Game/Utilities/LootLockerTimezoneConverter.cs.meta b/Runtime/Game/Utilities/LootLockerTimezoneConverter.cs.meta new file mode 100644 index 000000000..2fb556c84 --- /dev/null +++ b/Runtime/Game/Utilities/LootLockerTimezoneConverter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 415b91273747bfd45a40756442e73878 \ No newline at end of file diff --git a/Tests/LootLockerTests/PlayMode/BroadcastTests.cs b/Tests/LootLockerTests/PlayMode/BroadcastTests.cs new file mode 100644 index 000000000..43537d1c5 --- /dev/null +++ b/Tests/LootLockerTests/PlayMode/BroadcastTests.cs @@ -0,0 +1,76 @@ +using System; +using LootLocker; +using LootLocker.Requests; +using NUnit.Framework; + +namespace LootLockerTests.PlayMode +{ + public class BroadcastJsonTests + { + + + [Test, Category("LootLocker"), Category("LootLockerCI")] + public void Broadcasts_DeserializingBroadcasts_PopulatesConvenienceResponseObject() + { + // Given + string json = "{\"broadcasts\":[{\"id\":\"01K4QGQTNHTXHM9ET5238NB4NF\",\"name\":\"Broadcast no 5\",\"game_name\":\"MetaDataTest Aug24\",\"games\":[{\"id\":93893,\"name\":\"MetaDataTest Aug24\"}],\"publication_settings\":[{\"start\":\"2025-09-09T11:12:00Z\",\"end\":\"2025-09-11T15:15:00Z\",\"tz\":\"UTC\"}],\"languages\":[{\"language_code\":\"en\",\"localizations\":[{\"key\":\"ll.headline\",\"value\":\"Broadcast Title\"},{\"key\":\"ll.image_url\",\"value\":\"https://google.com\"},{\"key\":\"ll.action\",\"value\":\"window.open(\\\"/some/page\\\")\"},{\"key\":\"ll.body\",\"value\":\"Broadcast content\"},{\"key\":\"extra-key\",\"value\":\"extra-value\"},{\"key\":\"final-toll\",\"value\":\"troll toll\"},{\"key\":\"what are the requirements here?\",\"value\":\"{ \\\"i-dont-know\\\": true }\"}]},{\"language_code\":\"sv\",\"localizations\":[{\"key\":\"ll.headline\",\"value\":\"Meddelandetitel\"},{\"key\":\"ll.image_url\",\"value\":\"https://feet.com\"},{\"key\":\"ll.action\",\"value\":\"window.open(\\\"/some/page\\\")\"},{\"key\":\"ll.body\",\"value\":\"Lite innehåll\"},{\"key\":\"🤩🤩 Hello 🤩🤩\",\"value\":\"kjhasdkjah\"}]}]}],\"pagination\":{\"errors\":null,\"per_page\":10,\"offset\":0,\"total\":1,\"last_page\":1,\"current_page\":1,\"next_page\":null,\"prev_page\":null}}"; + + // When + var deserialized = + LootLockerJson.DeserializeObject<__LootLockerInternalListBroadcastsResponse>(json); + + var broadcastResponse = new LootLockerListBroadcastsResponse(deserialized); + // Then + Assert.IsNotNull(broadcastResponse); + Assert.IsNotEmpty(broadcastResponse.broadcasts); + Assert.AreEqual(1, broadcastResponse.broadcasts.Length); + Assert.AreEqual("01K4QGQTNHTXHM9ET5238NB4NF", broadcastResponse.broadcasts[0].id); + Assert.AreEqual("Broadcast no 5", broadcastResponse.broadcasts[0].name); + Assert.IsNotEmpty(broadcastResponse.broadcasts[0].publication_settings); + Assert.AreEqual(1, broadcastResponse.broadcasts[0].publication_settings.Length); + Assert.AreEqual(DateTime.Parse("2025-09-09T11:12:00Z").ToUniversalTime(), broadcastResponse.broadcasts[0].publication_settings[0].start.ToUniversalTime()); + Assert.AreEqual(DateTime.Parse("2025-09-11T15:15:00Z").ToUniversalTime(), broadcastResponse.broadcasts[0].publication_settings[0].end.ToUniversalTime()); + Assert.AreEqual("UTC", broadcastResponse.broadcasts[0].publication_settings[0].tz); + + Assert.IsNotEmpty(broadcastResponse.broadcasts[0].languages); + Assert.AreEqual(2, broadcastResponse.broadcasts[0].language_codes.Length); + Assert.AreEqual(2, broadcastResponse.broadcasts[0].languages.Count); + Assert.AreEqual("en", broadcastResponse.broadcasts[0].language_codes[0]); + + var enLang = broadcastResponse.broadcasts[0].languages["en"]; + Assert.IsNotNull(enLang); + + Assert.IsNotEmpty(enLang.localizations); + Assert.AreEqual(enLang.language_code, "en"); + Assert.AreEqual(enLang.headline, "Broadcast Title"); + Assert.AreEqual(enLang.image_url, "https://google.com"); + Assert.AreEqual(enLang.action, "window.open(\"/some/page\")"); + Assert.AreEqual(enLang.body, "Broadcast content"); + Assert.AreEqual(3, enLang.localizations.Count); + Assert.AreEqual(3, enLang.localization_keys.Length); + + Assert.AreEqual("extra-key", enLang.localization_keys[0]); + Assert.AreEqual("extra-value", enLang.localizations["extra-key"]); + Assert.AreEqual("final-toll", enLang.localization_keys[1]); + Assert.AreEqual("troll toll", enLang.localizations["final-toll"]); + Assert.AreEqual("what are the requirements here?", enLang.localization_keys[2]); + Assert.AreEqual("{ \"i-dont-know\": true }", enLang.localizations["what are the requirements here?"]); + + Assert.AreEqual("sv", broadcastResponse.broadcasts[0].language_codes[1]); + var sweLang = broadcastResponse.broadcasts[0].languages["sv"]; + Assert.IsNotNull(sweLang); + + Assert.IsNotEmpty(sweLang.localizations); + Assert.AreEqual(sweLang.language_code, "sv"); + Assert.AreEqual(sweLang.headline, "Meddelandetitel"); + Assert.AreEqual(sweLang.image_url, "https://feet.com"); + Assert.AreEqual(sweLang.action, "window.open(\"/some/page\")"); + Assert.AreEqual(sweLang.body, "Lite innehåll"); + Assert.AreEqual(1, sweLang.localizations.Count); + Assert.AreEqual(1, sweLang.localization_keys.Length); + + Assert.AreEqual("🤩🤩 Hello 🤩🤩", sweLang.localization_keys[0]); + Assert.AreEqual("kjhasdkjah", sweLang.localizations["🤩🤩 Hello 🤩🤩"]); + } + } +} \ No newline at end of file diff --git a/Tests/LootLockerTests/PlayMode/BroadcastTests.cs.meta b/Tests/LootLockerTests/PlayMode/BroadcastTests.cs.meta new file mode 100644 index 000000000..027cfebdd --- /dev/null +++ b/Tests/LootLockerTests/PlayMode/BroadcastTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e9484e14057ef7e40b7924953ab5613a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json index 3f1db1c11..38b3888f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.lootlocker.lootlockersdk", - "version": "6.2.0", + "version": "6.3.0", "displayName": "LootLocker", "description": "LootLocker is a game backend-as-a-service with plug and play tools to upgrade your game and give your players the best experience possible. Designed for teams of all shapes and sizes, on mobile, PC and console. From solo developers, indie teams, AAA studios, and publishers. Built with cross-platform in mind.\n\n▪ Manage your game\nSave time and upgrade your game with leaderboards, progression, and more. Completely off-the-shelf features, built to work with any game and platform.\n\n▪ Manage your content\nTake charge of your game's content on all platforms, in one place. Sort, edit and manage everything, from cosmetics to currencies, UGC to DLC. Without breaking a sweat.\n\n▪ Manage your players\nStore your players' data together in one place. Access their profile and friends list cross-platform. Manage reports, messages, refunds and gifts to keep them hooked.\n", "unity": "2019.2",